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:
parent
218f8e53bb
commit
b3e6a53868
@ -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++ )
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user