From 87817d8adfe702a7e66c37ca392671126ae2eebf Mon Sep 17 00:00:00 2001 From: Matt <4009945+MattBDev@users.noreply.github.com> Date: Thu, 6 Aug 2026 13:20:44 -0400 Subject: [PATCH 1/2] Document the secondary pool's task contract The javadoc on QueueHandler#async described what the secondary pool is for, but not what a task submitted to it is required to avoid. Two rules were relied on implicitly and are now stated: - A task must not block waiting on a future that this pool completes. ForkJoinPool only compensates for blocking it observes through managedBlock, so monitorenter and Future#get leave a worker counted as running and no replacement is started. Callers with such a dependency should submit the dependent half separately and chain the futures rather than blocking inside one task. - Actor and command work does not belong here; Actor#runAction serialises per actor without holding a worker. Also notes that downstream plugins should use their own threads rather than this pool, which is sized for FAWE's own cleanup work. Documentation only; no behavioural change. --- .../queue/implementation/QueueHandler.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java index e5f43ddbcb..e2416f3bbc 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java @@ -63,6 +63,27 @@ public abstract class QueueHandler implements Trimable, Runnable { /** * Secondary queue should be used for "cleanup" tasks that are likely to be shorter in life than those submitted to the * primary queue. They may be IO-bound tasks. + * + *
+ * Tasks submitted here must observe two rules: + *
+ *+ * Downstream plugins should not submit whole edits or other long-lived work to this pool, and should use their own + * threads instead. The pool is sized by {@code parallel-threads} for FAWE's own cleanup work; occupying it starves + * that work. + *
*/ private final ForkJoinPool forkJoinPoolSecondary = new ForkJoinPool( Settings.settings().QUEUE.PARALLEL_THREADS, @@ -195,6 +216,11 @@ public+ * The submitted task must not wait on a {@link Future} completed by this pool, and must not be actor or command work. + * See the {@code forkJoinPoolSecondary} field for the full contract and the reasoning behind it. + *
+ * * @param run Runnable to run * @param value Value to return when done * @param+ * The submitted task must not wait on a {@link Future} completed by this pool, and must not be actor or command work. + * See the {@code forkJoinPoolSecondary} field for the full contract and the reasoning behind it. + *
+ * * @param run Runnable to run * @return Future for submitted task */ @@ -219,6 +250,11 @@ public Future> async(Runnable run) { * Complete a task in the {@code forkJoinPoolSecondary} queue. Secondary queue should be used for "cleanup" tasks that are * likely to be shorter in life than those submitted to the primary queue. They may be IO-bound tasks. * + *+ * The submitted task must not wait on a {@link Future} completed by this pool, and must not be actor or command work. + * See the {@code forkJoinPoolSecondary} field for the full contract and the reasoning behind it. + *
+ * * @param call Callable to run * @param- * Tasks submitted here must observe two rules: - *
- *- * Downstream plugins should not submit whole edits or other long-lived work to this pool, and should use their own - * threads instead. The pool is sized by {@code parallel-threads} for FAWE's own cleanup work; occupying it starves - * that work. - *
+ * @see #getForkJoinPoolSecondary() the full task contract and the reasoning behind it */ private final ForkJoinPool forkJoinPoolSecondary = new ForkJoinPool( Settings.settings().QUEUE.PARALLEL_THREADS, @@ -218,7 +199,7 @@ public* The submitted task must not wait on a {@link Future} completed by this pool, and must not be actor or command work. - * See the {@code forkJoinPoolSecondary} field for the full contract and the reasoning behind it. + * See {@link #getForkJoinPoolSecondary()} for the full contract and the reasoning behind it. *
* * @param run Runnable to run @@ -236,7 +217,7 @@ public* The submitted task must not wait on a {@link Future} completed by this pool, and must not be actor or command work. - * See the {@code forkJoinPoolSecondary} field for the full contract and the reasoning behind it. + * See {@link #getForkJoinPoolSecondary()} for the full contract and the reasoning behind it. *
* * @param run Runnable to run @@ -252,7 +233,7 @@ public Future> async(Runnable run) { * ** The submitted task must not wait on a {@link Future} completed by this pool, and must not be actor or command work. - * See the {@code forkJoinPoolSecondary} field for the full contract and the reasoning behind it. + * See {@link #getForkJoinPoolSecondary()} for the full contract and the reasoning behind it. *
* * @param call Callable to run @@ -578,6 +559,27 @@ public ExecutorService getForkJoinPoolPrimary() { /** * Secondary queue should be used for "cleanup" tasks that are likely to be shorter in life than those submitted to the * primary queue. They may be IO-bound tasks. + * + *+ * Tasks submitted here must observe two rules: + *
+ *+ * Downstream plugins should not submit whole edits or other long-lived work to this pool, and should use their own + * threads instead. The pool is sized by {@code parallel-threads} for FAWE's own cleanup work; occupying it starves + * that work. + *
** Internal API usage only. *