mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 13:42:59 +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:
		| @@ -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 | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 SquidDev
					SquidDev