Skip to content

Commit

Permalink
W.I.P. implement virtual threading
Browse files Browse the repository at this point in the history
  • Loading branch information
DoggySazHi committed Jul 21, 2024
1 parent 663483c commit 566eebf
Show file tree
Hide file tree
Showing 15 changed files with 1,302 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package net.gensokyoreimagined.nitori.executor.annotation.thread;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

/**
* An annotation primarily for methods, identifying methods that can only be called on a thread that is an instance
* of {@link AssistThread}.
* <br>
* This annotation can also be used on fields or classes, similar to {@link ThreadRestricted}.
* <br>
* In a method annotated with {@link AssistThreadOnly}, fields and methods annotated with
* {@link AssistThreadOnly}, {@link BaseThreadOnly} or {@link AnyThreadSafe} may be used.
* <br>
* Methods that are annotated with {@link AssistThreadOnly} must never call methods that are annotated with
* {@link PotentiallyBlocking}.
*
* @author Martijn Muijsers under AGPL-3.0
*/
@SuppressWarnings("unused")
@Documented
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
public @interface AssistThreadOnly {

/**
* @see ThreadRestricted#fieldAccess()
*/
Access value() default Access.READ_WRITE;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package net.gensokyoreimagined.nitori.executor.queue;

/**
* A tier for {@link AbstractTaskQueue}s, that indicates the priority of the tasks in the task queues.
* Every tier contains a list of the queues that are part of the tier.
* The tiers are in order of priority, from high to low.
* Similarly, the queues for each tier are in the same order of priority.
* The tasks in each queue should also be in order of priority whenever relevant, but usually there
* is no strong difference in priority between tasks in the same queue, so they typically operate as FIFO queues,
* so that the longest waiting task implicitly has the highest priority within the queue.
* <br>
* Tasks from queues in the {@link #SERVER} tier can only be run on a {@link ServerThread}.
* Tasks from other tiers can be run on {@link ServerThread}s as well as on {@link AssistThread}s.
*
* @author Martijn Muijsers under AGPL-3.0
*/
public enum BaseTaskQueueTier {

/**
* A tier for queues that contain tasks that must be executed on a {@link ServerThread}.
* <br>
* Some parts of the server can only be safely accessed by one thread at a time.
* If they can not be guarded by a lock (or if this is not desired,
* because if a ticking thread would need to acquire this lock it would block it),
* then these parts of the code are typically deferred to the server thread.
* Based on the current use of the {@link TickThread} class, particularly given the existence of
* {@link TickThread#isTickThreadFor(Entity)} and {@link TickThread#isTickThreadFor(ServerLevel, int, int)},
* we can deduce that future support for performing some of these actions in parallel is planned.
* In such a case, some server thread tasks may become tasks that must be
* executed on an appropriate {@link TickThread}.
* In that case, the queues below should be changed so that the server thread and any of the
* ticking threads poll from queues that contain tasks appropriate for them.
* For example, {@link BaseTaskQueues#deferredToUniversalTickThread} would be for tasks that can run
* on any ticking thread, and additional queues would need to be added concerning a specific
* subject (like an entity or chunk) with tasks that will be run on whichever ticking thread is the
* ticking thread for that subject at the time of polling.
* <br>
* Note that a {@link ServerThread} can only yield to {@link TaskSpan#TINY} tasks in other tiers
* (since there are no higher tiers, and threads can only yield to lower tiers when
* the task yielded to is{@link TaskSpan#TINY}, or other non-yielding tasks in its own tier (since it
* has a {@link BaseThread#maximumYieldDepth} of 1).
* Yielding to other tasks in this same tier is somewhat risky, since this means that the tasks that were
* yielded to must assume that although they are running on the server thread, they may be running at
* some unknown point in execution of the main thread. Therefore, scheduling any non-yielding tasks to
* a queue in this tier must be done with the utmost care that the task cannot disrupt, or be disrupted by,
* the surrounding code that yields to it.
*/
SERVER(new AbstractTaskQueue[]{
BaseTaskQueues.deferredToServerThread,
BaseTaskQueues.serverThreadTick,
BaseTaskQueues.anyTickScheduledServerThread
}, MinecraftServer.SERVER_THREAD_PRIORITY),
/**
* A tier for queues that contain tasks that are part of ticking,
* to assist the main ticking thread(s) in doing so.
*/
TICK_ASSIST(new AbstractTaskQueue[]{
BaseTaskQueues.tickAssist
}, Integer.getInteger("gale.thread.priority.tick", 7)),
/**
* A tier for queues that contain general tasks that must be performed at some point in time,
* asynchronously with respect to the {@link ServerThread} and the ticking of the server.
* Execution of
*/
ASYNC(new AbstractTaskQueue[0], Integer.getInteger("gale.thread.priority.async", 6)),
/**
* A tier for queues that contain tasks with the same considerations as {@link #ASYNC},
* but with a low priority.
*/
LOW_PRIORITY_ASYNC(new AbstractTaskQueue[0], Integer.getInteger("gale.thread.priority.async.low", 3));

/**
* Equal to {@link #ordinal()}.
*/
public final int ordinal;

/**
* The task queues that belong to this tier.
*/
public final AbstractTaskQueue[] taskQueues;

/**
* The priority for threads that are executing a task from this tier.
* <br>
* If a thread yields to other tasks, the priority it will have is always the highest priority of any task
* on its stack.
*/
public final int threadPriority;

BaseTaskQueueTier(AbstractTaskQueue[] taskQueues, int threadPriority) {
this.ordinal = this.ordinal();
this.taskQueues = taskQueues;
for (AbstractTaskQueue queue : this.taskQueues) {
queue.setTier(this);
}
this.threadPriority = threadPriority;
}

/**
* Equal to {@link #values()}.
*/
public static final BaseTaskQueueTier[] VALUES = values();

/**
* Equal to {@link #VALUES}{@code .length}.
*/
public static final int length = VALUES.length;

/**
* Equal to {@link #VALUES} without {@link #SERVER}.
*/
public static final BaseTaskQueueTier[] VALUES_EXCEPT_SERVER = Arrays.stream(VALUES).filter(tier -> tier != SERVER).toList().toArray(new BaseTaskQueueTier[length - 1]);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package net.gensokyoreimagined.nitori.executor.thread;

/**
* A thread created by the {@link BaseThreadPool}.
*
* @author Martijn Muijsers under AGPL-3.0
*/
public class AssistThread extends BaseThread {

/**
* The maximum yield depth. While an {@link AssistThread} has a yield depth equal to or greater than this value,
* it can not start more potentially yielding tasks.
*/
public static final int MAXIMUM_YIELD_DEPTH = Integer.getInteger("gale.yield.depth.max", 100);

/**
* The index of this thread, as needed as an argument to
* {@link BaseThreadPool#getThreadByAssistIndex(int)}.
*/
public final int assistThreadIndex;

/**
* Must only be called from {@link BaseThreadPool#addAssistThread}.
*/
public AssistThread(int assistThreadIndex) {
super(AssistThread::getCurrentAssistThreadAndRunForever, "Assist Thread " + assistThreadIndex, assistThreadIndex + 1, MAXIMUM_YIELD_DEPTH);
this.assistThreadIndex = assistThreadIndex;
}

/**
* Causes this thread to loop forever, always attempting to find a task to do, and if none is found,
* registering itself with the places where a relevant task may be added in order to be signalled when
* one is actually added.
*/
@ThisThreadOnly
protected void runForever() {
this.runTasksUntil(null, () -> false, null);
}

/**
* @return The current thread if it is a {@link AssistThread}, or null otherwise.
*/
@SuppressWarnings("unused")
@AnyThreadSafe
@YieldFree
public static @Nullable AssistThread currentAssistThread() {
return Thread.currentThread() instanceof AssistThread assistThread ? assistThread : null;
}

/**
* @return Whether the current thread is a {@link AssistThread}.
*/
@SuppressWarnings("unused")
@AnyThreadSafe
@YieldFree
public static boolean isAssistThread() {
return Thread.currentThread() instanceof AssistThread;
}

/**
* A method that simply acquires the {@link AssistThread} that is the current thread, and calls
* {@link #runForever()} on it.
*/
@BaseThreadOnly
protected static void getCurrentAssistThreadAndRunForever() {
((AssistThread) Thread.currentThread()).runForever();
}

}
Loading

0 comments on commit 566eebf

Please sign in to comment.