1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-09-28 15:08:47 +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:
SquidDev 2019-02-28 16:36:26 +00:00
parent 218f8e53bb
commit b3e6a53868
6 changed files with 194 additions and 30 deletions

View File

@ -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() );

View File

@ -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;

View File

@ -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;
} }
} }

View File

@ -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;

View File

@ -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()
{ {

View File

@ -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++ )
{ {