mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-03-04 10:38:12 +00:00
Document everything, and several further improvements
- Only update all runtimes and the minimum runtime when queuing new exectors. We only need to update the current executor's runtime. - Fix overflows when comparing times within TimeoutState. System.nanotime() may (though probably won't) return negative values. - Hopefully explain how the scheduler works a little bit.
This commit is contained in:
parent
f7cb526793
commit
0bfb7049b0
@ -10,7 +10,6 @@ import dan200.computercraft.ComputerCraft;
|
|||||||
import dan200.computercraft.shared.util.ThreadUtils;
|
import dan200.computercraft.shared.util.ThreadUtils;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -26,7 +25,26 @@ import static dan200.computercraft.core.computer.TimeoutState.TIMEOUT;
|
|||||||
* Responsible for running all tasks from a {@link Computer}.
|
* Responsible for running all tasks from a {@link Computer}.
|
||||||
*
|
*
|
||||||
* 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 have not been terminated by
|
||||||
|
* {@link TimeoutState#isSoftAborted()}.
|
||||||
|
*
|
||||||
|
* Computers are executed using a priority system, with those who have spent less time executing having a higher
|
||||||
|
* priority than those hogging the thread. This, combined with {@link TimeoutState#isPaused()} means we can reduce the
|
||||||
|
* risk of badly behaved computers stalling execution for everyone else.
|
||||||
|
*
|
||||||
|
* This is done using an implementation of Linux's Completely Fair Scheduler. When a computer executes, we compute what
|
||||||
|
* share of execution time it has used (time executed/number of tasks). We then pick the computer who has the least
|
||||||
|
* "virtual execution time" (aka {@link ComputerExecutor#virtualRuntime}).
|
||||||
|
*
|
||||||
|
* When adding a computer to the queue, we make sure its "virtual runtime" is at least as big as the smallest runtime.
|
||||||
|
* This means that adding computers which have slept a lot do not then have massive priority over everyone else. See
|
||||||
|
* {@link #queue(ComputerExecutor)} for how this is implemented.
|
||||||
|
*
|
||||||
|
* In reality, it's unlikely that more than a few computers are waiting to execute at once, so this will not have much
|
||||||
|
* effect unless you have a computer hogging execution time. However, it is pretty effective in those situations.
|
||||||
|
*
|
||||||
|
* @see TimeoutState For how hard timeouts are handled.
|
||||||
|
* @see ComputerExecutor For how computers actually do execution.
|
||||||
*/
|
*/
|
||||||
public class ComputerThread
|
public class ComputerThread
|
||||||
{
|
{
|
||||||
@ -116,7 +134,7 @@ public class ComputerThread
|
|||||||
|
|
||||||
if( runners == null )
|
if( runners == null )
|
||||||
{
|
{
|
||||||
// TODO: Change the runners lenght on config reloads
|
// TODO: Change the runners length on config reloads
|
||||||
runners = new TaskRunner[ComputerCraft.computer_threads];
|
runners = new TaskRunner[ComputerCraft.computer_threads];
|
||||||
|
|
||||||
// latency and minPeriod are scaled by 1 + floor(log2(threads)). We can afford to execute tasks for
|
// latency and minPeriod are scaled by 1 + floor(log2(threads)). We can afford to execute tasks for
|
||||||
@ -172,7 +190,10 @@ public class ComputerThread
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark a computer as having work, enqueuing it on the thread.
|
* Mark a computer as having work, enqueuing it on the thread
|
||||||
|
*
|
||||||
|
* You must be holding {@link ComputerExecutor}'s {@code queueLock} when calling this method - it should only
|
||||||
|
* be called from {@code enqueue}.
|
||||||
*
|
*
|
||||||
* @param executor The computer to execute work on.
|
* @param executor The computer to execute work on.
|
||||||
*/
|
*/
|
||||||
@ -184,7 +205,7 @@ public class ComputerThread
|
|||||||
if( executor.onComputerQueue ) throw new IllegalStateException( "Cannot queue already queued executor" );
|
if( executor.onComputerQueue ) throw new IllegalStateException( "Cannot queue already queued executor" );
|
||||||
executor.onComputerQueue = true;
|
executor.onComputerQueue = true;
|
||||||
|
|
||||||
updateRuntimes( null );
|
updateRuntimes();
|
||||||
|
|
||||||
// We're not currently on the queue, so update its current execution time to
|
// We're not currently on the queue, so update its current execution time to
|
||||||
// ensure its at least as high as the minimum.
|
// ensure its at least as high as the minimum.
|
||||||
@ -215,23 +236,25 @@ public class ComputerThread
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the {@link ComputerExecutor#virtualRuntime}s of all running tasks, and then increment the
|
* Update the {@link ComputerExecutor#virtualRuntime}s of all running tasks, and then update the
|
||||||
* {@link #minimumVirtualRuntime} of the executor.
|
* {@link #minimumVirtualRuntime} based on the current tasks.
|
||||||
|
*
|
||||||
|
* This is called before queueing tasks, to ensure that {@link #minimumVirtualRuntime} is up-to-date.
|
||||||
*/
|
*/
|
||||||
private static void updateRuntimes( @Nullable ComputerExecutor current )
|
private static void updateRuntimes()
|
||||||
{
|
{
|
||||||
long minRuntime = Long.MAX_VALUE;
|
long minRuntime = Long.MAX_VALUE;
|
||||||
|
|
||||||
// If we've a task on the queue, use that as our base time.
|
// If we've a task on the queue, use that as our base time.
|
||||||
if( !computerQueue.isEmpty() ) minRuntime = computerQueue.first().virtualRuntime;
|
if( !computerQueue.isEmpty() ) minRuntime = computerQueue.first().virtualRuntime;
|
||||||
|
|
||||||
long now = System.nanoTime();
|
|
||||||
int tasks = 1 + computerQueue.size();
|
|
||||||
|
|
||||||
// Update all the currently executing tasks
|
// Update all the currently executing tasks
|
||||||
TaskRunner[] currentRunners = runners;
|
TaskRunner[] currentRunners = runners;
|
||||||
if( currentRunners != null )
|
if( currentRunners != null )
|
||||||
{
|
{
|
||||||
|
long now = System.nanoTime();
|
||||||
|
int tasks = 1 + computerQueue.size();
|
||||||
|
|
||||||
for( TaskRunner runner : currentRunners )
|
for( TaskRunner runner : currentRunners )
|
||||||
{
|
{
|
||||||
if( runner == null ) continue;
|
if( runner == null ) continue;
|
||||||
@ -245,12 +268,6 @@ public class ComputerThread
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// And update the most recently executed one if set.
|
|
||||||
if( current != null )
|
|
||||||
{
|
|
||||||
minRuntime = Math.min( minRuntime, current.virtualRuntime += (now - current.vRuntimeStart) / tasks );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( minRuntime > minimumVirtualRuntime && minRuntime < Long.MAX_VALUE )
|
if( minRuntime > minimumVirtualRuntime && minRuntime < Long.MAX_VALUE )
|
||||||
{
|
{
|
||||||
minimumVirtualRuntime = minRuntime;
|
minimumVirtualRuntime = minRuntime;
|
||||||
@ -258,7 +275,8 @@ public class ComputerThread
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-add this task to the queue
|
* Ensure the "currently working" state of the executor is reset, the timings are updated, and then requeue the
|
||||||
|
* executor if needed.
|
||||||
*
|
*
|
||||||
* @param runner The runner this task was on.
|
* @param runner The runner this task was on.
|
||||||
* @param executor The executor to requeue
|
* @param executor The executor to requeue
|
||||||
@ -278,11 +296,14 @@ public class ComputerThread
|
|||||||
computerLock.lock();
|
computerLock.lock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
updateRuntimes( executor );
|
// Update the virtual runtime of this task.
|
||||||
|
long now = System.nanoTime();
|
||||||
|
executor.virtualRuntime += (now - executor.vRuntimeStart) / (1 + computerQueue.size());
|
||||||
|
|
||||||
// Add to the queue, and signal the workers.
|
// If we've no more tasks, just return.
|
||||||
if( !executor.afterWork() ) return;
|
if( !executor.afterWork() ) return;
|
||||||
|
|
||||||
|
// Otherwise, add to the queue, and signal any waiting workers.
|
||||||
computerQueue.add( executor );
|
computerQueue.add( executor );
|
||||||
hasWork.signal();
|
hasWork.signal();
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
|
|
||||||
package dan200.computercraft.core.computer;
|
package dan200.computercraft.core.computer;
|
||||||
|
|
||||||
|
import dan200.computercraft.core.lua.ILuaMachine;
|
||||||
|
import dan200.computercraft.core.lua.MachineResult;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,8 +25,13 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* abort ({@link #ABORT_TIMEOUT}), we trigger a hard abort (note, this is done from the computer thread manager). This
|
* abort ({@link #ABORT_TIMEOUT}), we trigger a hard abort (note, this is done from the computer thread manager). This
|
||||||
* will destroy the entire Lua runtime and shut the computer down.
|
* will destroy the entire Lua runtime and shut the computer down.
|
||||||
*
|
*
|
||||||
|
* The Lua runtime is also allowed to pause execution if there are other computers contesting for work. All computers
|
||||||
|
* are allowed to run for {@link ComputerThread#scaledPeriod()} nanoseconds (see {@link #currentDeadline}). After that
|
||||||
|
* period, if any computers are waiting to be executed then we'll set the paused flag to true ({@link #isPaused()}.
|
||||||
|
*
|
||||||
* @see ComputerThread
|
* @see ComputerThread
|
||||||
* @see dan200.computercraft.core.lua.ILuaMachine
|
* @see ILuaMachine
|
||||||
|
* @see MachineResult#isPause()
|
||||||
*/
|
*/
|
||||||
public final class TimeoutState
|
public final class TimeoutState
|
||||||
{
|
{
|
||||||
@ -81,9 +89,11 @@ public final class TimeoutState
|
|||||||
*/
|
*/
|
||||||
public void refresh()
|
public void refresh()
|
||||||
{
|
{
|
||||||
|
// Important: The weird arithmetic here is important, as nanoTime may return negative values, and so we
|
||||||
|
// need to handle overflow.
|
||||||
long now = System.nanoTime();
|
long now = System.nanoTime();
|
||||||
if( !paused ) paused = now >= currentDeadline && ComputerThread.hasPendingWork();
|
if( !paused ) paused = currentDeadline - now <= 0 && ComputerThread.hasPendingWork(); // now >= currentDeadline
|
||||||
if( !softAbort ) softAbort = (now - cumulativeStart) >= TIMEOUT;
|
if( !softAbort ) softAbort = (now - cumulativeStart - TIMEOUT) >= 0; // now - cumulativeStart >= TIMEOUT
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user