mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-30 21:23:00 +00:00 
			
		
		
		
	Allow pausing Lua machines
TimeoutState now introduces a TIMESLICE, which is the maximum period of time a computer can run before we will look into pausing it. When we have executed a task for more than this period, and if there are other computers waiting to execute work, then we will suspend the machine. Suspending the machine sets a flag on the ComputerExecutor, and pauses the "cumulative" time - the time spent handling this particular event. When resuming the machine, we restart our timer and resume the machine.
This commit is contained in:
		| @@ -95,7 +95,11 @@ final class ComputerExecutor | ||||
|     private final Object queueLock = new Object(); | ||||
|  | ||||
|     /** | ||||
|      * Determines if this executer is present within {@link ComputerThread}. | ||||
|      * Determines if this executor is present within {@link ComputerThread}. | ||||
|      * | ||||
|      * @see #queueLock | ||||
|      * @see #enqueue() | ||||
|      * @see #afterWork() | ||||
|      */ | ||||
|     volatile boolean onComputerQueue = false; | ||||
|  | ||||
| @@ -117,6 +121,14 @@ final class ComputerExecutor | ||||
|      */ | ||||
|     private final Queue<Event> eventQueue = new ArrayDeque<>(); | ||||
|  | ||||
|     /** | ||||
|      * Whether we interrupted an event and so should resume it instead of executing another task. | ||||
|      * | ||||
|      * @see #work() | ||||
|      * @see #resumeMachine(String, Object[]) | ||||
|      */ | ||||
|     private boolean interruptedEvent = false; | ||||
|  | ||||
|     /** | ||||
|      * Whether this executor has been closed, and will no longer accept any incoming commands or events. | ||||
|      * | ||||
| @@ -391,6 +403,7 @@ final class ComputerExecutor | ||||
|         { | ||||
|             // Reset the terminal and event queue | ||||
|             computer.getTerminal().reset(); | ||||
|             interruptedEvent = false; | ||||
|             synchronized( queueLock ) | ||||
|             { | ||||
|                 eventQueue.clear(); | ||||
| @@ -432,6 +445,7 @@ final class ComputerExecutor | ||||
|         try | ||||
|         { | ||||
|             isOn = false; | ||||
|             interruptedEvent = false; | ||||
|             synchronized( queueLock ) | ||||
|             { | ||||
|                 eventQueue.clear(); | ||||
| @@ -468,7 +482,7 @@ final class ComputerExecutor | ||||
|      */ | ||||
|     void beforeWork() | ||||
|     { | ||||
|         timeout.reset(); | ||||
|         timeout.startTimer(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -477,11 +491,20 @@ final class ComputerExecutor | ||||
|      */ | ||||
|     void afterWork() | ||||
|     { | ||||
|         Tracking.addTaskTiming( getComputer(), timeout.nanoSinceStart() ); | ||||
|         if( interruptedEvent ) | ||||
|         { | ||||
|             timeout.pauseTimer(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             timeout.stopTimer(); | ||||
|         } | ||||
|  | ||||
|         Tracking.addTaskTiming( getComputer(), timeout.nanoCurrent() ); | ||||
|  | ||||
|         synchronized( queueLock ) | ||||
|         { | ||||
|             if( eventQueue.isEmpty() && command == null ) | ||||
|             if( !interruptedEvent && eventQueue.isEmpty() && command == null ) | ||||
|             { | ||||
|                 onComputerQueue = false; | ||||
|             } | ||||
| @@ -510,6 +533,16 @@ final class ComputerExecutor | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             if( interruptedEvent ) | ||||
|             { | ||||
|                 interruptedEvent = false; | ||||
|                 if( machine != null ) | ||||
|                 { | ||||
|                     resumeMachine( null, null ); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             StateCommand command; | ||||
|             Event event = null; | ||||
|             synchronized( queueLock ) | ||||
| @@ -601,6 +634,7 @@ final class ComputerExecutor | ||||
|     private void resumeMachine( String event, Object[] args ) throws InterruptedException | ||||
|     { | ||||
|         MachineResult result = machine.handleEvent( event, args ); | ||||
|         interruptedEvent = result.isPause(); | ||||
|         if( !result.isError() ) return; | ||||
|  | ||||
|         displayFailure( "Error running computer", result.getMessage() ); | ||||
|   | ||||
| @@ -126,6 +126,16 @@ public class ComputerThread | ||||
|         computersActive.add( computer ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Determine if the thread has computers queued up | ||||
|      * | ||||
|      * @return If we have work queued up. | ||||
|      */ | ||||
|     static boolean hasPendingWork() | ||||
|     { | ||||
|         return computersActive.size() > 0; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Observes all currently active {@link TaskRunner}s and terminates their tasks once they have exceeded the hard | ||||
|      * abort limit. | ||||
| @@ -158,7 +168,7 @@ public class ComputerThread | ||||
|  | ||||
|                             // If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT), | ||||
|                             // then we can let the Lua machine do its work. | ||||
|                             long afterStart = executor.timeout.nanoSinceStart(); | ||||
|                             long afterStart = executor.timeout.nanoCumulative(); | ||||
|                             long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT; | ||||
|                             if( afterHardAbort < 0 ) continue; | ||||
|  | ||||
|   | ||||
| @@ -27,6 +27,11 @@ import java.util.concurrent.TimeUnit; | ||||
|  */ | ||||
| public final class TimeoutState | ||||
| { | ||||
|     /** | ||||
|      * The time to run a task before pausing in nanoseconds | ||||
|      */ | ||||
|     private static final long TIMESLICE = TimeUnit.MILLISECONDS.toNanos( 40 ); | ||||
|  | ||||
|     /** | ||||
|      * The total time a task is allowed to run before aborting in nanoseconds | ||||
|      */ | ||||
| @@ -37,16 +42,49 @@ public final class TimeoutState | ||||
|      */ | ||||
|     static final long ABORT_TIMEOUT = TimeUnit.MILLISECONDS.toNanos( 1500 ); | ||||
|  | ||||
|     /** | ||||
|      * The error message to display when we trigger an abort. | ||||
|      */ | ||||
|     public static final String ABORT_MESSAGE = "Too long without yielding"; | ||||
|  | ||||
|     private volatile boolean softAbort; | ||||
|     private boolean paused; | ||||
|     private boolean softAbort; | ||||
|     private volatile boolean hardAbort; | ||||
|  | ||||
|     private long nanoTime; | ||||
|     private long nanoCumulative; | ||||
|     private long nanoCurrent; | ||||
|  | ||||
|     long nanoSinceStart() | ||||
|     long nanoCumulative() | ||||
|     { | ||||
|         return System.nanoTime() - nanoTime; | ||||
|         return System.nanoTime() - nanoCumulative; | ||||
|     } | ||||
|  | ||||
|     long nanoCurrent() | ||||
|     { | ||||
|         return System.nanoTime() - nanoCurrent; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Recompute the {@link #isSoftAborted()} and {@link #isPaused()} flags. | ||||
|      */ | ||||
|     public void refresh() | ||||
|     { | ||||
|         long now = System.nanoTime(); | ||||
|         if( !paused ) paused = (now - nanoCurrent) >= TIMESLICE; | ||||
|         if( !softAbort ) softAbort = (now - nanoCumulative) >= TIMEOUT; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Whether we should pause execution of this machine. | ||||
|      * | ||||
|      * This is determined by whether we've consumed our time slice, and if there are other computers waiting to perform | ||||
|      * work. | ||||
|      * | ||||
|      * @return Whether we should pause execution. | ||||
|      */ | ||||
|     public boolean isPaused() | ||||
|     { | ||||
|         return paused && ComputerThread.hasPendingWork(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -54,7 +92,7 @@ public final class TimeoutState | ||||
|      */ | ||||
|     public boolean isSoftAborted() | ||||
|     { | ||||
|         return softAbort || (softAbort = (System.nanoTime() - nanoTime) >= TIMEOUT); | ||||
|         return softAbort; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -74,11 +112,34 @@ public final class TimeoutState | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Reset all abort flags and start the abort timer. | ||||
|      * Start the current and cumulative timers again. | ||||
|      */ | ||||
|     void reset() | ||||
|     void startTimer() | ||||
|     { | ||||
|         softAbort = hardAbort = false; | ||||
|         nanoTime = System.nanoTime(); | ||||
|         long now = System.nanoTime(); | ||||
|         nanoCurrent = now; | ||||
|         // Compute the "nominal start time". | ||||
|         nanoCumulative = now - nanoCumulative; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Pauses the cumulative time, to be resumed by {@link #startTimer()} | ||||
|      * | ||||
|      * @see #nanoCumulative() | ||||
|      */ | ||||
|     void pauseTimer() | ||||
|     { | ||||
|         // We set the cumulative time to difference between current time and "nominal start time". | ||||
|         nanoCumulative = System.nanoTime() - nanoCumulative; | ||||
|         paused = false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Resets the cumulative time and resets the abort flags. | ||||
|      */ | ||||
|     void stopTimer() | ||||
|     { | ||||
|         nanoCumulative = 0; | ||||
|         paused = softAbort = hardAbort = false; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -40,6 +40,8 @@ import java.util.concurrent.TimeUnit; | ||||
| import static org.squiddev.cobalt.Constants.NONE; | ||||
| import static org.squiddev.cobalt.ValueFactory.valueOf; | ||||
| import static org.squiddev.cobalt.ValueFactory.varargsOf; | ||||
| import static org.squiddev.cobalt.debug.DebugFrame.FLAG_HOOKED; | ||||
| import static org.squiddev.cobalt.debug.DebugFrame.FLAG_HOOKYIELD; | ||||
|  | ||||
| public class CobaltLuaMachine implements ILuaMachine | ||||
| { | ||||
| @@ -165,6 +167,7 @@ public class CobaltLuaMachine implements ILuaMachine | ||||
|         } | ||||
|  | ||||
|         // If the soft abort has been cleared then we can reset our flag. | ||||
|         timeout.refresh(); | ||||
|         if( !timeout.isSoftAborted() ) debug.thrownSoftAbort = false; | ||||
|  | ||||
|         try | ||||
| @@ -175,8 +178,13 @@ public class CobaltLuaMachine implements ILuaMachine | ||||
|                 resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) ); | ||||
|             } | ||||
|  | ||||
|             Varargs results = LuaThread.run( m_mainRoutine, resumeArgs ); | ||||
|             // Resume the current thread, or the main one when first starting off. | ||||
|             LuaThread thread = m_state.getCurrentThread(); | ||||
|             if( thread == null || thread == m_state.getMainThread() ) thread = m_mainRoutine; | ||||
|  | ||||
|             Varargs results = LuaThread.run( thread, resumeArgs ); | ||||
|             if( timeout.isHardAborted() ) throw HardAbortError.INSTANCE; | ||||
|             if( results == null ) return MachineResult.PAUSE; | ||||
|  | ||||
|             LuaValue filter = results.first(); | ||||
|             m_eventFilter = filter.isString() ? filter.toString() : null; | ||||
| @@ -620,6 +628,10 @@ public class CobaltLuaMachine implements ILuaMachine | ||||
|         private int count = 0; | ||||
|         boolean thrownSoftAbort; | ||||
|  | ||||
|         private boolean isPaused; | ||||
|         private int oldFlags; | ||||
|         private boolean oldInHook; | ||||
|  | ||||
|         TimeoutDebugHandler() | ||||
|         { | ||||
|             this.timeout = CobaltLuaMachine.this.timeout; | ||||
| @@ -628,8 +640,32 @@ public class CobaltLuaMachine implements ILuaMachine | ||||
|         @Override | ||||
|         public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaError, UnwindThrowable | ||||
|         { | ||||
|             // We check our current abort state every 128 instructions. | ||||
|             if( (count = (count + 1) & 127) == 0 ) handleAbort(); | ||||
|             di.pc = pc; | ||||
|  | ||||
|             if( isPaused ) resetPaused( ds, di ); | ||||
|  | ||||
|             // We check our current pause/abort state every 128 instructions. | ||||
|             if( (count = (count + 1) & 127) == 0 ) | ||||
|             { | ||||
|                 // If we've been hard aborted or closed then abort. | ||||
|                 if( timeout.isHardAborted() || m_state == null ) throw HardAbortError.INSTANCE; | ||||
|  | ||||
|                 timeout.refresh(); | ||||
|                 if( timeout.isPaused() ) | ||||
|                 { | ||||
|                     // Preserve the current state | ||||
|                     isPaused = true; | ||||
|                     oldInHook = ds.inhook; | ||||
|                     oldFlags = di.flags; | ||||
|  | ||||
|                     // Suspend the state. This will probably throw, but we need to handle the case where it won't. | ||||
|                     di.flags |= FLAG_HOOKYIELD | FLAG_HOOKED; | ||||
|                     LuaThread.suspend( ds.getLuaState() ); | ||||
|                     resetPaused( ds, di ); | ||||
|                 } | ||||
|  | ||||
|                 handleSoftAbort(); | ||||
|             } | ||||
|  | ||||
|             super.onInstruction( ds, di, pc ); | ||||
|         } | ||||
| @@ -637,14 +673,25 @@ public class CobaltLuaMachine implements ILuaMachine | ||||
|         @Override | ||||
|         public void poll() throws LuaError | ||||
|         { | ||||
|             handleAbort(); | ||||
|             // If we've been hard aborted or closed then abort. | ||||
|             LuaState state = m_state; | ||||
|             if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE; | ||||
|  | ||||
|             timeout.refresh(); | ||||
|             if( timeout.isPaused() ) LuaThread.suspendBlocking( state ); | ||||
|             handleSoftAbort(); | ||||
|         } | ||||
|  | ||||
|         private void handleAbort() throws LuaError | ||||
|         private void resetPaused( DebugState ds, DebugFrame di ) | ||||
|         { | ||||
|             // If we've been hard aborted or closed then abort. | ||||
|             if( timeout.isHardAborted() || m_state == null ) throw HardAbortError.INSTANCE; | ||||
|             // Restore the previous paused state | ||||
|             isPaused = false; | ||||
|             ds.inhook = oldInHook; | ||||
|             di.flags = oldFlags; | ||||
|         } | ||||
|  | ||||
|         private void handleSoftAbort() throws LuaError | ||||
|         { | ||||
|             // If we already thrown our soft abort error then don't do it again. | ||||
|             if( !timeout.isSoftAborted() || thrownSoftAbort ) return; | ||||
|  | ||||
|   | ||||
| @@ -23,37 +23,44 @@ import java.io.InputStream; | ||||
| public final class MachineResult | ||||
| { | ||||
|     /** | ||||
|      * Represents a successful execution | ||||
|      * A successful complete execution. | ||||
|      */ | ||||
|     public static final MachineResult OK = new MachineResult( false, null ); | ||||
|     public static final MachineResult OK = new MachineResult( false, false, null ); | ||||
|  | ||||
|     /** | ||||
|      * A successful paused execution. | ||||
|      */ | ||||
|     public static final MachineResult PAUSE = new MachineResult( false, true, null ); | ||||
|  | ||||
|     /** | ||||
|      * An execution which timed out. | ||||
|      */ | ||||
|     public static final MachineResult TIMEOUT = new MachineResult( true, TimeoutState.ABORT_MESSAGE ); | ||||
|     public static final MachineResult TIMEOUT = new MachineResult( true, false, TimeoutState.ABORT_MESSAGE ); | ||||
|  | ||||
|     /** | ||||
|      * An error with no user-friendly error message | ||||
|      */ | ||||
|     public static final MachineResult GENERIC_ERROR = new MachineResult( true, null ); | ||||
|     public static final MachineResult GENERIC_ERROR = new MachineResult( true, false, null ); | ||||
|  | ||||
|     private final boolean error; | ||||
|     private final boolean pause; | ||||
|     private final String message; | ||||
|  | ||||
|     private MachineResult( boolean error, String message ) | ||||
|     private MachineResult( boolean error, boolean pause, String message ) | ||||
|     { | ||||
|         this.pause = pause; | ||||
|         this.message = message; | ||||
|         this.error = error; | ||||
|     } | ||||
|  | ||||
|     public static MachineResult error( @Nonnull String error ) | ||||
|     { | ||||
|         return new MachineResult( true, error ); | ||||
|         return new MachineResult( true, false, error ); | ||||
|     } | ||||
|  | ||||
|     public static MachineResult error( @Nonnull Exception error ) | ||||
|     { | ||||
|         return new MachineResult( true, error.getMessage() ); | ||||
|         return new MachineResult( true, false, error.getMessage() ); | ||||
|     } | ||||
|  | ||||
|     public boolean isError() | ||||
| @@ -61,6 +68,11 @@ public final class MachineResult | ||||
|         return error; | ||||
|     } | ||||
|  | ||||
|     public boolean isPause() | ||||
|     { | ||||
|         return pause; | ||||
|     } | ||||
|  | ||||
|     @Nullable | ||||
|     public String getMessage() | ||||
|     { | ||||
|   | ||||
| @@ -84,10 +84,10 @@ public class ComputerBootstrap | ||||
|  | ||||
|             if( computer.isOn() || !api.didAssert ) | ||||
|             { | ||||
|                 StringBuilder builder = new StringBuilder().append( "Did not correctly" ); | ||||
|                 StringBuilder builder = new StringBuilder().append( "Did not correctly [" ); | ||||
|                 if( !api.didAssert ) builder.append( " assert" ); | ||||
|                 if( computer.isOn() ) builder.append( " shutdown" ); | ||||
|                 builder.append( "\n" ); | ||||
|                 builder.append( " ]\n" ); | ||||
|  | ||||
|                 for( int line = 0; line < 19; line++ ) | ||||
|                 { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 SquidDev
					SquidDev