mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 05:33: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(); |     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; |     volatile boolean onComputerQueue = false; | ||||||
|  |  | ||||||
| @@ -117,6 +121,14 @@ final class ComputerExecutor | |||||||
|      */ |      */ | ||||||
|     private final Queue<Event> eventQueue = new ArrayDeque<>(); |     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. |      * 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 |             // Reset the terminal and event queue | ||||||
|             computer.getTerminal().reset(); |             computer.getTerminal().reset(); | ||||||
|  |             interruptedEvent = false; | ||||||
|             synchronized( queueLock ) |             synchronized( queueLock ) | ||||||
|             { |             { | ||||||
|                 eventQueue.clear(); |                 eventQueue.clear(); | ||||||
| @@ -432,6 +445,7 @@ final class ComputerExecutor | |||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             isOn = false; |             isOn = false; | ||||||
|  |             interruptedEvent = false; | ||||||
|             synchronized( queueLock ) |             synchronized( queueLock ) | ||||||
|             { |             { | ||||||
|                 eventQueue.clear(); |                 eventQueue.clear(); | ||||||
| @@ -468,7 +482,7 @@ final class ComputerExecutor | |||||||
|      */ |      */ | ||||||
|     void beforeWork() |     void beforeWork() | ||||||
|     { |     { | ||||||
|         timeout.reset(); |         timeout.startTimer(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -477,11 +491,20 @@ final class ComputerExecutor | |||||||
|      */ |      */ | ||||||
|     void afterWork() |     void afterWork() | ||||||
|     { |     { | ||||||
|         Tracking.addTaskTiming( getComputer(), timeout.nanoSinceStart() ); |         if( interruptedEvent ) | ||||||
|  |         { | ||||||
|  |             timeout.pauseTimer(); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             timeout.stopTimer(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Tracking.addTaskTiming( getComputer(), timeout.nanoCurrent() ); | ||||||
|  |  | ||||||
|         synchronized( queueLock ) |         synchronized( queueLock ) | ||||||
|         { |         { | ||||||
|             if( eventQueue.isEmpty() && command == null ) |             if( !interruptedEvent && eventQueue.isEmpty() && command == null ) | ||||||
|             { |             { | ||||||
|                 onComputerQueue = false; |                 onComputerQueue = false; | ||||||
|             } |             } | ||||||
| @@ -510,6 +533,16 @@ final class ComputerExecutor | |||||||
|  |  | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|  |             if( interruptedEvent ) | ||||||
|  |             { | ||||||
|  |                 interruptedEvent = false; | ||||||
|  |                 if( machine != null ) | ||||||
|  |                 { | ||||||
|  |                     resumeMachine( null, null ); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|             StateCommand command; |             StateCommand command; | ||||||
|             Event event = null; |             Event event = null; | ||||||
|             synchronized( queueLock ) |             synchronized( queueLock ) | ||||||
| @@ -601,6 +634,7 @@ final class ComputerExecutor | |||||||
|     private void resumeMachine( String event, Object[] args ) throws InterruptedException |     private void resumeMachine( String event, Object[] args ) throws InterruptedException | ||||||
|     { |     { | ||||||
|         MachineResult result = machine.handleEvent( event, args ); |         MachineResult result = machine.handleEvent( event, args ); | ||||||
|  |         interruptedEvent = result.isPause(); | ||||||
|         if( !result.isError() ) return; |         if( !result.isError() ) return; | ||||||
|  |  | ||||||
|         displayFailure( "Error running computer", result.getMessage() ); |         displayFailure( "Error running computer", result.getMessage() ); | ||||||
|   | |||||||
| @@ -126,6 +126,16 @@ public class ComputerThread | |||||||
|         computersActive.add( computer ); |         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 |      * Observes all currently active {@link TaskRunner}s and terminates their tasks once they have exceeded the hard | ||||||
|      * abort limit. |      * abort limit. | ||||||
| @@ -158,7 +168,7 @@ public class ComputerThread | |||||||
|  |  | ||||||
|                             // If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT), |                             // If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT), | ||||||
|                             // then we can let the Lua machine do its work. |                             // 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; |                             long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT; | ||||||
|                             if( afterHardAbort < 0 ) continue; |                             if( afterHardAbort < 0 ) continue; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -27,6 +27,11 @@ import java.util.concurrent.TimeUnit; | |||||||
|  */ |  */ | ||||||
| public final class TimeoutState | 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 |      * 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 ); |     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"; |     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 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() |     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; |         long now = System.nanoTime(); | ||||||
|         nanoTime = 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.Constants.NONE; | ||||||
| import static org.squiddev.cobalt.ValueFactory.valueOf; | import static org.squiddev.cobalt.ValueFactory.valueOf; | ||||||
| import static org.squiddev.cobalt.ValueFactory.varargsOf; | 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 | 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. |         // If the soft abort has been cleared then we can reset our flag. | ||||||
|  |         timeout.refresh(); | ||||||
|         if( !timeout.isSoftAborted() ) debug.thrownSoftAbort = false; |         if( !timeout.isSoftAborted() ) debug.thrownSoftAbort = false; | ||||||
|  |  | ||||||
|         try |         try | ||||||
| @@ -175,8 +178,13 @@ public class CobaltLuaMachine implements ILuaMachine | |||||||
|                 resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) ); |                 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( timeout.isHardAborted() ) throw HardAbortError.INSTANCE; | ||||||
|  |             if( results == null ) return MachineResult.PAUSE; | ||||||
|  |  | ||||||
|             LuaValue filter = results.first(); |             LuaValue filter = results.first(); | ||||||
|             m_eventFilter = filter.isString() ? filter.toString() : null; |             m_eventFilter = filter.isString() ? filter.toString() : null; | ||||||
| @@ -620,6 +628,10 @@ public class CobaltLuaMachine implements ILuaMachine | |||||||
|         private int count = 0; |         private int count = 0; | ||||||
|         boolean thrownSoftAbort; |         boolean thrownSoftAbort; | ||||||
|  |  | ||||||
|  |         private boolean isPaused; | ||||||
|  |         private int oldFlags; | ||||||
|  |         private boolean oldInHook; | ||||||
|  |  | ||||||
|         TimeoutDebugHandler() |         TimeoutDebugHandler() | ||||||
|         { |         { | ||||||
|             this.timeout = CobaltLuaMachine.this.timeout; |             this.timeout = CobaltLuaMachine.this.timeout; | ||||||
| @@ -628,8 +640,32 @@ public class CobaltLuaMachine implements ILuaMachine | |||||||
|         @Override |         @Override | ||||||
|         public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaError, UnwindThrowable |         public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaError, UnwindThrowable | ||||||
|         { |         { | ||||||
|             // We check our current abort state every 128 instructions. |             di.pc = pc; | ||||||
|             if( (count = (count + 1) & 127) == 0 ) handleAbort(); |  | ||||||
|  |             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 ); |             super.onInstruction( ds, di, pc ); | ||||||
|         } |         } | ||||||
| @@ -637,14 +673,25 @@ public class CobaltLuaMachine implements ILuaMachine | |||||||
|         @Override |         @Override | ||||||
|         public void poll() throws LuaError |         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. |             // Restore the previous paused state | ||||||
|             if( timeout.isHardAborted() || m_state == null ) throw HardAbortError.INSTANCE; |             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 we already thrown our soft abort error then don't do it again. | ||||||
|             if( !timeout.isSoftAborted() || thrownSoftAbort ) return; |             if( !timeout.isSoftAborted() || thrownSoftAbort ) return; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,37 +23,44 @@ import java.io.InputStream; | |||||||
| public final class MachineResult | 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. |      * 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 |      * 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 error; | ||||||
|  |     private final boolean pause; | ||||||
|     private final String message; |     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.message = message; | ||||||
|         this.error = error; |         this.error = error; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static MachineResult error( @Nonnull String 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 ) |     public static MachineResult error( @Nonnull Exception error ) | ||||||
|     { |     { | ||||||
|         return new MachineResult( true, error.getMessage() ); |         return new MachineResult( true, false, error.getMessage() ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public boolean isError() |     public boolean isError() | ||||||
| @@ -61,6 +68,11 @@ public final class MachineResult | |||||||
|         return error; |         return error; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public boolean isPause() | ||||||
|  |     { | ||||||
|  |         return pause; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Nullable |     @Nullable | ||||||
|     public String getMessage() |     public String getMessage() | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -84,10 +84,10 @@ public class ComputerBootstrap | |||||||
|  |  | ||||||
|             if( computer.isOn() || !api.didAssert ) |             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( !api.didAssert ) builder.append( " assert" ); | ||||||
|                 if( computer.isOn() ) builder.append( " shutdown" ); |                 if( computer.isOn() ) builder.append( " shutdown" ); | ||||||
|                 builder.append( "\n" ); |                 builder.append( " ]\n" ); | ||||||
|  |  | ||||||
|                 for( int line = 0; line < 19; line++ ) |                 for( int line = 0; line < 19; line++ ) | ||||||
|                 { |                 { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 SquidDev
					SquidDev