1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-06-08 01:24:11 +00:00

Several improvements to the computer thread rework

- TimeoutState uses nanoseconds rather than milliseconds. While this is
   slightly less efficient on Windows, it's a) not the bottleneck of Lua
   execution and b) we need a monotonic counter, otherwise we could
   fail to terminate computers if the time changes.
 - Add an exception handler to all threads.
 - Document several classes a little better - I'm not sure how useful
   all of these are, but _hopefully_ it'll make the internals a little
   more accessible.
This commit is contained in:
SquidDev 2019-02-27 20:56:45 +00:00
parent 3e28f79ce9
commit c78adb2cdc
8 changed files with 85 additions and 29 deletions

View File

@ -225,7 +225,6 @@ final class ComputerExecutor
*/ */
void abort() void abort()
{ {
// TODO: We need to test this much more thoroughly.
ILuaMachine machine = this.machine; ILuaMachine machine = this.machine;
if( machine != null ) machine.close(); if( machine != null ) machine.close();

View File

@ -18,7 +18,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
* Implementation of {@link IComputerAccess}/{@link IComputerSystem} for external APIs. * Implementation of {@link IComputerAccess}/{@link IComputerSystem} for usage by externally registered APIs.
* *
* @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory) * @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
* @see ILuaAPIFactory * @see ILuaAPIFactory

View File

@ -24,8 +24,6 @@ import static dan200.computercraft.core.computer.TimeoutState.TIMEOUT;
* *
* This is split into two components: the {@link TaskRunner}s, which pull an executor from the queue and execute it, and * This is split into two components: the {@link TaskRunner}s, which pull an executor from the queue and execute it, and
* a single {@link Monitor} which observes all runners and kills them if they are behaving badly. * a single {@link Monitor} which observes all runners and kills them if they are behaving badly.
*
* TODO: Flesh out the documentation here.
*/ */
public class ComputerThread public class ComputerThread
{ {
@ -95,7 +93,7 @@ public class ComputerThread
} }
/** /**
* Attempt to stop the computer thread * Attempt to stop the computer thread. This interrupts each runner, and clears the task queue.
*/ */
public static void stop() public static void stop()
{ {
@ -160,7 +158,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.milliSinceStart(); long afterStart = executor.timeout.nanoSinceStart();
long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT; long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT;
if( afterHardAbort < 0 ) continue; if( afterHardAbort < 0 ) continue;
@ -168,7 +166,7 @@ public class ComputerThread
executor.timeout.hardAbort(); executor.timeout.hardAbort();
executor.abort(); executor.abort();
if( afterHardAbort >= ABORT_TIMEOUT + ABORT_TIMEOUT ) if( afterHardAbort >= ABORT_TIMEOUT * 2 )
{ {
// If we've hard aborted and interrupted, and we're still not dead, then mark the runner // If we've hard aborted and interrupted, and we're still not dead, then mark the runner
// as dead, finish off the task, and spawn a new runner. // as dead, finish off the task, and spawn a new runner.
@ -206,6 +204,10 @@ public class ComputerThread
/** /**
* Pulls tasks from the {@link #computersActive} queue and runs them. * Pulls tasks from the {@link #computersActive} queue and runs them.
*
* This is responsible for running the {@link ComputerExecutor#work()}, {@link ComputerExecutor#beforeWork()} and
* {@link ComputerExecutor#afterWork()} functions. Everything else is either handled by the executor, timeout
* state or monitor.
*/ */
private static final class TaskRunner implements Runnable private static final class TaskRunner implements Runnable
{ {

View File

@ -6,10 +6,28 @@
package dan200.computercraft.core.computer; package dan200.computercraft.core.computer;
import dan200.computercraft.api.peripheral.IComputerAccess;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/**
* {@link IComputerOwned} marks objects which are known to belong to a computer.
*
* The primary purpose of this is to allow Plethora (and potentially other mods) to run the various tracking methods
* on {@link Computer}.
*
* You can generally assume {@link IComputerAccess} implements this interface, though you should always check first.
*
* @see dan200.computercraft.core.apis.ComputerAccess
* @see dan200.computercraft.shared.peripheral.modem.wired.WiredModemPeripheral and the peripheral wrapper
*/
public interface IComputerOwned public interface IComputerOwned
{ {
/**
* Get the computer associated with this object
*
* @return The associated object, or {@code null} if none is known.
*/
@Nullable @Nullable
Computer getComputer(); Computer getComputer();
} }

View File

@ -6,6 +6,8 @@
package dan200.computercraft.core.computer; package dan200.computercraft.core.computer;
import java.util.concurrent.TimeUnit;
/** /**
* Used to measure how long a computer has executed for, and thus the relevant timeout states. * Used to measure how long a computer has executed for, and thus the relevant timeout states.
* *
@ -26,28 +28,22 @@ package dan200.computercraft.core.computer;
public final class TimeoutState public final class TimeoutState
{ {
/** /**
* The total time a task is allowed to run before aborting in milliseconds * The total time a task is allowed to run before aborting in nanoseconds
*/ */
static final long TIMEOUT = 7000; static final long TIMEOUT = TimeUnit.MILLISECONDS.toNanos( 7000 );
/** /**
* The time the task is allowed to run after each abort in milliseconds * The time the task is allowed to run after each abort in nanoseconds
*/ */
static final long ABORT_TIMEOUT = 1500; static final long ABORT_TIMEOUT = TimeUnit.MILLISECONDS.toNanos( 1500 );
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 volatile boolean softAbort;
private volatile boolean hardAbort; private volatile boolean hardAbort;
private long milliTime;
private long nanoTime; private long nanoTime;
long milliSinceStart()
{
return System.currentTimeMillis() - milliTime;
}
long nanoSinceStart() long nanoSinceStart()
{ {
return System.nanoTime() - nanoTime; return System.nanoTime() - nanoTime;
@ -58,7 +54,7 @@ public final class TimeoutState
*/ */
public boolean isSoftAborted() public boolean isSoftAborted()
{ {
return softAbort || (softAbort = (System.currentTimeMillis() - milliTime) >= TIMEOUT); return softAbort || (softAbort = (System.nanoTime() - nanoTime) >= TIMEOUT);
} }
/** /**
@ -83,7 +79,6 @@ public final class TimeoutState
void reset() void reset()
{ {
softAbort = hardAbort = false; softAbort = hardAbort = false;
milliTime = System.currentTimeMillis();
nanoTime = System.nanoTime(); nanoTime = System.nanoTime();
} }
} }

View File

@ -99,11 +99,8 @@ public class CobaltLuaMachine implements ILuaMachine
return; return;
} }
if( hasSoftAbort && !timeout.isHardAborted() ) // If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
{ if( hasSoftAbort ) return;
// If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
return;
}
hasSoftAbort = true; hasSoftAbort = true;
throw new LuaError( TimeoutState.ABORT_MESSAGE ); throw new LuaError( TimeoutState.ABORT_MESSAGE );
@ -157,7 +154,7 @@ public class CobaltLuaMachine implements ILuaMachine
} }
@Override @Override
public void addAPI( ILuaAPI api ) public void addAPI( @Nonnull ILuaAPI api )
{ {
// Add the methods of an API to the global table // Add the methods of an API to the global table
LuaTable table = wrapLuaObject( api ); LuaTable table = wrapLuaObject( api );
@ -169,7 +166,7 @@ public class CobaltLuaMachine implements ILuaMachine
} }
@Override @Override
public void loadBios( InputStream bios ) public void loadBios( @Nonnull InputStream bios )
{ {
// Begin executing a file (ie, the bios) // Begin executing a file (ie, the bios)
if( m_mainRoutine != null ) return; if( m_mainRoutine != null ) return;

View File

@ -7,18 +7,61 @@
package dan200.computercraft.core.lua; package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaObject;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InputStream; import java.io.InputStream;
/**
* Represents a machine which will execute Lua code. Technically this API is flexible enough to support many languages,
* but you'd need a way to provide alternative ROMs, BIOSes, etc...
*
* There should only be one concrete implementation at any one time, which is currently {@link CobaltLuaMachine}. If
* external mod authors are interested in registering their own machines, we can look into how we can provide some
* mechanism for registering these.
*
* This should provide implementations of {@link dan200.computercraft.api.lua.ILuaContext}, and the ability to convert
* {@link ILuaObject}s into something the VM understands, as well as handling method calls.
*/
public interface ILuaMachine public interface ILuaMachine
{ {
void addAPI( ILuaAPI api ); /**
* Inject an API into the global environment of this machine. This should construct an object, as it would for any
* {@link ILuaObject} and set it to all names in {@link ILuaAPI#getNames()}.
*
* Called before {@link #loadBios(InputStream)}.
*
* @param api The API to register.
*/
void addAPI( @Nonnull ILuaAPI api );
void loadBios( InputStream bios ); /**
* Create a function from the provided program, and set it up to run when {@link #handleEvent(String, Object[])} is
* called
*
* @param bios The stream containing the boot program.
*/
void loadBios( @Nonnull InputStream bios );
void handleEvent( String eventName, Object[] arguments ); /**
* Resume the machine, either starting or resuming the coroutine.
*
* @param eventName The name of the event. This is {@code null} when first starting the machine. Note, this may
* do nothing if it does not match the event filter.
* @param arguments The arguments for this event.
*/
void handleEvent( @Nullable String eventName, @Nullable Object[] arguments );
/**
* If this machine has finished executing, either due to an error or it just shutting down.
*
* @return If this machine is finished.
*/
boolean isFinished(); boolean isFinished();
/**
* Close the Lua machine, aborting any running functions and deleting the internal state.
*/
void close(); void close();
} }

View File

@ -7,6 +7,7 @@
package dan200.computercraft.shared.util; package dan200.computercraft.shared.util;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import dan200.computercraft.ComputerCraft;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
@ -58,6 +59,7 @@ public final class ThreadUtils
return new ThreadFactoryBuilder() return new ThreadFactoryBuilder()
.setDaemon( true ) .setDaemon( true )
.setNameFormat( group.getName().replace( "%", "%%" ) + "-%d" ) .setNameFormat( group.getName().replace( "%", "%%" ) + "-%d" )
.setUncaughtExceptionHandler( ( t, e ) -> ComputerCraft.log.error( "Exception in thread " + t.getName(), e ) )
.setThreadFactory( x -> new Thread( group, x ) ); .setThreadFactory( x -> new Thread( group, x ) );
} }