diff --git a/patches/api/0001-Hide-irrelevant-compilation-warnings.patch b/patches/api/0001-Hide-irrelevant-compilation-warnings.patch index c0946ed..39d613a 100644 --- a/patches/api/0001-Hide-irrelevant-compilation-warnings.patch +++ b/patches/api/0001-Hide-irrelevant-compilation-warnings.patch @@ -7,7 +7,7 @@ License: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) Gale - https://galemc.org diff --git a/build.gradle.kts b/build.gradle.kts -index 9f5694204091e23c4771657127a06f98e27ad8f1..fdb495c0e0f1ce702a590e6a7bcdcf40fd418d1c 100644 +index 3c4dd6ebc2289c44c2f5723e7920aadffdc51884..f983a2c154da233919c277d951c7ddbeeaabf2e1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -83,6 +83,15 @@ val generateApiVersioningFile by tasks.registering { @@ -26,3 +26,12 @@ index 9f5694204091e23c4771657127a06f98e27ad8f1..fdb495c0e0f1ce702a590e6a7bcdcf40 tasks.jar { from(generateApiVersioningFile.map { it.outputs.files.singleFile }) { into("META-INF/maven/${project.group}/${project.name}") +@@ -131,6 +140,8 @@ tasks.withType { + into("build/docs/javadoc") + } + } ++ ++ options.addStringOption("Xdoclint:none", "-quiet") // Gale - hide irrelevant compilation warnings + } + + // Paper start diff --git a/patches/api/0004-SIMD-support.patch b/patches/api/0004-SIMD-support.patch index 45b35d6..fc9466f 100644 --- a/patches/api/0004-SIMD-support.patch +++ b/patches/api/0004-SIMD-support.patch @@ -13,7 +13,7 @@ As part of: Pufferfish (https://github.com/pufferfish-gg/Pufferfish) Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) diff --git a/build.gradle.kts b/build.gradle.kts -index 447b549bc7035410ae18f3e9063de2d014530b98..33e7179c81310283e5e6efb5fd776a60ae669624 100644 +index f983a2c154da233919c277d951c7ddbeeaabf2e1..e19c9544c346e7f505cfdd379be7f8f059d9e53d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -89,6 +89,7 @@ tasks.withType { @@ -24,6 +24,14 @@ index 447b549bc7035410ae18f3e9063de2d014530b98..33e7179c81310283e5e6efb5fd776a60 } // Gale end - hide irrelevant compilation warnings +@@ -142,6 +143,7 @@ tasks.withType { + } + + options.addStringOption("Xdoclint:none", "-quiet") // Gale - hide irrelevant compilation warnings ++ options.addStringOption("-add-modules", "jdk.incubator.vector") // Gale - Pufferfish - SIMD support + } + + // Paper start diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java new file mode 100644 index 0000000000000000000000000000000000000000..48312d416805697257e4cd3451b8d14bab7ea252 diff --git a/patches/api/0007-Player-canSee-by-entity-UUID.patch b/patches/api/0007-Player-canSee-by-entity-UUID.patch index 25816e9..cc689ea 100644 --- a/patches/api/0007-Player-canSee-by-entity-UUID.patch +++ b/patches/api/0007-Player-canSee-by-entity-UUID.patch @@ -37,7 +37,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 9f762cf670bf5db9138e468e72e57781d8d22f54..f0302931b082d9a70799d1984dfeb5b0e0973473 100644 +index 9f762cf670bf5db9138e468e72e57781d8d22f54..a729c8367cd755dd4e1e3dc314869b2136900158 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java @@ -1466,6 +1466,16 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM @@ -48,7 +48,7 @@ index 9f762cf670bf5db9138e468e72e57781d8d22f54..f0302931b082d9a70799d1984dfeb5b0 + /** + * Checks to see if an entity has been visually hidden from this player. + * -+ * @param uuid The {@link Entity#getUniqueId()} of the entity to check ++ * @param entityUUID The {@link Entity#getUniqueId()} of the entity to check + * @return True if the entity with the provided UUID is not being hidden from this player + */ + boolean canSee(@NotNull UUID entityUUID); diff --git a/patches/server/0101-Optimize-villager-data-storage.patch b/patches/server/0101-Optimize-villager-data-storage.patch index 9739098..360be78 100644 --- a/patches/server/0101-Optimize-villager-data-storage.patch +++ b/patches/server/0101-Optimize-villager-data-storage.patch @@ -212,7 +212,7 @@ index 75dc06a3041bfdfb08c914eb50cfa282ae9eb2fe..53b0519bbc5d52490040eaf0fe449648 } else { brain.eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index a4029cd16d964cd3a58f9f6e8471fbdf07de578b..7740adf7e852d860d08cdf15ab9d6b0fb6b68055 100644 +index a4029cd16d964cd3a58f9f6e8471fbdf07de578b..825cdb927925a35a53e399ee647ea0738e67a5c9 100644 --- a/src/main/java/net/minecraft/world/entity/npc/Villager.java +++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java @@ -88,6 +88,7 @@ import net.minecraft.world.item.trading.MerchantOffers; @@ -234,7 +234,7 @@ index a4029cd16d964cd3a58f9f6e8471fbdf07de578b..7740adf7e852d860d08cdf15ab9d6b0f private static final int MAX_GOSSIP_TOPICS = 10; private static final int GOSSIP_COOLDOWN = 1200; private static final int GOSSIP_DECAY_INTERVAL = 24000; -@@ -896,7 +898,27 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +@@ -896,7 +898,28 @@ public class Villager extends AbstractVillager implements ReputationEventHandler public boolean wantsToPickUp(ItemStack stack) { Item item = stack.getItem(); @@ -249,12 +249,13 @@ index a4029cd16d964cd3a58f9f6e8471fbdf07de578b..7740adf7e852d860d08cdf15ab9d6b0f + } + if (!isDesired) { + var requestedItems = this.getVillagerData().getProfession().requestedItems(); -+ if (requestedItems != null) { -+ for (Item requestedItem : requestedItems) { -+ if (requestedItem == item) { -+ isDesired = true; -+ break; -+ } ++ if (requestedItems == null) { ++ return false; ++ } ++ for (Item requestedItem : requestedItems) { ++ if (requestedItem == item) { ++ isDesired = true; ++ break; + } + } + } diff --git a/patches/server/0141-Thread-safety-annotations.patch b/patches/server/0141-Thread-safety-annotations.patch index 064b538..fa043da 100644 --- a/patches/server/0141-Thread-safety-annotations.patch +++ b/patches/server/0141-Thread-safety-annotations.patch @@ -53,10 +53,10 @@ index 0000000000000000000000000000000000000000..d07f68ff73a368c8f0da56152021a954 +} diff --git a/src/main/java/org/galemc/gale/executor/annotation/Guarded.java b/src/main/java/org/galemc/gale/executor/annotation/Guarded.java new file mode 100644 -index 0000000000000000000000000000000000000000..37365e1fd6042dc721d875021b4092410f095ca5 +index 0000000000000000000000000000000000000000..84a0bac98a382550c826e6adbecec1fe7be974a1 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/annotation/Guarded.java -@@ -0,0 +1,45 @@ +@@ -0,0 +1,47 @@ +// Gale - thread-safety annotations + +package org.galemc.gale.executor.annotation; @@ -65,6 +65,7 @@ index 0000000000000000000000000000000000000000..37365e1fd6042dc721d875021b409241 + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; ++import java.lang.annotation.Repeatable; +import java.lang.annotation.Target; + +/** @@ -82,6 +83,7 @@ index 0000000000000000000000000000000000000000..37365e1fd6042dc721d875021b409241 + * @author Martijn Muijsers under AGPL-3.0 + */ +@Documented ++@Repeatable +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +public @interface Guarded { + diff --git a/patches/server/0144-Paired-lock-and-condition-utility.patch b/patches/server/0144-Paired-lock-and-condition-utility.patch deleted file mode 100644 index d88a556..0000000 --- a/patches/server/0144-Paired-lock-and-condition-utility.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Martijn Muijsers -Date: Fri, 2 Dec 2022 20:31:06 +0100 -Subject: [PATCH] Paired lock and condition utility - -License: AGPL-3.0 (https://www.gnu.org/licenses/agpl-3.0.html) -Gale - https://galemc.org - -diff --git a/src/main/java/org/galemc/gale/concurrent/LockAndCondition.java b/src/main/java/org/galemc/gale/concurrent/LockAndCondition.java -new file mode 100644 -index 0000000000000000000000000000000000000000..73fd8ca0bd1168862a03d9bdcae93d62895e8c1f ---- /dev/null -+++ b/src/main/java/org/galemc/gale/concurrent/LockAndCondition.java -@@ -0,0 +1,28 @@ -+// Gale - paired lock and condition utility -+ -+package org.galemc.gale.concurrent; -+ -+import java.util.concurrent.locks.Condition; -+import java.util.concurrent.locks.Lock; -+ -+/** -+ * A utility class that stores a {@link Condition} with its {@link Lock}, that can be passed around and used instead -+ * of using an {@link Object} monitor, which does not support speculative locking. -+ * -+ * @author Martijn Muijsers -+ */ -+public class LockAndCondition { -+ -+ public final Lock lock; -+ public final Condition condition; -+ -+ public LockAndCondition(Lock lock) { -+ this(lock, lock.newCondition()); -+ } -+ -+ public LockAndCondition(Lock lock, Condition condition) { -+ this.lock = lock; -+ this.condition = condition; -+ } -+ -+} diff --git a/patches/server/0145-Unterminable-executor-utility.patch b/patches/server/0144-Unterminable-executor-utility.patch similarity index 100% rename from patches/server/0145-Unterminable-executor-utility.patch rename to patches/server/0144-Unterminable-executor-utility.patch diff --git a/patches/server/0146-FIFO-concurrent-queue-utility.patch b/patches/server/0145-FIFO-concurrent-queue-utility.patch similarity index 100% rename from patches/server/0146-FIFO-concurrent-queue-utility.patch rename to patches/server/0145-FIFO-concurrent-queue-utility.patch diff --git a/patches/server/0147-Base-thread-pools.patch b/patches/server/0146-Base-thread-pool.patch similarity index 62% rename from patches/server/0147-Base-thread-pools.patch rename to patches/server/0146-Base-thread-pool.patch index ed7c5ad..3f4d22b 100644 --- a/patches/server/0147-Base-thread-pools.patch +++ b/patches/server/0146-Base-thread-pool.patch @@ -1,13 +1,13 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Martijn Muijsers Date: Fri, 2 Dec 2022 11:43:51 +0100 -Subject: [PATCH] Base thread pools +Subject: [PATCH] Base thread pool License: AGPL-3.0 (https://www.gnu.org/licenses/agpl-3.0.html) Gale - https://galemc.org diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java -index b71404be2c82f7db35272b367af861e94d6c73d3..238e7aa6e8a9e9f26bc6dee8d7e49a853c3cc0e2 100644 +index b71404be2c82f7db35272b367af861e94d6c73d3..0b4ae235398eda804d6facd4db74c721e9d76b57 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java @@ -1,10 +1,15 @@ @@ -21,7 +21,7 @@ index b71404be2c82f7db35272b367af861e94d6c73d3..238e7aa6e8a9e9f26bc6dee8d7e49a85 public class PrioritisedThreadedTaskQueue implements PrioritisedExecutor { -+ private final boolean influenceMayHaveDelayedTasks; // Gale - base thread pools ++ private final boolean influenceMayHaveDelayedTasks; // Gale - base thread pool + protected final ArrayDeque[] queues = new ArrayDeque[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; { for (int i = 0; i < Priority.TOTAL_SCHEDULABLE_PRIORITIES; ++i) { @@ -34,11 +34,11 @@ index b71404be2c82f7db35272b367af861e94d6c73d3..238e7aa6e8a9e9f26bc6dee8d7e49a85 + this(false); + } + -+ // Gale start - base thread pools ++ // Gale start - base thread pool + public PrioritisedThreadedTaskQueue(boolean influenceMayHaveDelayedTasks) { + this.influenceMayHaveDelayedTasks = influenceMayHaveDelayedTasks; + } -+ // Gale end - base thread pools ++ // Gale end - base thread pool + @Override public PrioritisedExecutor.PrioritisedTask queueRunnable(final Runnable task, final PrioritisedExecutor.Priority priority) throws IllegalStateException, IllegalArgumentException { @@ -47,12 +47,12 @@ index b71404be2c82f7db35272b367af861e94d6c73d3..238e7aa6e8a9e9f26bc6dee8d7e49a85 } protected final long getAndAddTotalScheduledTasksVolatile(final long value) { -+ // Gale start - base thread pools ++ // Gale start - base thread pool + if (this.influenceMayHaveDelayedTasks) { + MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks = true; -+ BaseTaskQueues.allLevelsScheduledTickThreadChunk.signalReason.signalAnother(); ++ BaseTaskQueues.allLevelsScheduledTickThreadChunk.newTaskWasAdded(); + } -+ // Gale end - base thread pools ++ // Gale end - base thread pool return this.totalScheduledTasks.getAndAdd(value); } @@ -60,17 +60,17 @@ index b71404be2c82f7db35272b367af861e94d6c73d3..238e7aa6e8a9e9f26bc6dee8d7e49a85 return this.totalCompletedTasks.getAndAdd(value); } -+ // Gale start - base thread pools ++ // Gale start - base thread pool + public final boolean hasScheduledUncompletedTasksVolatile() { + return this.totalScheduledTasks.get() > this.totalCompletedTasks.get(); + } -+ // Gale end - base thread pools ++ // Gale end - base thread pool + protected static final class PrioritisedTask implements PrioritisedExecutor.PrioritisedTask { protected final PrioritisedThreadedTaskQueue queue; protected long id; diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java -index 4f3670b2bdb8b1b252e9f074a6af56a018a8c465..757229093ecce162c99e27c2f92ead2e1a1a2b10 100644 +index 4f3670b2bdb8b1b252e9f074a6af56a018a8c465..aa7467c0ce302c27d77f0af032b81c4f8ef9408d 100644 --- a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java +++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java @@ -22,6 +22,7 @@ import net.minecraft.world.level.block.EntityBlock; @@ -86,12 +86,354 @@ index 4f3670b2bdb8b1b252e9f074a6af56a018a8c465..757229093ecce162c99e27c2f92ead2e if (!Bukkit.isPrimaryThread()) { // Plugins? - MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo)); -+ ScheduledServerThreadTaskQueues.add(() -> modifyBlocks(chunkPacket, chunkPacketInfo), ScheduledServerThreadTaskQueues.ANTI_XRAY_MODIFY_BLOCKS_TASK_MAX_DELAY); // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> modifyBlocks(chunkPacket, chunkPacketInfo), ScheduledServerThreadTaskQueues.ANTI_XRAY_MODIFY_BLOCKS_TASK_MAX_DELAY); // Gale - base thread pool return; } +diff --git a/src/main/java/com/mojang/logging/LogUtils.java b/src/main/java/com/mojang/logging/LogUtils.java +index 49019b4a9bc4e634d54a9b0acaf9229a5c896f85..6aae3b36bfe3ffc630cd7af250633de3444095e8 100644 +--- a/src/main/java/com/mojang/logging/LogUtils.java ++++ b/src/main/java/com/mojang/logging/LogUtils.java +@@ -6,6 +6,7 @@ import org.apache.logging.log4j.core.LifeCycle; + import org.apache.logging.log4j.core.config.Configuration; + import org.apache.logging.log4j.core.config.LoggerConfig; + import org.apache.logging.log4j.spi.LoggerContext; ++import org.jetbrains.annotations.NotNull; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import org.slf4j.Marker; +@@ -66,4 +67,329 @@ public class LogUtils { + return LoggerFactory.getLogger(STACK_WALKER.getCallerClass().getSimpleName()); + } + // Paper end ++ ++ // Gale start - base thread pool - thread loggers ++ public static @NotNull Logger prefixLogger(@NotNull Logger logger, @NotNull Supplier<@NotNull String> prefixSupplier) { ++ return new org.slf4j.Logger() { ++ @Override ++ public String getName() { ++ return logger.getName(); ++ } ++ ++ @Override ++ public boolean isTraceEnabled() { ++ return logger.isTraceEnabled(); ++ } ++ ++ @Override ++ public void trace(String msg) { ++ logger.trace(prefixSupplier.get() + msg); ++ } ++ ++ @Override ++ public void trace(String format, Object arg) { ++ logger.trace(prefixSupplier.get() + format, arg); ++ } ++ ++ @Override ++ public void trace(String format, Object arg1, Object arg2) { ++ logger.trace(prefixSupplier.get() + format, arg1, arg2); ++ } ++ ++ @Override ++ public void trace(String format, Object... arguments) { ++ logger.trace(prefixSupplier.get() + format, arguments); ++ } ++ ++ @Override ++ public void trace(String msg, Throwable t) { ++ logger.trace(prefixSupplier.get() + msg, t); ++ } ++ ++ @Override ++ public boolean isTraceEnabled(Marker marker) { ++ return logger.isTraceEnabled(marker); ++ } ++ ++ @Override ++ public void trace(Marker marker, String msg) { ++ logger.trace(marker, prefixSupplier.get() + msg); ++ } ++ ++ @Override ++ public void trace(Marker marker, String format, Object arg) { ++ logger.trace(marker, prefixSupplier.get() + format, arg); ++ } ++ ++ @Override ++ public void trace(Marker marker, String format, Object arg1, Object arg2) { ++ logger.trace(marker, prefixSupplier.get() + format, arg1, arg2); ++ } ++ ++ @Override ++ public void trace(Marker marker, String format, Object... argArray) { ++ logger.trace(marker, prefixSupplier.get() + format, argArray); ++ } ++ ++ @Override ++ public void trace(Marker marker, String msg, Throwable t) { ++ logger.trace(marker, prefixSupplier.get() + msg, t); ++ } ++ ++ @Override ++ public boolean isDebugEnabled() { ++ return logger.isDebugEnabled(); ++ } ++ ++ @Override ++ public void debug(String msg) { ++ logger.debug(prefixSupplier.get() + msg); ++ } ++ ++ @Override ++ public void debug(String format, Object arg) { ++ logger.debug(prefixSupplier.get() + format, arg); ++ } ++ ++ @Override ++ public void debug(String format, Object arg1, Object arg2) { ++ logger.debug(prefixSupplier.get() + format, arg1, arg2); ++ } ++ ++ @Override ++ public void debug(String format, Object... arguments) { ++ logger.debug(prefixSupplier.get() + format, arguments); ++ } ++ ++ @Override ++ public void debug(String msg, Throwable t) { ++ logger.debug(prefixSupplier.get() + msg, t); ++ } ++ ++ @Override ++ public boolean isDebugEnabled(Marker marker) { ++ return logger.isDebugEnabled(marker); ++ } ++ ++ @Override ++ public void debug(Marker marker, String msg) { ++ logger.debug(marker, prefixSupplier.get() + msg); ++ } ++ ++ @Override ++ public void debug(Marker marker, String format, Object arg) { ++ logger.debug(marker, prefixSupplier.get() + format, arg); ++ } ++ ++ @Override ++ public void debug(Marker marker, String format, Object arg1, Object arg2) { ++ logger.debug(marker, prefixSupplier.get() + format, arg1, arg2); ++ } ++ ++ @Override ++ public void debug(Marker marker, String format, Object... arguments) { ++ logger.debug(marker, prefixSupplier.get() + format, arguments); ++ } ++ ++ @Override ++ public void debug(Marker marker, String msg, Throwable t) { ++ logger.debug(marker, prefixSupplier.get() + msg, t); ++ } ++ ++ @Override ++ public boolean isInfoEnabled() { ++ return logger.isInfoEnabled(); ++ } ++ ++ @Override ++ public void info(String msg) { ++ logger.info(prefixSupplier.get() + msg); ++ } ++ ++ @Override ++ public void info(String format, Object arg) { ++ logger.info(prefixSupplier.get() + format, arg); ++ } ++ ++ @Override ++ public void info(String format, Object arg1, Object arg2) { ++ logger.info(prefixSupplier.get() + format, arg1, arg2); ++ } ++ ++ @Override ++ public void info(String format, Object... arguments) { ++ logger.info(prefixSupplier.get() + format, arguments); ++ } ++ ++ @Override ++ public void info(String msg, Throwable t) { ++ logger.info(prefixSupplier.get() + msg, t); ++ } ++ ++ @Override ++ public boolean isInfoEnabled(Marker marker) { ++ return logger.isInfoEnabled(marker); ++ } ++ ++ @Override ++ public void info(Marker marker, String msg) { ++ logger.info(marker, prefixSupplier.get() + msg); ++ } ++ ++ @Override ++ public void info(Marker marker, String format, Object arg) { ++ logger.info(marker, prefixSupplier.get() + format, arg); ++ } ++ ++ @Override ++ public void info(Marker marker, String format, Object arg1, Object arg2) { ++ logger.info(marker, prefixSupplier.get() + format, arg1, arg2); ++ } ++ ++ @Override ++ public void info(Marker marker, String format, Object... arguments) { ++ logger.info(marker, prefixSupplier.get() + format, arguments); ++ } ++ ++ @Override ++ public void info(Marker marker, String msg, Throwable t) { ++ logger.info(marker, prefixSupplier.get() + msg, t); ++ } ++ ++ @Override ++ public boolean isWarnEnabled() { ++ return logger.isWarnEnabled(); ++ } ++ ++ @Override ++ public void warn(String msg) { ++ logger.warn(prefixSupplier.get() + msg); ++ } ++ ++ @Override ++ public void warn(String format, Object arg) { ++ logger.warn(prefixSupplier.get() + format, arg); ++ } ++ ++ @Override ++ public void warn(String format, Object arg1, Object arg2) { ++ logger.warn(prefixSupplier.get() + format, arg1, arg2); ++ } ++ ++ @Override ++ public void warn(String format, Object... arguments) { ++ logger.warn(prefixSupplier.get() + format, arguments); ++ } ++ ++ @Override ++ public void warn(String msg, Throwable t) { ++ logger.warn(prefixSupplier.get() + msg, t); ++ } ++ ++ @Override ++ public boolean isWarnEnabled(Marker marker) { ++ return logger.isWarnEnabled(marker); ++ } ++ ++ @Override ++ public void warn(Marker marker, String msg) { ++ logger.warn(marker, prefixSupplier.get() + msg); ++ } ++ ++ @Override ++ public void warn(Marker marker, String format, Object arg) { ++ logger.warn(marker, prefixSupplier.get() + format, arg); ++ } ++ ++ @Override ++ public void warn(Marker marker, String format, Object arg1, Object arg2) { ++ logger.warn(marker, prefixSupplier.get() + format, arg1, arg2); ++ } ++ ++ @Override ++ public void warn(Marker marker, String format, Object... arguments) { ++ logger.warn(marker, prefixSupplier.get() + format, arguments); ++ } ++ ++ @Override ++ public void warn(Marker marker, String msg, Throwable t) { ++ logger.warn(marker, prefixSupplier.get() + msg, t); ++ } ++ ++ @Override ++ public boolean isErrorEnabled() { ++ return logger.isErrorEnabled(); ++ } ++ ++ @Override ++ public void error(String msg) { ++ logger.error(prefixSupplier.get() + msg); ++ } ++ ++ @Override ++ public void error(String format, Object arg) { ++ logger.error(prefixSupplier.get() + format, arg); ++ } ++ ++ @Override ++ public void error(String format, Object arg1, Object arg2) { ++ logger.error(prefixSupplier.get() + format, arg1, arg2); ++ } ++ ++ @Override ++ public void error(String format, Object... arguments) { ++ logger.error(prefixSupplier.get() + format, arguments); ++ } ++ ++ @Override ++ public void error(String msg, Throwable t) { ++ logger.error(prefixSupplier.get() + msg, t); ++ } ++ ++ @Override ++ public boolean isErrorEnabled(Marker marker) { ++ return logger.isErrorEnabled(marker); ++ } ++ ++ @Override ++ public void error(Marker marker, String msg) { ++ logger.error(marker, prefixSupplier.get() + msg); ++ } ++ ++ @Override ++ public void error(Marker marker, String format, Object arg) { ++ logger.error(marker, prefixSupplier.get() + format, arg); ++ } ++ ++ @Override ++ public void error(Marker marker, String format, Object arg1, Object arg2) { ++ logger.error(marker, prefixSupplier.get() + format, arg1, arg2); ++ } ++ ++ @Override ++ public void error(Marker marker, String format, Object... arguments) { ++ logger.error(marker, prefixSupplier.get() + format, arguments); ++ } ++ ++ @Override ++ public void error(Marker marker, String msg, Throwable t) { ++ logger.error(marker, prefixSupplier.get() + msg, t); ++ } ++ ++ }; ++ } ++ ++ public static @NotNull Logger prefixLoggerWithThread(@NotNull Logger logger) { ++ return prefixLogger(logger, () -> "[" + Thread.currentThread().getName() + "] "); ++ } ++ ++ public static @NotNull Logger getLoggerPrefixedWithThread() { ++ return prefixLoggerWithThread(LoggerFactory.getLogger(STACK_WALKER.getCallerClass())); ++ } ++ ++ public static @NotNull Logger getClassLoggerPrefixedWithThread() { ++ return prefixLoggerWithThread(LoggerFactory.getLogger(STACK_WALKER.getCallerClass().getSimpleName())); ++ } ++ // Gale end - base thread pool - thread loggers ++ + } diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -index 84cc9397237fa0c17aa1012dfb5683c90eb6d3b8..3686b0330b48119e08cbc1528cfd86c5ec7bd8e9 100644 +index 84cc9397237fa0c17aa1012dfb5683c90eb6d3b8..f5c15d40094c2ddc6220b0595597d12103fcf425 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java @@ -113,7 +113,7 @@ public final class ChunkTaskScheduler { @@ -99,12 +441,12 @@ index 84cc9397237fa0c17aa1012dfb5683c90eb6d3b8..3686b0330b48119e08cbc1528cfd86c5 public final PrioritisedThreadPool.PrioritisedPoolExecutor loadExecutor; - private final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue(); -+ public final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue(true); // Gale end - base thread pools - private -> public, count delayed tasks ++ public final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue(true); // Gale - base thread pool - private -> public, count delayed tasks final ReentrantLock schedulingLock = new ReentrantLock(); public final ChunkHolderManager chunkHolderManager; diff --git a/src/main/java/io/papermc/paper/configuration/Configurations.java b/src/main/java/io/papermc/paper/configuration/Configurations.java -index cf6d50218769e3fecd12dbde70a03b5042feddf4..9b94defa9520ac5c17c9b8bf1cfb3b17ddac2d22 100644 +index cf6d50218769e3fecd12dbde70a03b5042feddf4..9d8ee965f7dcd0f416b7aa8368e34b911edef6b0 100644 --- a/src/main/java/io/papermc/paper/configuration/Configurations.java +++ b/src/main/java/io/papermc/paper/configuration/Configurations.java @@ -322,7 +322,7 @@ public abstract class Configurations { @@ -112,12 +454,12 @@ index cf6d50218769e3fecd12dbde70a03b5042feddf4..9b94defa9520ac5c17c9b8bf1cfb3b17 ConfigurationSection worlds = global.createSection(legacyWorldsSectionKey); worlds.set(legacyWorldDefaultsSectionKey, YamlConfiguration.loadConfiguration(this.globalFolder.resolve(this.defaultWorldConfigFileName).toFile())); - for (ServerLevel level : server.getAllLevels()) { -+ for (ServerLevel level : server.getAllLevelsArray()) { // Gale - base thread pools - optimize server levels ++ for (ServerLevel level : server.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels worlds.set(level.getWorld().getName(), YamlConfiguration.loadConfiguration(getWorldConfigFile(level).toFile())); } return global; diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -index 3088d5f008a8cb5a75f1e11bd80a2614a4c1b75d..0c771f024cec0b3fbb68e4eeeeb778187a89456f 100644 +index 3088d5f008a8cb5a75f1e11bd80a2614a4c1b75d..052ac3ee2a59995ae48f141b2b81b25e1c220f42 100644 --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -285,7 +285,7 @@ public class PaperConfigurations extends Configurations void ensureRunningOnSameThread(Packet packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException { - PacketUtils.ensureRunningOnSameThread(packet, listener, (BlockableEventLoop) world.getServer()); -+ PacketUtils.ensureRunningOnSameThread(packet, listener, world.getServer()); // Gale - base thread pools ++ PacketUtils.ensureRunningOnSameThread(packet, listener, world.getServer()); // Gale - base thread pool } - public static void ensureRunningOnSameThread(Packet packet, T listener, BlockableEventLoop engine) throws RunningOnDifferentThreadException { -+ public static void ensureRunningOnSameThread(Packet packet, T listener, AbstractBlockableEventLoop engine) throws RunningOnDifferentThreadException { // Gale - base thread pools ++ public static void ensureRunningOnSameThread(Packet packet, T listener, AbstractBlockableEventLoop engine) throws RunningOnDifferentThreadException { // Gale - base thread pool if (!engine.isSameThread()) { engine.execute(() -> { // Paper - Fix preemptive player kick on a server shutdown. packetProcessing.push(listener); // Paper - detailed watchdog information diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 0c4c62674b4c7e8e3921c7eb3ef726759ac75075..31b488a3c1b81b99bf5bda9f90c3cf697d727841 100644 +index 0c4c62674b4c7e8e3921c7eb3ef726759ac75075..40f20806cc06106b4aa8e708467dcea94d23c83e 100644 --- a/src/main/java/net/minecraft/server/Main.java +++ b/src/main/java/net/minecraft/server/Main.java @@ -1,27 +1,22 @@ @@ -450,16 +789,15 @@ index 0c4c62674b4c7e8e3921c7eb3ef726759ac75075..31b488a3c1b81b99bf5bda9f90c3cf69 import net.minecraft.SharedConstants; import net.minecraft.Util; import net.minecraft.commands.Commands; -@@ -57,6 +52,8 @@ import net.minecraft.world.level.storage.LevelStorageSource; +@@ -57,6 +52,7 @@ import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.LevelSummary; import net.minecraft.world.level.storage.PrimaryLevelData; import net.minecraft.world.level.storage.WorldData; -+import org.galemc.gale.executor.queue.BaseTaskQueues; -+import org.galemc.gale.executor.thread.pooled.AsyncThreadPool; ++import org.galemc.gale.executor.queue.BaseTaskQueueTier; import org.slf4j.Logger; // CraftBukkit start -@@ -64,7 +61,7 @@ import com.google.common.base.Charsets; +@@ -64,7 +60,7 @@ import com.google.common.base.Charsets; import com.mojang.bridge.game.PackType; import java.io.InputStreamReader; import java.util.concurrent.atomic.AtomicReference; @@ -468,43 +806,55 @@ index 0c4c62674b4c7e8e3921c7eb3ef726759ac75075..31b488a3c1b81b99bf5bda9f90c3cf69 import org.bukkit.configuration.file.YamlConfiguration; // CraftBukkit end -@@ -228,6 +225,15 @@ public class Main { +@@ -228,6 +224,12 @@ public class Main { WorldStem worldstem; -+ // Gale start - base thread pools -+ // Initialize the task queues by calling an arbitrary method on the last queue ++ // Gale start - base thread pool ++ // Initialize the task tiers and queues by calling an arbitrary method on the last tier and queue + //noinspection ResultOfMethodCallIgnored -+ BaseTaskQueues.scheduledAsync.hashCode(); -+ // Initialize the async executor by calling an arbitrary method -+ //noinspection ResultOfMethodCallIgnored -+ AsyncThreadPool.instance.hashCode(); -+ // Gale end - base thread pools ++ BaseTaskQueueTier.ASYNC.ordinal(); ++ // Gale end - base thread pool + try { WorldLoader.InitConfig worldloader_c = Main.loadOrCreateConfig(dedicatedserversettings.getProperties(), convertable_conversionsession, flag, resourcepackrepository); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index eb951c9fda85d9620d3038a3db22d578db45e878..60ed76588347f4d4c09d8df4952bf55501ed7c00 100644 +index eb951c9fda85d9620d3038a3db22d578db45e878..77b4d5050a5e708522cc07e819db2b3619c46518 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -40,7 +40,6 @@ import java.util.Optional; +@@ -40,10 +40,8 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; -import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BooleanSupplier; - import java.util.function.Consumer; -@@ -120,7 +119,6 @@ import net.minecraft.util.profiling.metrics.profiling.InactiveMetricsRecorder; - import net.minecraft.util.profiling.metrics.profiling.MetricsRecorder; - import net.minecraft.util.profiling.metrics.profiling.ServerMetricsSamplersProvider; - import net.minecraft.util.profiling.metrics.storage.MetricsPersister; +-import java.util.function.Consumer; + import java.util.function.Function; + import java.util.stream.Collectors; + import java.util.stream.Stream; +@@ -108,19 +106,8 @@ import net.minecraft.util.ProgressListener; + import net.minecraft.util.RandomSource; + import net.minecraft.util.SignatureValidator; + import net.minecraft.util.datafix.DataFixers; +-import net.minecraft.util.profiling.EmptyProfileResults; +-import net.minecraft.util.profiling.ProfileResults; +-import net.minecraft.util.profiling.ProfilerFiller; +-import net.minecraft.util.profiling.ResultField; +-import net.minecraft.util.profiling.SingleTickProfiler; + import net.minecraft.util.profiling.jfr.JvmProfiler; + import net.minecraft.util.profiling.jfr.callback.ProfiledDuration; +-import net.minecraft.util.profiling.metrics.profiling.ActiveMetricsRecorder; +-import net.minecraft.util.profiling.metrics.profiling.InactiveMetricsRecorder; +-import net.minecraft.util.profiling.metrics.profiling.MetricsRecorder; +-import net.minecraft.util.profiling.metrics.profiling.ServerMetricsSamplersProvider; +-import net.minecraft.util.profiling.metrics.storage.MetricsPersister; -import net.minecraft.util.thread.ReentrantBlockableEventLoop; import net.minecraft.world.Difficulty; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.ai.village.VillageSiege; -@@ -161,7 +159,15 @@ import net.minecraft.world.level.storage.loot.PredicateManager; +@@ -161,7 +148,15 @@ import net.minecraft.world.level.storage.loot.PredicateManager; import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; import org.apache.commons.lang3.Validate; @@ -514,46 +864,59 @@ index eb951c9fda85d9620d3038a3db22d578db45e878..60ed76588347f4d4c09d8df4952bf555 +import org.galemc.gale.executor.queue.BaseTaskQueues; +import org.galemc.gale.executor.queue.ScheduledServerThreadTaskQueues; +import org.galemc.gale.executor.thread.OriginalServerThread; -+import org.galemc.gale.executor.thread.pooled.AsyncThreadPool; -+import org.galemc.gale.executor.thread.wait.SignalReason; ++import org.galemc.gale.executor.thread.SignalReason; ++import org.galemc.gale.executor.thread.pool.BaseThreadActivation; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; // CraftBukkit start -@@ -192,11 +198,19 @@ import org.bukkit.event.server.ServerLoadEvent; +@@ -181,23 +176,26 @@ import net.minecraft.world.level.levelgen.PatrolSpawner; + import net.minecraft.world.level.levelgen.PhantomSpawner; + import net.minecraft.world.level.levelgen.WorldDimensions; + import net.minecraft.world.level.levelgen.presets.WorldPresets; +-import org.bukkit.Bukkit; +-import org.bukkit.craftbukkit.CraftServer; +-import org.bukkit.craftbukkit.Main; +-import org.bukkit.craftbukkit.util.CraftChatMessage; +-import org.bukkit.craftbukkit.util.LazyPlayerSet; +-import org.bukkit.event.player.AsyncPlayerChatPreviewEvent; + import org.bukkit.event.server.ServerLoadEvent; + // CraftBukkit end import co.aikar.timings.MinecraftTimings; // Paper -public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements CommandSource, AutoCloseable { -+public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop implements CommandSource, AutoCloseable { // Gale - base thread pools ++public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop implements CommandSource, AutoCloseable { // Gale - base thread pool public static final int SERVER_THREAD_PRIORITY = Integer.getInteger("gale.thread.priority.server", 8); // Gale - server thread priority environment variable - private static MinecraftServer SERVER; // Paper -+ // Gale start - base thread pools -+ public static MinecraftServer SERVER; // Paper // Gale - base thread pools - private -> public ++ // Gale start - base thread pool ++ public static MinecraftServer SERVER; // Paper // Gale - base thread pool - private -> public + + /** + * Whether {@link #SERVER} has been set. + */ + public static boolean isConstructed; + -+ // Gale end - base thread pools ++ // Gale end - base thread pool public static final Logger LOGGER = LogUtils.getLogger(); ++ public static final Optional THREAD_DEBUG_LOGGER = Boolean.FALSE ? Optional.of(LogUtils.prefixLoggerWithThread(LogUtils.prefixLogger(LogUtils.getLogger(), () -> "TEMP DEBUG - "))) : Optional.empty(); // Gale - base thread pool - temporary debug logger public static final String VANILLA_BRAND = "vanilla"; private static final float AVERAGE_TICK_TIME_SMOOTHING = 0.8F; -@@ -226,6 +240,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop registries; private Map, ServerLevel> levels; -+ // Gale start - base thread pools - optimize server levels ++ // Gale start - base thread pool - optimize server levels + private @NotNull ServerLevel @NotNull [] levelArray = ArrayConstants.emptyServerLevelArray; + private @Nullable ServerLevel overworld; -+ // Gale end - base thread pools - optimize server levels ++ // Gale end - base thread pool - optimize server levels private PlayerList playerList; private volatile boolean running; private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart -@@ -255,10 +273,115 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop static, final -> non-final (but still effectively final) -+ // Gale start - base thread pools - make fields volatile ++ public static OriginalServerThread serverThread; // Gale - base thread pool - rename, instance -> static, final -> non-final (but still effectively final) ++ // Gale start - base thread pool - make fields volatile + private volatile long nextTickTime; + private volatile long delayedTasksMaxNextTickTime; -+ // Gale end - base thread pools - make fields volatile ++ // Gale end - base thread pool - make fields volatile + -+ // Gale start - base thread pools ++ // Gale start - base thread pool + + public static volatile long nextTickStartNanoTime; + public static volatile long delayedTasksMaxNextTickNanoTime; @@ -649,63 +1012,62 @@ index eb951c9fda85d9620d3038a3db22d578db45e878..60ed76588347f4d4c09d8df4952bf555 + */ + public static volatile boolean currentManagedBlockStopConditionHasBecomeTrue = false; + -+ public static final SignalReason managedBlockStopConditionBecameTrueSignalReason = SignalReason.createNonRetrying(); -+ + public static void signalServerThreadIfCurrentManagedBlockStopConditionBecameTrue() { + if (currentManagedBlockStopConditionHasBecomeTrue) { + // We already signalled the thread + return; + } -+ if (currentManagedBlockStopCondition == null) { ++ var managedBlockStopCondition = currentManagedBlockStopCondition; ++ if (managedBlockStopCondition == null) { + // There is no ongoing managedBlock cal + return; + } -+ if (!currentManagedBlockStopCondition.getAsBoolean()) { ++ if (!managedBlockStopCondition.getAsBoolean()) { + // The stop condition is not true + return; + } + currentManagedBlockStopConditionHasBecomeTrue = true; -+ serverThread.signal(managedBlockStopConditionBecameTrueSignalReason); ++ serverThread.signal(null); + } + -+ // Gale start - base thread pools ++ // Gale start - base thread pool + private final PackRepository packRepository; private final ServerScoreboard scoreboard; @Nullable -@@ -287,7 +410,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); public int autosavePeriod; public Commands vanillaCommandDispatcher; - public boolean forceTicks; // Paper -+ public volatile boolean forceTicks; // Paper // Gale - base thread pools - make fields volatile ++ public volatile boolean forceTicks; // Paper // Gale - base thread pool - make fields volatile // CraftBukkit end // Spigot start public static final int TPS = 20; -@@ -303,9 +426,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { -+ public static S spin(Function serverFactory) { // Gale - base thread pools ++ public static S spin(Function serverFactory) { // Gale - base thread pool AtomicReference atomicreference = new AtomicReference(); - Thread thread = new io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system -+ OriginalServerThread thread = new OriginalServerThread(() -> { // Paper - rewrite chunk system // Gale - base thread pools ++ OriginalServerThread thread = new OriginalServerThread(() -> { // Paper - rewrite chunk system // Gale - base thread pool ((MinecraftServer) atomicreference.get()).runServer(); }, "Server thread"); -@@ -324,16 +447,19 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop holdergetter = this.registries.compositeAccess().registryOrThrow(Registries.BLOCK).asLookup().filterFeatures(this.worldData.enabledFeatures()); this.structureTemplateManager = new StructureTemplateManager(worldstem.resourceManager(), convertable_conversionsession, datafixer, holdergetter); - this.serverThread = thread; -+ // Gale start - base thread pools ++ // Gale start - base thread pool + serverThread = thread; -+ AsyncThreadPool.instance.intendedActiveThreadCountMayHaveChanged(); -+ // Gale end - base thread pools ++ BaseThreadActivation.callForUpdate(); ++ // Gale end - base thread pool this.executor = Util.backgroundExecutor(); } // CraftBukkit start -@@ -599,7 +728,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop public ++ public boolean hasStopped = false; // Gale - base thread pool - private -> public public volatile boolean hasFullyShutdown = false; // Paper private boolean hasLoggedStop = false; // Paper private final Object stopLock = new Object(); -@@ -916,8 +1046,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop Util.getMillis() - startTime >= 30 || !BaseTaskQueues.scheduledAsync.hasTasks(), null); // Paper + LOGGER.info("Shutting down IO executor..."); -+ // Gale end - base thread pools - remove Paper async executor -+ // Gale end - base thread pools - remove background executor ++ // Gale end - base thread pool - remove Paper async executor ++ // Gale end - base thread pool - remove background executor Util.shutdownExecutors(); // Paper LOGGER.info("Closing Server"); try { -@@ -1017,7 +1148,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop public ++ public boolean haveTime() { // Gale - base thread pool - private -> public // Paper start if (this.forceTicks) { return true; -@@ -1253,13 +1424,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop public ++ public volatile boolean isOversleep = false; // Gale - base thread pool - make fields volatile, package -> public private boolean canOversleep() { - return this.mayHaveDelayedTasks && Util.getMillis() < this.delayedTasksMaxNextTickTime; -+ return Util.getMillis() < this.delayedTasksMaxNextTickTime && mayHaveDelayedTasks(); // Gale - base thread pools ++ return Util.getMillis() < this.delayedTasksMaxNextTickTime && mayHaveDelayedTasks(); // Gale - base thread pool } private boolean canSleepForTickNoOversleep() { -@@ -1268,7 +1439,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { return !this.canSleepForTickNoOversleep(); // Paper - move oversleep into full server tick -+ // Gale start - base thread pools ++ // Gale start - base thread pool }); + isInSpareTime = false; + isWaitingUntilNextTick = false; -+ // Gale end - base thread pools ++ // Gale end - base thread pool lastTickOversleepTime = (System.nanoTime() - tickOversleepStart) / 1000000L; // Gale - YAPFA - last tick time } @@ -1044,36 +1405,36 @@ index eb951c9fda85d9620d3038a3db22d578db45e878..60ed76588347f4d4c09d8df4952bf555 private void updateStatusIcon(ServerStatus metadata) { Optional optional = Optional.of(this.getFile("server-icon.png")).filter(File::isFile); -@@ -1378,14 +1508,19 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { return !this.canOversleep(); -+ // Gale start - base thread pools ++ // Gale start - base thread pool }); + isInSpareTime = false; -+ // Gale end - base thread pools ++ // Gale end - base thread pool isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); // Paper end new com.destroystokyo.paper.event.server.ServerTickStartEvent(this.tickCount+1).callEvent(); // Paper ++this.tickCount; -+ ScheduledServerThreadTaskQueues.shiftTasksForNextTick(); // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.shiftTasksForNextTick(); // Gale - base thread pool this.tickChildren(shouldKeepTicking); if (i - this.lastServerStatus >= 5000000000L) { this.lastServerStatus = i; -@@ -1420,7 +1555,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { this.playerList.saveAll(playerSaveInterval); } - for (ServerLevel level : this.getAllLevels()) { -+ for (ServerLevel level : this.getAllLevelsArray()) { // Gale - base thread pools - optimize server levels ++ for (ServerLevel level : this.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels if (level.paperConfig().chunks.autoSaveInterval.value() > 0) { level.saveIncrementally(fullSave); } -@@ -1432,7 +1567,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper -@@ -1569,7 +1702,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); newLevels.put(level.dimension(), level); this.levels = Collections.unmodifiableMap(newLevels); -+ // Gale start - base thread pools - optimize server levels ++ // Gale start - base thread pool - optimize server levels + this.levelArray = newLevels.values().toArray(this.levelArray); + for (int i = 0; i < this.levelArray.length; i++) { + this.levelArray[i].serverLevelArrayIndex = i; + } + this.overworld = null; -+ // Gale end - base thread pools - optimize server levels ++ // Gale end - base thread pool - optimize server levels } public void removeLevel(ServerLevel level) { -@@ -1598,6 +1743,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); newLevels.remove(level.dimension()); this.levels = Collections.unmodifiableMap(newLevels); -+ // Gale start - base thread pools - optimize server levels ++ // Gale start - base thread pool - optimize server levels + level.serverLevelArrayIndex = -1; + this.levelArray = newLevels.values().toArray(this.levelArray); + for (int i = 0; i < this.levelArray.length; i++) { + this.levelArray[i].serverLevelArrayIndex = i; + } + this.overworld = null; -+ // Gale end - base thread pools - optimize server levels ++ // Gale end - base thread pool - optimize server levels } // CraftBukkit end -@@ -1605,8 +1758,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop getAllLevels() { - return this.levels.values(); -+ return this.levels == null ? Collections.emptyList() : this.levels.values(); // Gale - base thread pools ++ return this.levels == null ? Collections.emptyList() : this.levels.values(); // Gale - base thread pool } public String getServerVersion() { -@@ -1726,10 +1885,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop players; public final ServerChunkCache chunkSource; -+ // Gale start - base thread pools ++ // Gale start - base thread pool + @AnyThreadSafe(Access.READ) + public volatile int serverLevelArrayIndex; -+ // Gale end - base thread pools ++ // Gale end - base thread pool private final MinecraftServer server; public final PrimaryLevelData serverLevelData; // CraftBukkit - type final EntityTickList entityTickList; @@ -1379,39 +1741,41 @@ index 37e0b6212fec71ec9662e6be3b1e8bea487eb4a6..251f098fa2203d06e5e5aa68a31a7653 if ( entity instanceof Player ) { - com.google.common.collect.Streams.stream( ServerLevel.this.getServer().getAllLevels() ).map( ServerLevel::getDataStorage ).forEach( (worldData) -> -+ Arrays.stream( ServerLevel.this.getServer().getAllLevelsArray() ).map( ServerLevel::getDataStorage ).forEach( (worldData) -> // Gale - base thread pools - optimize server levels ++ Arrays.stream( ServerLevel.this.getServer().getAllLevelsArray() ).map( ServerLevel::getDataStorage ).forEach( (worldData) -> // Gale - base thread pool - optimize server levels { for (Object o : worldData.cache.values() ) { diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 68e0f2208c5f098042ebfad08301e3154e2a2152..deadeb7a98e5b64d7b9fae3a9e7858a4cd1d39e2 100644 +index 68e0f2208c5f098042ebfad08301e3154e2a2152..05234b2093be73f36e53590e4e44027376294828 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -187,6 +187,8 @@ import net.minecraft.world.phys.shapes.VoxelShape; +@@ -185,8 +185,9 @@ import net.minecraft.world.phys.shapes.BooleanOp; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions; - import org.apache.commons.lang3.StringUtils; +-import org.apache.commons.lang3.StringUtils; import org.galemc.gale.configuration.GaleGlobalConfiguration; +import org.galemc.gale.executor.queue.BaseTaskQueues; +import org.galemc.gale.executor.queue.ScheduledServerThreadTaskQueues; import org.slf4j.Logger; // CraftBukkit start -@@ -552,7 +554,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -552,7 +553,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic Objects.requireNonNull(this.connection); // CraftBukkit - Don't wait - minecraftserver.scheduleOnMain(networkmanager::handleDisconnection); // Paper -+ ScheduledServerThreadTaskQueues.add(networkmanager::handleDisconnection, ScheduledServerThreadTaskQueues.HANDLE_DISCONNECT_TASK_MAX_DELAY); // Paper // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(networkmanager::handleDisconnection, ScheduledServerThreadTaskQueues.HANDLE_DISCONNECT_TASK_MAX_DELAY); // Paper // Gale - base thread pool } private CompletableFuture filterTextPacket(T text, BiFunction> filterer) { -@@ -883,21 +885,20 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -883,21 +884,20 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } // Paper start - private static final java.util.concurrent.ExecutorService TAB_COMPLETE_EXECUTOR = java.util.concurrent.Executors.newFixedThreadPool(4, - new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Tab Complete Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); -+ private static final java.util.concurrent.ExecutorService TAB_COMPLETE_EXECUTOR = BaseTaskQueues.scheduledAsync.yieldingExecutor; // Gale - base thread pools - remove tab complete executor ++ private static final java.util.concurrent.ExecutorService TAB_COMPLETE_EXECUTOR = BaseTaskQueues.scheduledAsync.yieldingExecutor; // Gale - base thread pool - remove tab complete executor // Paper end @Override public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { @@ -1419,50 +1783,50 @@ index 68e0f2208c5f098042ebfad08301e3154e2a2152..deadeb7a98e5b64d7b9fae3a9e7858a4 // CraftBukkit start if (this.chatSpamTickCount.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper start - split and make configurable - server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause // Gale - JettPack - reduce array allocations -+ ScheduledServerThreadTaskQueues.add(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), ScheduledServerThreadTaskQueues.KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY); // Paper - kick event cause // Gale - JettPack - reduce array allocations // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), ScheduledServerThreadTaskQueues.KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY); // Paper - kick event cause // Gale - JettPack - reduce array allocations // Gale - base thread pool return; } // Paper start String str = packet.getCommand(); int index = -1; if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) { - server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause // Gale - JettPack - reduce array allocations -+ ScheduledServerThreadTaskQueues.add(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), ScheduledServerThreadTaskQueues.KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY); // Paper - kick event cause // Gale - JettPack - reduce array allocations // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), ScheduledServerThreadTaskQueues.KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY); // Paper - kick event cause // Gale - JettPack - reduce array allocations // Gale - base thread pool return; } // Paper end -@@ -922,7 +923,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -922,7 +922,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (!event.isHandled()) { if (!event.isCancelled()) { - this.server.scheduleOnMain(() -> { // This needs to be on main -+ ScheduledServerThreadTaskQueues.add(() -> { // This needs to be on main // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> { // This needs to be on main // Gale - base thread pool ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { -@@ -933,7 +934,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -933,7 +933,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestEvent.getSuggestions())); // Paper end - Brigadier API }); - }); -+ }, ScheduledServerThreadTaskQueues.SEND_COMMAND_COMPLETION_SUGGESTIONS_TASK_MAX_DELAY); // Gale - base thread pools ++ }, ScheduledServerThreadTaskQueues.SEND_COMMAND_COMPLETION_SUGGESTIONS_TASK_MAX_DELAY); // Gale - base thread pool } } else if (!completions.isEmpty()) { final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(command, stringreader.getTotalLength()); -@@ -1247,7 +1248,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1247,7 +1247,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; if (byteLength > 256 * 4) { ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); - server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause -+ ScheduledServerThreadTaskQueues.add(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), ScheduledServerThreadTaskQueues.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY); // Paper - kick event cause // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), ScheduledServerThreadTaskQueues.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY); // Paper - kick event cause // Gale - base thread pool return; } byteTotal += byteLength; -@@ -1270,14 +1271,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1270,14 +1270,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (byteTotal > byteAllowed) { ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); - server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause -+ ScheduledServerThreadTaskQueues.add(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), ScheduledServerThreadTaskQueues.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY); // Paper - kick event cause // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), ScheduledServerThreadTaskQueues.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY); // Paper - kick event cause // Gale - base thread pool return; } } @@ -1470,11 +1834,11 @@ index 68e0f2208c5f098042ebfad08301e3154e2a2152..deadeb7a98e5b64d7b9fae3a9e7858a4 // CraftBukkit start if (this.lastBookTick + 20 > MinecraftServer.currentTick) { - server.scheduleOnMain(() -> this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause // Paper - Also ensure this is called on main -+ ScheduledServerThreadTaskQueues.add(() -> this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), ScheduledServerThreadTaskQueues.KICK_FOR_EDITING_BOOK_TOO_QUICKLY_TASK_MAX_DELAY); // Paper - kick event cause // Paper - Also ensure this is called on main // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), ScheduledServerThreadTaskQueues.KICK_FOR_EDITING_BOOK_TOO_QUICKLY_TASK_MAX_DELAY); // Paper - kick event cause // Paper - Also ensure this is called on main // Gale - base thread pool return; } this.lastBookTick = MinecraftServer.currentTick; -@@ -2081,10 +2082,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2081,10 +2081,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); if (this.player.isSpectator()) { @@ -1482,58 +1846,58 @@ index 68e0f2208c5f098042ebfad08301e3154e2a2152..deadeb7a98e5b64d7b9fae3a9e7858a4 - - while (iterator.hasNext()) { - ServerLevel worldserver = (ServerLevel) iterator.next(); -+ for (ServerLevel worldserver : this.server.getAllLevelsArray()) { // Gale - base thread pools - optimize server levels ++ for (ServerLevel worldserver : this.server.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels Entity entity = packet.getEntity(worldserver); if (entity != null) { -@@ -2233,9 +2231,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2233,9 +2230,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } // CraftBukkit end if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.message())) { - this.server.scheduleOnMain(() -> { // Paper - push to main for event firing -+ ScheduledServerThreadTaskQueues.add(() -> { // Paper - push to main for event firing // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> { // Paper - push to main for event firing // Gale - base thread pool this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add cause - }); // Paper - push to main for event firing -+ }, ScheduledServerThreadTaskQueues.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main for event firing // Gale - base thread pools ++ }, ScheduledServerThreadTaskQueues.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main for event firing // Gale - base thread pool } else { Optional optional = this.tryHandleChat(packet.message(), packet.timeStamp(), packet.lastSeenMessages()); -@@ -2269,9 +2267,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2269,9 +2266,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @Override public void handleChatCommand(ServerboundChatCommandPacket packet) { if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.command())) { - this.server.scheduleOnMain(() -> { // Paper - push to main for event firing -+ ScheduledServerThreadTaskQueues.add(() -> { // Paper - push to main for event firing // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> { // Paper - push to main for event firing // Gale - base thread pool this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - }); // Paper - push to main for event firing -+ }, ScheduledServerThreadTaskQueues.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main for event firing // Gale - base thread pools ++ }, ScheduledServerThreadTaskQueues.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main for event firing // Gale - base thread pool } else { Optional optional = this.tryHandleChat(packet.command(), packet.timeStamp(), packet.lastSeenMessages()); -@@ -2353,9 +2351,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2353,9 +2350,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic private Optional tryHandleChat(String message, Instant timestamp, LastSeenMessages.Update acknowledgment) { if (!this.updateChatOrder(timestamp)) { if (GaleGlobalConfiguration.get().logToConsole.chat.outOfOrderMessageWarning) ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}': {} > {}", this.player.getName().getString(), message, this.lastChatTimeStamp.get().getEpochSecond(), timestamp.getEpochSecond()); // Paper // Gale - do not log out-of-order message warnings - this.server.scheduleOnMain(() -> { // Paper - push to main - this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event ca - }); // Paper - push to main -+ ScheduledServerThreadTaskQueues.add(() -> { // Paper - push to main // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> { // Paper - push to main // Gale - base thread pool + this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event cause -+ }, ScheduledServerThreadTaskQueues.KICK_FOR_OUT_OF_ORDER_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main // Gale - base thread pools ++ }, ScheduledServerThreadTaskQueues.KICK_FOR_OUT_OF_ORDER_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main // Gale - base thread pool return Optional.empty(); } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false)); -@@ -3290,7 +3288,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -3290,7 +3287,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic // Paper start if (!org.bukkit.Bukkit.isPrimaryThread()) { if (recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { - server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause // Gale - JettPack - reduce array allocations -+ ScheduledServerThreadTaskQueues.add(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), ScheduledServerThreadTaskQueues.KICK_FOR_RECIPE_PACKET_SPAM_TASK_MAX_DELAY); // Paper - kick event cause // Gale - JettPack - reduce array allocations // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), ScheduledServerThreadTaskQueues.KICK_FOR_RECIPE_PACKET_SPAM_TASK_MAX_DELAY); // Paper - kick event cause // Gale - JettPack - reduce array allocations // Gale - base thread pool return; } } diff --git a/src/main/java/net/minecraft/server/network/TextFilterClient.java b/src/main/java/net/minecraft/server/network/TextFilterClient.java -index 4b3d2280326c7eeda4952c36edff141cbff90e16..fa3a58f09178604e301b107f1a029e59a7164e13 100644 +index 4b3d2280326c7eeda4952c36edff141cbff90e16..e684fa1990d631cafd8e84debe52301fc9ed329f 100644 --- a/src/main/java/net/minecraft/server/network/TextFilterClient.java +++ b/src/main/java/net/minecraft/server/network/TextFilterClient.java @@ -23,7 +23,6 @@ import java.util.List; @@ -1557,12 +1921,12 @@ index 4b3d2280326c7eeda4952c36edff141cbff90e16..fa3a58f09178604e301b107f1a029e59 this.leaveEndpoint = leaveEndpoint; this.leaveEncoder = leaveEncoder; - this.workerPool = Executors.newFixedThreadPool(parallelism, THREAD_FACTORY); -+ this.workerPool = BaseTaskQueues.scheduledAsync.yieldingExecutor; // Gale - base thread pools - remove text filter executor ++ this.workerPool = BaseTaskQueues.scheduledAsync.yieldingExecutor; // Gale - base thread pool - remove text filter executor } private static URL getEndpoint(URI root, @Nullable JsonObject endpoints, String key, String fallback) throws MalformedURLException { diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index ac12cde39125f3b9dc57f251dd124739422426f9..92a1a5cfc9f0ba2f2af7773e98794ec0e5db81f1 100644 +index ac12cde39125f3b9dc57f251dd124739422426f9..c75082dcf1eb2c4820d47886b8d9aada854dc9b5 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -15,7 +15,6 @@ import java.net.SocketAddress; @@ -1590,7 +1954,7 @@ index ac12cde39125f3b9dc57f251dd124739422426f9..92a1a5cfc9f0ba2f2af7773e98794ec0 // Gale start - MultiPaper - do not place player in world if kicked before being spawned in if (!connection.isConnected() || player.quitReason != null) { - pendingPlayers.remove(player.getUUID(), player); -+ /*pendingPlayers.remove(player.getUUID(), player);*/ // Gale - base thread pools - this patch was removed from Paper but might be useful later ++ /*pendingPlayers.remove(player.getUUID(), player);*/ // Gale - base thread pool - this patch was removed from Paper but might be useful later return; } // Gale end - MultiPaper - do not place player in world if kicked before being spawned in @@ -1598,7 +1962,7 @@ index ac12cde39125f3b9dc57f251dd124739422426f9..92a1a5cfc9f0ba2f2af7773e98794ec0 player.getRecipeBook().sendInitialRecipeBook(player); this.updateEntireScoreboard(worldserver1.getScoreboard(), player); this.server.invalidateStatus(); -+/* // Gale - base thread pools - this patch was removed from Paper but might be useful later ++/* // Gale - base thread pool - this patch was removed from Paper but might be useful later + // Paper start - async load spawn in chunk + ServerLevel finalWorldserver = worldserver1; + finalWorldserver.pendingLogin.add(player); @@ -1611,7 +1975,7 @@ index ac12cde39125f3b9dc57f251dd124739422426f9..92a1a5cfc9f0ba2f2af7773e98794ec0 + worldserver1, chunkX, chunkZ, net.minecraft.server.level.ChunkHolder.FullChunkStatus.ENTITY_TICKING, true, + ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHEST, + (chunk) -> { -+ ScheduledServerThreadTaskQueues.add(() -> { // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> { // Gale - base thread pool + try { + if (!playerconnection.connection.isConnected()) { + return; @@ -1624,7 +1988,7 @@ index ac12cde39125f3b9dc57f251dd124739422426f9..92a1a5cfc9f0ba2f2af7773e98794ec0 + } finally { + finalWorldserver.pendingLogin.remove(player); + } -+ }, ScheduledServerThreadTaskQueues.POST_CHUNK_LOAD_JOIN_TASK_MAX_DELAY); // Gale - base thread pools ++ }, ScheduledServerThreadTaskQueues.POST_CHUNK_LOAD_JOIN_TASK_MAX_DELAY); // Gale - base thread pool + } + ); + } @@ -1649,7 +2013,7 @@ index ac12cde39125f3b9dc57f251dd124739422426f9..92a1a5cfc9f0ba2f2af7773e98794ec0 + } + player.didPlayerJoinEvent = true; + // Paper end -+*/ // Gale - base thread pools - this patch was removed from Paper but might be useful later ++*/ // Gale - base thread pool - this patch was removed from Paper but might be useful later MutableComponent ichatmutablecomponent; if (player.getGameProfile().getName().equalsIgnoreCase(s)) { @@ -1661,7 +2025,7 @@ index ac12cde39125f3b9dc57f251dd124739422426f9..92a1a5cfc9f0ba2f2af7773e98794ec0 - while (iterator.hasNext()) { - ServerLevel worldserver = (ServerLevel) iterator.next(); -+ for (ServerLevel worldserver : this.server.getAllLevelsArray()) { // Gale - base thread pools - optimize server levels ++ for (ServerLevel worldserver : this.server.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels if (worldserver != null) { worldserver.getChunkSource().setViewDistance(viewDistance); @@ -1673,12 +2037,12 @@ index ac12cde39125f3b9dc57f251dd124739422426f9..92a1a5cfc9f0ba2f2af7773e98794ec0 - while (iterator.hasNext()) { - ServerLevel worldserver = (ServerLevel) iterator.next(); -+ for (ServerLevel worldserver : this.server.getAllLevelsArray()) { // Gale - base thread pools - optimize server levels ++ for (ServerLevel worldserver : this.server.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels if (worldserver != null) { worldserver.getChunkSource().setSimulationDistance(simulationDistance); diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java -index 83701fbfaa56a232593ee8f11a3afb8941238bfa..321be4cfea7228f5f5131eb521daa67590f00078 100644 +index 83701fbfaa56a232593ee8f11a3afb8941238bfa..392e7b4a89669f16b32043b65b69e6593d17f10e 100644 --- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java @@ -6,17 +6,18 @@ import com.mojang.logging.LogUtils; @@ -1698,7 +2062,7 @@ index 83701fbfaa56a232593ee8f11a3afb8941238bfa..321be4cfea7228f5f5131eb521daa675 import org.slf4j.Logger; -public abstract class BlockableEventLoop implements ProfilerMeasured, ProcessorHandle, Executor { -+public abstract class BlockableEventLoop implements ProfilerMeasured, ProcessorHandle, AbstractBlockableEventLoop { // Gale - base thread pools ++public abstract class BlockableEventLoop implements ProfilerMeasured, ProcessorHandle, AbstractBlockableEventLoop { // Gale - base thread pool private final String name; private static final Logger LOGGER = LogUtils.getLogger(); private final Queue pendingRunnables = Queues.newConcurrentLinkedQueue(); @@ -1706,7 +2070,7 @@ index 83701fbfaa56a232593ee8f11a3afb8941238bfa..321be4cfea7228f5f5131eb521daa675 protected abstract boolean shouldRun(R task); -+ @Override // Gale - base thread pools ++ @Override // Gale - base thread pool public boolean isSameThread() { return Thread.currentThread() == this.getRunningThread(); } @@ -1714,11 +2078,11 @@ index 83701fbfaa56a232593ee8f11a3afb8941238bfa..321be4cfea7228f5f5131eb521daa675 return this.pendingRunnables.size(); } -+ // Gale start - base thread pools ++ // Gale start - base thread pool + public boolean hasPendingTasks() { + return !this.pendingRunnables.isEmpty(); + } -+ // Gale end - base thread pools ++ // Gale end - base thread pool + @Override public String name() { @@ -1727,12 +2091,12 @@ index 83701fbfaa56a232593ee8f11a3afb8941238bfa..321be4cfea7228f5f5131eb521daa675 } -+ @Override // Gale - base thread pools ++ @Override // Gale - base thread pool public void executeIfPossible(Runnable runnable) { this.execute(runnable); } diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index 9948cc4c65d5681c171b38cdf7cf3e63a01e4364..c37793871951b0044168610bc05ee0529f3c4611 100644 +index 9948cc4c65d5681c171b38cdf7cf3e63a01e4364..cba854bdcab80ba411096ef4fd97e46861764d48 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java @@ -98,7 +98,7 @@ public abstract class Projectile extends Entity { @@ -1740,12 +2104,12 @@ index 9948cc4c65d5681c171b38cdf7cf3e63a01e4364..c37793871951b0044168610bc05ee052 // Paper start - check all worlds if (this.cachedOwner == null) { - for (final ServerLevel level : this.level.getServer().getAllLevels()) { -+ for (final ServerLevel level : this.level.getServer().getAllLevelsArray()) { // Gale - base thread pools - optimize server levels ++ for (final ServerLevel level : this.level.getServer().getAllLevelsArray()) { // Gale - base thread pool - optimize server levels if (level == this.level) continue; final Entity entity = level.getEntity(this.ownerUUID); if (entity != null) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index e23fdd5ba09b50b7eef0ca4f36c5480779fba624..79f3a6174873834de61d7dc9fdbf6eb5a0fd6cd9 100644 +index e23fdd5ba09b50b7eef0ca4f36c5480779fba624..a7bb3275b2da8308696b18fb527514f9c4859d35 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -986,7 +986,7 @@ public final class CraftServer implements Server { @@ -1753,7 +2117,7 @@ index e23fdd5ba09b50b7eef0ca4f36c5480779fba624..79f3a6174873834de61d7dc9fdbf6eb5 this.console.paperConfigurations.reloadConfigs(this.console); this.console.galeConfigurations.reloadConfigs(this.console); // Gale - Gale configuration - for (ServerLevel world : this.console.getAllLevels()) { -+ for (ServerLevel world : this.console.getAllLevelsArray()) { // Gale - base thread pools - optimize server levels ++ for (ServerLevel world : this.console.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) @@ -1762,7 +2126,7 @@ index e23fdd5ba09b50b7eef0ca4f36c5480779fba624..79f3a6174873834de61d7dc9fdbf6eb5 @Override public World createWorld(WorldCreator creator) { - Preconditions.checkState(this.console.getAllLevels().iterator().hasNext(), "Cannot create additional worlds on STARTUP"); -+ Preconditions.checkState(this.console.getAllLevelsArray().length > 0, "Cannot create additional worlds on STARTUP"); // Gale - base thread pools - optimize server levels ++ Preconditions.checkState(this.console.getAllLevelsArray().length > 0, "Cannot create additional worlds on STARTUP"); // Gale - base thread pool - optimize server levels //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot create a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. Validate.notNull(creator, "Creator may not be null"); @@ -1771,12 +2135,12 @@ index e23fdd5ba09b50b7eef0ca4f36c5480779fba624..79f3a6174873834de61d7dc9fdbf6eb5 Validate.notNull(uuid, "UUID cannot be null"); - for (ServerLevel world : this.getServer().getAllLevels()) { -+ for (ServerLevel world : this.getServer().getAllLevelsArray()) { // Gale - base thread pools - optimize server levels ++ for (ServerLevel world : this.getServer().getAllLevelsArray()) { // Gale - base thread pool - optimize server levels net.minecraft.world.entity.Entity entity = world.getEntity(uuid); if (entity != null) { return entity.getBukkitEntity(); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index f8d321e925bf2708e51590542325c1bdc67d5964..a190bb9ce7b3701963f315452359f6f9c3aae329 100644 +index f8d321e925bf2708e51590542325c1bdc67d5964..76bd4095a051fb78e71f51aadb0376483c3fa053 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -5,7 +5,6 @@ import com.google.common.base.Predicates; @@ -1816,17 +2180,17 @@ index f8d321e925bf2708e51590542325c1bdc67d5964..a190bb9ce7b3701963f315452359f6f9 io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> { - net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { -+ ScheduledServerThreadTaskQueues.add(() -> { // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> { // Gale - base thread pool net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c; if (chunk != null) addTicket(x, z); // Paper ret.complete(chunk == null ? null : chunk.getBukkitChunk()); - }); -+ }, ScheduledServerThreadTaskQueues.COMPLETE_CHUNK_FUTURE_TASK_MAX_DELAY); // Gale - base thread pools ++ }, ScheduledServerThreadTaskQueues.COMPLETE_CHUNK_FUTURE_TASK_MAX_DELAY); // Gale - base thread pool }); return ret; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 78f53ee557276de85f0431ebcb146445b1f4fb92..c8b0a191832523e6c2e0fe4fd6cb1b8fa5104f86 100644 +index 78f53ee557276de85f0431ebcb146445b1f4fb92..6176867eea06c53882dcaacfbde0334b39b903cc 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -190,6 +190,7 @@ import org.bukkit.plugin.Plugin; @@ -1842,7 +2206,7 @@ index 78f53ee557276de85f0431ebcb146445b1f4fb92..c8b0a191832523e6c2e0fe4fd6cb1b8f chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId()); } - net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { -+ ScheduledServerThreadTaskQueues.add(() -> { // Gale - base thread pools ++ ScheduledServerThreadTaskQueues.add(() -> { // Gale - base thread pool try { ret.complete(CraftEntity.this.teleport(locationClone, cause) ? Boolean.TRUE : Boolean.FALSE); } catch (Throwable throwable) { @@ -1851,23 +2215,10 @@ index 78f53ee557276de85f0431ebcb146445b1f4fb92..c8b0a191832523e6c2e0fe4fd6cb1b8f ret.completeExceptionally(throwable); } - }); -+ }, ScheduledServerThreadTaskQueues.TELEPORT_ASYNC_TASK_MAX_DELAY); // Gale - base thread pools ++ }, ScheduledServerThreadTaskQueues.TELEPORT_ASYNC_TASK_MAX_DELAY); // Gale - base thread pool }); return ret; -diff --git a/src/main/java/org/galemc/gale/concurrent/LockAndCondition.java b/src/main/java/org/galemc/gale/concurrent/LockAndCondition.java -index 73fd8ca0bd1168862a03d9bdcae93d62895e8c1f..6985e86495fc7046cbe1623d90f72adc95d909e2 100644 ---- a/src/main/java/org/galemc/gale/concurrent/LockAndCondition.java -+++ b/src/main/java/org/galemc/gale/concurrent/LockAndCondition.java -@@ -9,7 +9,7 @@ import java.util.concurrent.locks.Lock; - * A utility class that stores a {@link Condition} with its {@link Lock}, that can be passed around and used instead - * of using an {@link Object} monitor, which does not support speculative locking. - * -- * @author Martijn Muijsers -+ * @author Martijn Muijsers under AGPL-3.0 - */ - public class LockAndCondition { - diff --git a/src/main/java/org/galemc/gale/concurrent/Mutex.java b/src/main/java/org/galemc/gale/concurrent/Mutex.java index 65ec8cf910575dfa4c5024ec69b3be1ef2634722..174c248aa706f6b5f3e248cb7604b44a4d508967 100644 --- a/src/main/java/org/galemc/gale/concurrent/Mutex.java @@ -1895,7 +2246,7 @@ index 2e31501d26b141729c80975e97a23b09653ba3bf..5a454236073dd75ed36d058c0f033c4a @AnyThreadSafe @YieldFree diff --git a/src/main/java/org/galemc/gale/configuration/GaleConfigurations.java b/src/main/java/org/galemc/gale/configuration/GaleConfigurations.java -index 69acbab61a79c24312359a63086f9353d740113f..49ace73d901b6f55545bb21a93d026a04c5757ad 100644 +index 69acbab61a79c24312359a63086f9353d740113f..8832121c8ab79d7f006858f2abcd03b3b96e5589 100644 --- a/src/main/java/org/galemc/gale/configuration/GaleConfigurations.java +++ b/src/main/java/org/galemc/gale/configuration/GaleConfigurations.java @@ -267,7 +267,7 @@ public class GaleConfigurations extends Configurations + * This may or may not extend to conceptual access and/or modifications. + * + * @see #READ + * @see #WRITE + */ +- READ_WRITE; ++ READ_WRITE + + } +diff --git a/src/main/java/org/galemc/gale/executor/annotation/Guarded.java b/src/main/java/org/galemc/gale/executor/annotation/Guarded.java +index 84a0bac98a382550c826e6adbecec1fe7be974a1..6f1d1960953daf7f6f61643f5165e9a0760a647e 100644 +--- a/src/main/java/org/galemc/gale/executor/annotation/Guarded.java ++++ b/src/main/java/org/galemc/gale/executor/annotation/Guarded.java +@@ -24,7 +24,7 @@ import java.lang.annotation.Target; + * @author Martijn Muijsers under AGPL-3.0 + */ + @Documented +-@Repeatable ++@Repeatable(Guarded.Container.class) + @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) + public @interface Guarded { + +@@ -44,4 +44,12 @@ public @interface Guarded { + */ + String except() default ""; + ++ @Documented ++ @Target(ElementType.ANNOTATION_TYPE) ++ @interface Container { ++ ++ Guarded[] value(); ++ ++ } ++ + } diff --git a/src/main/java/org/galemc/gale/executor/annotation/PotentiallyBlocking.java b/src/main/java/org/galemc/gale/executor/annotation/PotentiallyBlocking.java index 71f26852c96dea34ea07efe07f834f8262509957..d324c303245bcbedaaaab573803d73caff941901 100644 --- a/src/main/java/org/galemc/gale/executor/annotation/PotentiallyBlocking.java @@ -2416,18 +2881,19 @@ index e87ee2612348fc559b21256cc7cadfc684f01f8e..7ff4e4ab43d316e319efb33b2dd365d6 * * @author Martijn Muijsers under AGPL-3.0 */ -diff --git a/src/main/java/org/galemc/gale/executor/annotation/thread/AsyncThreadOnly.java b/src/main/java/org/galemc/gale/executor/annotation/thread/AsyncThreadOnly.java +diff --git a/src/main/java/org/galemc/gale/executor/annotation/thread/AssistThreadOnly.java b/src/main/java/org/galemc/gale/executor/annotation/thread/AssistThreadOnly.java new file mode 100644 -index 0000000000000000000000000000000000000000..604ece0c20e986afdf6958ba969052b2c69762b7 +index 0000000000000000000000000000000000000000..203799c5a9ddec3e665a7476f5e48a2c0f457b04 --- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/annotation/thread/AsyncThreadOnly.java -@@ -0,0 +1,36 @@ ++++ b/src/main/java/org/galemc/gale/executor/annotation/thread/AssistThreadOnly.java +@@ -0,0 +1,37 @@ +// Gale - thread-safety annotations + +package org.galemc.gale.executor.annotation.thread; + +import org.galemc.gale.executor.annotation.Access; +import org.galemc.gale.executor.annotation.PotentiallyBlocking; ++import org.galemc.gale.executor.thread.AssistThread; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; @@ -2435,22 +2901,22 @@ index 0000000000000000000000000000000000000000..604ece0c20e986afdf6958ba969052b2 + +/** + * An annotation primarily for methods, identifying methods that can only be called on a thread that is an instance -+ * of {@link AsyncThread}. ++ * of {@link AssistThread}. + *
+ * This annotation can also be used on fields or classes, similar to {@link ThreadRestricted}. + *
-+ * In a method annotated with {@link AsyncThreadOnly}, fields and methods annotated with -+ * {@link AsyncThreadOnly}, {@link BaseYieldingThreadOnly}, {@link YieldingThreadOnly} -+ * or {@link AnyThreadSafe} may be used. ++ * In a method annotated with {@link AssistThreadOnly}, fields and methods annotated with ++ * {@link AssistThreadOnly}, {@link BaseThreadOnly} or {@link AnyThreadSafe} may be used. + *
-+ * Methods that are annotated with {@link AsyncThreadOnly} must never call methods that are annotated with ++ * 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 AsyncThreadOnly { ++public @interface AssistThreadOnly { + + /** + * @see ThreadRestricted#fieldAccess() @@ -2458,11 +2924,11 @@ index 0000000000000000000000000000000000000000..604ece0c20e986afdf6958ba969052b2 + Access value() default Access.READ_WRITE; + +} -diff --git a/src/main/java/org/galemc/gale/executor/annotation/thread/BaseYieldingThreadOnly.java b/src/main/java/org/galemc/gale/executor/annotation/thread/BaseYieldingThreadOnly.java +diff --git a/src/main/java/org/galemc/gale/executor/annotation/thread/BaseThreadOnly.java b/src/main/java/org/galemc/gale/executor/annotation/thread/BaseThreadOnly.java new file mode 100644 -index 0000000000000000000000000000000000000000..2b20fd3fd5e9d73049bcc6bf0cbca0eb7a77630b +index 0000000000000000000000000000000000000000..e682953181dbf208e731ab5b081664a129210310 --- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/annotation/thread/BaseYieldingThreadOnly.java ++++ b/src/main/java/org/galemc/gale/executor/annotation/thread/BaseThreadOnly.java @@ -0,0 +1,36 @@ +// Gale - thread-safety annotations + @@ -2470,7 +2936,7 @@ index 0000000000000000000000000000000000000000..2b20fd3fd5e9d73049bcc6bf0cbca0eb + +import org.galemc.gale.executor.annotation.Access; +import org.galemc.gale.executor.annotation.PotentiallyBlocking; -+import org.galemc.gale.executor.thread.BaseYieldingThread; ++import org.galemc.gale.executor.thread.BaseThread; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; @@ -2478,21 +2944,21 @@ index 0000000000000000000000000000000000000000..2b20fd3fd5e9d73049bcc6bf0cbca0eb + +/** + * An annotation primarily for methods, identifying methods that can only be called on a thread that is an instance -+ * of {@link BaseYieldingThread}. ++ * of {@link BaseThread}. + *
+ * This annotation can also be used on fields or classes, similar to {@link ThreadRestricted}. + *
-+ * In a method annotated with {@link BaseYieldingThreadOnly}, fields and methods annotated with -+ * {@link BaseYieldingThreadOnly}, {@link YieldingThreadOnly} or {@link AnyThreadSafe} may be used. ++ * In a method annotated with {@link BaseThreadOnly}, fields and methods annotated with ++ * {@link BaseThreadOnly} or {@link AnyThreadSafe} may be used. + *
-+ * Methods that are annotated with {@link BaseYieldingThreadOnly} must never call methods that are annotated with ++ * Methods that are annotated with {@link BaseThreadOnly} must never call methods that are annotated with + * {@link PotentiallyBlocking}. + * + * @author Martijn Muijsers under AGPL-3.0 + */ +@Documented +@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) -+public @interface BaseYieldingThreadOnly { ++public @interface BaseThreadOnly { + + /** + * @see ThreadRestricted#fieldAccess() @@ -2502,10 +2968,10 @@ index 0000000000000000000000000000000000000000..2b20fd3fd5e9d73049bcc6bf0cbca0eb +} diff --git a/src/main/java/org/galemc/gale/executor/annotation/thread/OriginalServerThreadOnly.java b/src/main/java/org/galemc/gale/executor/annotation/thread/OriginalServerThreadOnly.java new file mode 100644 -index 0000000000000000000000000000000000000000..13bd7088eb33cfb8e839debcb77f3a26c2d2a441 +index 0000000000000000000000000000000000000000..d6476e007de11fb4e556cee6ec6eea107dc814fa --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/annotation/thread/OriginalServerThreadOnly.java -@@ -0,0 +1,35 @@ +@@ -0,0 +1,39 @@ +// Gale - thread-safety annotations + +package org.galemc.gale.executor.annotation.thread; @@ -2524,6 +2990,10 @@ index 0000000000000000000000000000000000000000..13bd7088eb33cfb8e839debcb77f3a26 + *
+ * This annotation can also be used on fields, similar to {@link ThreadRestricted}. + *
++ * In a method annotated with {@link OriginalServerThreadOnly}, fields and methods annotated with ++ * {@link OriginalServerThreadOnly}, {@link ServerThreadOnly}, {@link BaseThreadOnly} ++ * or {@link AnyThreadSafe} may be used. ++ *
+ * Methods that are annotated with {@link OriginalServerThreadOnly} must never call methods that are annotated with + * {@link PotentiallyBlocking}. + * @@ -2543,17 +3013,17 @@ index 0000000000000000000000000000000000000000..13bd7088eb33cfb8e839debcb77f3a26 +} diff --git a/src/main/java/org/galemc/gale/executor/annotation/thread/ServerThreadOnly.java b/src/main/java/org/galemc/gale/executor/annotation/thread/ServerThreadOnly.java new file mode 100644 -index 0000000000000000000000000000000000000000..efd540e2a40d79f70e7ac6a709bb10f0138ed7a8 +index 0000000000000000000000000000000000000000..d27ee27a65635d0136c5c9e33925b64036ae5cd3 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/annotation/thread/ServerThreadOnly.java -@@ -0,0 +1,38 @@ +@@ -0,0 +1,37 @@ +// Gale - thread-safety annotations + +package org.galemc.gale.executor.annotation.thread; + +import org.galemc.gale.executor.annotation.Access; +import org.galemc.gale.executor.annotation.PotentiallyBlocking; -+import org.galemc.gale.executor.thread.pooled.ServerThread; ++import org.galemc.gale.executor.thread.ServerThread; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; @@ -2565,8 +3035,7 @@ index 0000000000000000000000000000000000000000..efd540e2a40d79f70e7ac6a709bb10f0 + * This annotation can also be used on fields or classes, similar to {@link ThreadRestricted}. + *
+ * In a method annotated with {@link ServerThreadOnly}, fields and methods annotated with -+ * {@link ServerThreadOnly}, {@link BaseYieldingThreadOnly}, {@link YieldingThreadOnly} -+ * or {@link AnyThreadSafe} may be used. ++ * {@link ServerThreadOnly}, {@link BaseThreadOnly} or {@link AnyThreadSafe} may be used. + *
+ * Methods that are annotated with {@link ServerThreadOnly} must never call methods that are annotated with + * {@link PotentiallyBlocking}. @@ -2585,146 +3054,84 @@ index 0000000000000000000000000000000000000000..efd540e2a40d79f70e7ac6a709bb10f0 + Access value() default Access.READ_WRITE; + +} -diff --git a/src/main/java/org/galemc/gale/executor/annotation/thread/TickAssistThreadOnly.java b/src/main/java/org/galemc/gale/executor/annotation/thread/TickAssistThreadOnly.java +diff --git a/src/main/java/org/galemc/gale/executor/lock/CheckableLock.java b/src/main/java/org/galemc/gale/executor/lock/CheckableLock.java new file mode 100644 -index 0000000000000000000000000000000000000000..b86526dbdd531827fc4064b22c3281b1215aa188 +index 0000000000000000000000000000000000000000..995746e7f89481f885ea6e3965fc9a2f3f9e9498 --- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/annotation/thread/TickAssistThreadOnly.java -@@ -0,0 +1,37 @@ -+// Gale - thread-safety annotations -+ -+package org.galemc.gale.executor.annotation.thread; -+ -+import org.galemc.gale.executor.annotation.Access; -+import org.galemc.gale.executor.annotation.PotentiallyBlocking; -+import org.galemc.gale.executor.thread.pooled.TickAssistThread; -+ -+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 TickAssistThread}. -+ *
-+ * This annotation can also be used on fields or classes, similar to {@link ThreadRestricted}. -+ *
-+ * In a method annotated with {@link TickAssistThreadOnly}, fields and methods annotated with -+ * {@link TickAssistThreadOnly}, {@link BaseYieldingThreadOnly}, {@link YieldingThreadOnly} -+ * or {@link AnyThreadSafe} may be used. -+ *
-+ * Methods that are annotated with {@link TickAssistThreadOnly} must never call methods that are annotated with -+ * {@link PotentiallyBlocking}. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+@Documented -+@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) -+public @interface TickAssistThreadOnly { -+ -+ /** -+ * @see ThreadRestricted#fieldAccess() -+ */ -+ Access value() default Access.READ_WRITE; -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/annotation/thread/YieldingThreadOnly.java b/src/main/java/org/galemc/gale/executor/annotation/thread/YieldingThreadOnly.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e37018a92d8854674b12172af33073e39982b3a6 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/annotation/thread/YieldingThreadOnly.java -@@ -0,0 +1,36 @@ -+// Gale - thread-safety annotations -+ -+package org.galemc.gale.executor.annotation.thread; -+ -+import org.galemc.gale.executor.annotation.Access; -+import org.galemc.gale.executor.annotation.PotentiallyBlocking; -+import org.galemc.gale.executor.thread.AbstractYieldingThread; -+ -+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 AbstractYieldingThread}. -+ *
-+ * This annotation can also be used on fields or classes, similar to {@link ThreadRestricted}. -+ *
-+ * In a method annotated with {@link YieldingThreadOnly}, fields and methods annotated with -+ * {@link YieldingThreadOnly} or {@link AnyThreadSafe} may be used. -+ *
-+ * Methods that are annotated with {@link YieldingThreadOnly} must never call methods that are annotated with -+ * {@link PotentiallyBlocking}. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+@Documented -+@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) -+public @interface YieldingThreadOnly { -+ -+ /** -+ * @see ThreadRestricted#fieldAccess() -+ */ -+ Access value() default Access.READ_WRITE; -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/lock/MultipleWaitingBaseThreadsYieldingLock.java b/src/main/java/org/galemc/gale/executor/lock/MultipleWaitingBaseThreadsYieldingLock.java -new file mode 100644 -index 0000000000000000000000000000000000000000..377e41518a336d4efaf33e3e4257229225761627 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/lock/MultipleWaitingBaseThreadsYieldingLock.java -@@ -0,0 +1,42 @@ -+// Gale - base thread pools -+ ++++ b/src/main/java/org/galemc/gale/executor/lock/CheckableLock.java +@@ -0,0 +1,20 @@ +package org.galemc.gale.executor.lock; + -+import org.galemc.gale.executor.thread.BaseYieldingThread; -+import org.galemc.gale.executor.thread.wait.SignalReason; -+import org.galemc.gale.executor.thread.wait.WaitingBaseThreadSet; -+import org.galemc.gale.executor.thread.wait.WaitingThreadSet; ++import org.galemc.gale.executor.annotation.YieldFree; + +import java.util.concurrent.locks.Lock; + +/** -+ * A {@link YieldingLock} for which multiple {@link BaseYieldingThread}s may be waiting at the same time. ++ * A {@link Lock} that also provides an {@link #isLocked()} method. + * + * @author Martijn Muijsers under AGPL-3.0 + */ ++public interface CheckableLock extends Lock { ++ ++ /** ++ * @return Whether this lock is currently held. ++ */ ++ @YieldFree ++ boolean isLocked(); ++ ++} +diff --git a/src/main/java/org/galemc/gale/executor/lock/MultipleWaitingBaseThreadsYieldingLock.java b/src/main/java/org/galemc/gale/executor/lock/MultipleWaitingBaseThreadsYieldingLock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a248a9ea644a8bb4175da2e1903483ab6866bc48 +--- /dev/null ++++ b/src/main/java/org/galemc/gale/executor/lock/MultipleWaitingBaseThreadsYieldingLock.java +@@ -0,0 +1,39 @@ ++// Gale - base thread pool ++ ++package org.galemc.gale.executor.lock; ++ ++import org.galemc.gale.executor.thread.BaseThread; ++ ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.locks.Lock; ++ ++/** ++ * A {@link YieldingLock} for which multiple {@link BaseThread}s may be waiting at the same time. ++ * ++ * @author Martijn Muijsers under AGPL-3.0 ++ */ ++@SuppressWarnings("unused") +public class MultipleWaitingBaseThreadsYieldingLock extends YieldingLock { + -+ private final WaitingThreadSet waitingThreads = new WaitingBaseThreadSet(); -+ -+ private final SignalReason signalReason = SignalReason.createForWaitingThreadSet(waitingThreads); ++ private final AtomicInteger waitingThreadCount = new AtomicInteger(); + + public MultipleWaitingBaseThreadsYieldingLock(Lock innerLock) { + super(innerLock); + } + + @Override -+ public void addWaitingThread(Thread thread) { -+ this.waitingThreads.add(thread); ++ public void incrementWaitingThreads() { ++ this.waitingThreadCount.incrementAndGet(); + } + + @Override -+ public void removeWaitingThread(Thread thread) { -+ this.waitingThreads.remove(thread); ++ public void decrementWaitingThreads() { ++ this.waitingThreadCount.decrementAndGet(); + } + + @Override -+ public SignalReason getSignalReason() { -+ return this.signalReason; ++ protected boolean hasWaitingThreads() { ++ return this.waitingThreadCount.get() > 0; + } + +} diff --git a/src/main/java/org/galemc/gale/executor/lock/YieldingLock.java b/src/main/java/org/galemc/gale/executor/lock/YieldingLock.java new file mode 100644 -index 0000000000000000000000000000000000000000..1977fb5fb3403a8ecd8e1396bcd1244eb27e78f8 +index 0000000000000000000000000000000000000000..ad3a7e94824c32c812e6ca57cc6cea78227eac5f --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/lock/YieldingLock.java -@@ -0,0 +1,122 @@ -+// Gale - base thread pools +@@ -0,0 +1,152 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.lock; + @@ -2732,12 +3139,14 @@ index 0000000000000000000000000000000000000000..1977fb5fb3403a8ecd8e1396bcd1244e +import org.galemc.gale.executor.annotation.PotentiallyYielding; +import org.galemc.gale.executor.annotation.YieldFree; +import org.galemc.gale.executor.thread.AbstractYieldingThread; -+import org.galemc.gale.executor.thread.wait.SignalReason; ++import org.galemc.gale.executor.thread.pool.BaseThreadActivation; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; ++import java.util.concurrent.locks.ReentrantLock; + +/** + * A wrapper for a lock that can be acquired, but if not able to be acquired right away, can cause the current thread @@ -2752,12 +3161,31 @@ index 0000000000000000000000000000000000000000..1977fb5fb3403a8ecd8e1396bcd1244e + * @author Martijn Muijsers under AGPL-3.0 + */ +@AnyThreadSafe -+public abstract class YieldingLock implements Lock { ++public abstract class YieldingLock implements CheckableLock { + + private final Lock innerLock; + ++ /** ++ * The same value as {@link #lock}, or null if it is not an instance of {@link CheckableLock}. ++ */ ++ private final @Nullable CheckableLock innerCheckableLock; ++ ++ /** ++ * The same value as {@link #lock}, or null if it is not an instance of {@link ReentrantLock}. ++ */ ++ private final @Nullable ReentrantLock innerReentrantLock; ++ + public YieldingLock(Lock innerLock) { + this.innerLock = innerLock; ++ if (innerLock instanceof CheckableLock checkableLock) { ++ this.innerCheckableLock = checkableLock; ++ this.innerReentrantLock = null; ++ } else if (innerLock instanceof ReentrantLock reentrantLock) { ++ this.innerCheckableLock = null; ++ this.innerReentrantLock = reentrantLock; ++ } else { ++ throw new IllegalArgumentException("The innerLock passed to the YieldingLock() constructor must be an instance of CheckableLock or ReentrantLock"); ++ } + } + + /** @@ -2801,19 +3229,23 @@ index 0000000000000000000000000000000000000000..1977fb5fb3403a8ecd8e1396bcd1244e + @Override + public void unlock() { + this.innerLock.unlock(); -+ // Signal the first waiting thread, if any. ++ // Potentially signal a thread that this lock has become available. + // Another thread could also acquire the lock at this moment, so when we signal the thread we obtain below, + // it may already be too late for the polled thread to acquire this lock + // (but note that the same thread cannot have been added again because only the thread itself can do that - + // and it is still waiting). -+ this.getSignalReason().signalAnother(); ++ if (this.hasWaitingThreads()) { ++ BaseThreadActivation.yieldingLockWithWaitingThreadsWasUnlocked(); ++ } + } + ++ @SuppressWarnings("RedundantThrows") + @Override + public boolean tryLock(long l, @NotNull TimeUnit timeUnit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + ++ @SuppressWarnings("RedundantThrows") + @Override + public void lockInterruptibly() throws InterruptedException { + throw new UnsupportedOperationException(); @@ -2826,45 +3258,53 @@ index 0000000000000000000000000000000000000000..1977fb5fb3403a8ecd8e1396bcd1244e + return this.innerLock.newCondition(); + } + -+ /** -+ * Adds a thread to the set of threads waiting for this lock to be released. -+ * -+ * @param thread The thread to register as waiting for this lock. -+ */ -+ @YieldFree -+ public abstract void addWaitingThread(Thread thread); ++ @Override ++ public boolean isLocked() { ++ //noinspection DataFlowIssue ++ return this.innerCheckableLock != null ? this.innerCheckableLock.isLocked() : this.innerReentrantLock.isLocked(); ++ } + + /** -+ * Removes a thread from the set of threads waiting for this lock to be released. -+ * -+ * @param thread The thread to unregister as waiting for this lock. ++ * Increments the number of threads waiting for this lock to be released. + */ + @YieldFree -+ public abstract void removeWaitingThread(Thread thread); ++ public abstract void incrementWaitingThreads(); + ++ /** ++ * Decrements the number of threads waiting for this lock to be released. ++ */ + @YieldFree -+ public abstract SignalReason getSignalReason(); ++ public abstract void decrementWaitingThreads(); ++ ++ /** ++ * @return Whether this lock has any threads waiting for it. ++ */ ++ @YieldFree ++ protected abstract boolean hasWaitingThreads(); + +} diff --git a/src/main/java/org/galemc/gale/executor/queue/AbstractTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/AbstractTaskQueue.java new file mode 100644 -index 0000000000000000000000000000000000000000..b38bf6c1da417f00a3c8e2ebb0d2a6df13696921 +index 0000000000000000000000000000000000000000..552e82a33c59261b06911b479400a7b11965bbd6 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/AbstractTaskQueue.java -@@ -0,0 +1,104 @@ -+// Gale - base thread pools +@@ -0,0 +1,93 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.queue; + ++import org.galemc.gale.executor.TaskSpan; +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; +import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.annotation.thread.BaseYieldingThreadOnly; -+import org.galemc.gale.executor.thread.BaseYieldingThread; -+import org.galemc.gale.executor.thread.pooled.AbstractYieldingSignallableThreadPool; ++import org.galemc.gale.executor.annotation.thread.BaseThreadOnly; ++import org.galemc.gale.executor.thread.BaseThread; ++import org.galemc.gale.executor.thread.ServerThread; +import org.jetbrains.annotations.Nullable; + +/** -+ * An interface for a task queue that may contain tasks that are potentially yielding and tasks that are yield-free. ++ * An interface for a task queue that may contain tasks of specific {@link TaskSpan}s. ++ *
++ * All tasks must be non-blocking. + * + * @author Martijn Muijsers under AGPL-3.0 + */ @@ -2878,56 +3318,56 @@ index 0000000000000000000000000000000000000000..b38bf6c1da417f00a3c8e2ebb0d2a6df + String getName(); + + /** -+ * @return Whether this queue has potentially yielding tasks that could start right now. -+ * -+ * @see #hasTasksThatCanStartNow -+ */ -+ boolean hasYieldingTasksThatCanStartNow(); -+ -+ /** -+ * @return Whether this queue has yield-free tasks that could start right now. -+ * -+ * @see #hasTasksThatCanStartNow -+ */ -+ boolean hasFreeTasksThatCanStartNow(); -+ -+ /** -+ * @return Whether this queue has tasks that could start right now. An example of -+ * tasks that could not start right now are tasks in {@link ScheduledServerThreadTaskQueues} that are scheduled for -+ * a later tick, while we are already out of spare time this tick. -+ */ -+ @AnyThreadSafe -+ default boolean hasTasksThatCanStartNow(BaseYieldingThread thread) { -+ return (!thread.isRestrictedDueToYieldDepth && this.hasYieldingTasksThatCanStartNow()) || this.hasFreeTasksThatCanStartNow(); -+ } -+ -+ /** + * @return Whether this queue has any tasks at all. + */ + boolean hasTasks(); + + /** ++ * @return Whether this queue has any tasks with the given {@link TaskSpan}. ++ */ ++ boolean hasTasks(TaskSpan span); ++ ++ /** ++ * @return Whether this queue supports tasks of the given {@link TaskSpan} at all. ++ */ ++ boolean canHaveTasks(TaskSpan span); ++ ++ /** + * Attempts to poll a task. ++ *
++ * This must not be called on queues in the {@link BaseTaskQueueTier#SERVER} tier by threads that are not an ++ * instance of {@link ServerThread}. ++ *
++ * This must not be called on queues apart from the {@link BaseTaskQueueTier#SERVER} tier by threads that are an ++ * instance of {@link ServerThread}. Use {@link #pollTiny(BaseThread)} instead. + * + * @param currentThread The current thread. + * @return The polled task, or null if this queue was empty. + */ -+ @BaseYieldingThreadOnly -+ @Nullable Runnable poll(BaseYieldingThread currentThread); ++ @BaseThreadOnly ++ @Nullable Runnable poll(BaseThread currentThread); ++ ++ /** ++ * Attempts to poll a {@link TaskSpan#TINY} task. ++ * ++ * @see #poll(BaseThread) ++ */ ++ @BaseThreadOnly ++ @Nullable Runnable pollTiny(BaseThread currentThread); + + /** + * Schedules a new task to this queue. + * + * @param task The task to schedule. -+ * @param yielding Whether the task is potentially yielding. ++ * @param span The {@link TaskSpan} of the task. + */ -+ void add(Runnable task, boolean yielding); ++ void add(Runnable task, TaskSpan span); + + /** -+ * Sets the thread pool of this queue. This ensures queues do not accidentally initialize the thread pools -+ * before they have finished initializing themselves. ++ * Sets the tier of this queue. This ensures queues do not accidentally initialize the tiers ++ * before the tiers have finished initializing themselves. + */ -+ void setThreadPool(AbstractYieldingSignallableThreadPool threadPool); ++ void setTier(BaseTaskQueueTier tier); + + /** + * @return Whether any of the given task queues is non-empty. @@ -2941,40 +3381,24 @@ index 0000000000000000000000000000000000000000..b38bf6c1da417f00a3c8e2ebb0d2a6df + return false; + } + -+ /** -+ * @return Whether any of the given task queues has a task that the given thread could start -+ * based on amongst others whether {@link BaseYieldingThread#isRestrictedDueToYieldDepth} is true, -+ * and potentially other external circumstances that may stop a queue from releasing some its held tasks. -+ */ -+ static boolean taskQueuesHaveTasksCouldStart(AbstractTaskQueue[] queues, BaseYieldingThread thread) { -+ for (AbstractTaskQueue queue : queues) { -+ if (queue.hasTasksThatCanStartNow(thread)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ +} diff --git a/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledChunkCacheTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledChunkCacheTaskQueue.java new file mode 100644 -index 0000000000000000000000000000000000000000..c6672e91706cdeded2f5f43c1b0c4d8dbe2df75e +index 0000000000000000000000000000000000000000..60a3b6e935fcea6cf27c31e2b967bf3758283274 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledChunkCacheTaskQueue.java -@@ -0,0 +1,54 @@ -+// Gale - base thread pools +@@ -0,0 +1,52 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.queue; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; ++import org.galemc.gale.executor.TaskSpan; +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; +import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.thread.pooled.AbstractYieldingSignallableThreadPool; -+import org.galemc.gale.executor.thread.pooled.ServerThread; -+import org.galemc.gale.executor.thread.pooled.ServerThreadPool; -+import org.galemc.gale.executor.thread.wait.SignalReason; ++import org.galemc.gale.executor.thread.ServerThread; +import org.jetbrains.annotations.Nullable; + +/** @@ -2993,7 +3417,7 @@ index 0000000000000000000000000000000000000000..c6672e91706cdeded2f5f43c1b0c4d8d +public final class AllLevelsScheduledChunkCacheTaskQueue extends AllLevelsScheduledTaskQueue { + + AllLevelsScheduledChunkCacheTaskQueue() { -+ super(); ++ super(TaskSpan.FREE, false); // TODO Gale could be TINY maybe? Should check the type of tasks scheduled + } + + @Override @@ -3018,21 +3442,21 @@ index 0000000000000000000000000000000000000000..c6672e91706cdeded2f5f43c1b0c4d8d +} diff --git a/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java new file mode 100644 -index 0000000000000000000000000000000000000000..158aa20f2840260306ecd4c48358544384fbf285 +index 0000000000000000000000000000000000000000..e5c89b7fa702d5c12b4aea0b7464ba8a78317a66 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java -@@ -0,0 +1,103 @@ -+// Gale - base thread pools +@@ -0,0 +1,128 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.queue; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; ++import org.galemc.gale.executor.TaskSpan; +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; +import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.thread.BaseYieldingThread; -+import org.galemc.gale.executor.thread.pooled.AbstractYieldingSignallableThreadPool; -+import org.galemc.gale.executor.thread.wait.SignalReason; ++import org.galemc.gale.executor.thread.BaseThread; ++import org.galemc.gale.executor.thread.pool.BaseThreadActivation; +import org.jetbrains.annotations.Nullable; + +/** @@ -3048,36 +3472,36 @@ index 0000000000000000000000000000000000000000..158aa20f2840260306ecd4c483585443 +public abstract class AllLevelsScheduledTaskQueue implements AbstractTaskQueue { + + /** -+ * Will be initialized in {@link #setThreadPool}. ++ * The {@link TaskSpan} of tasks in this queue. The span must be yield-free. + */ -+ public SignalReason signalReason; ++ public final TaskSpan span; ++ ++ /** ++ * Value of {@code onlyIfLastTimeIsTooLongAgo} in calls to ++ * {@link BaseThreadActivation#newTaskWasAdded(BaseTaskQueueTier, TaskSpan, boolean)}. ++ */ ++ private final boolean onlyNotifyBaseThreadPoolOfNewTasksIfLastTimeIsTooLongAgo; ++ ++ /** ++ * Will be initialized in {@link #setTier}. ++ */ ++ private BaseTaskQueueTier tier; + + /** + * An iteration index for iterating over the levels in {@link #poll}. + */ + private int levelIterationIndex; + -+ protected AllLevelsScheduledTaskQueue() {} ++ protected AllLevelsScheduledTaskQueue(TaskSpan span, boolean onlyNotifyBaseThreadPoolOfNewTasksIfLastTimeIsTooLongAgo) { ++ this.span = span; ++ this.onlyNotifyBaseThreadPoolOfNewTasksIfLastTimeIsTooLongAgo = onlyNotifyBaseThreadPoolOfNewTasksIfLastTimeIsTooLongAgo; ++ } + + protected abstract boolean hasLevelTasks(ServerLevel level); + + protected abstract @Nullable Runnable pollLevel(ServerLevel level); + + @Override -+ public boolean hasYieldingTasksThatCanStartNow() { -+ return false; -+ } -+ -+ @Override -+ public boolean hasFreeTasksThatCanStartNow() { -+ // Skip during server bootstrap or if there is no more time in the current spare time -+ if (MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking || !MinecraftServer.isConstructed) { -+ return false; -+ } -+ return this.hasTasks(); -+ } -+ -+ @Override + public boolean hasTasks() { + for (ServerLevel level : MinecraftServer.SERVER.getAllLevels()) { + if (this.hasLevelTasks(level)) { @@ -3088,7 +3512,17 @@ index 0000000000000000000000000000000000000000..158aa20f2840260306ecd4c483585443 + } + + @Override -+ public @Nullable Runnable poll(BaseYieldingThread currentThread) { ++ public boolean hasTasks(TaskSpan span) { ++ return span == this.span && this.hasTasks(); ++ } ++ ++ @Override ++ public boolean canHaveTasks(TaskSpan span) { ++ return span == this.span; ++ } ++ ++ @Override ++ public @Nullable Runnable poll(BaseThread currentThread) { + // Skip during server bootstrap or if there is no more time in the current spare time + if (MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking || !MinecraftServer.isConstructed) { + return null; @@ -3112,26 +3546,41 @@ index 0000000000000000000000000000000000000000..158aa20f2840260306ecd4c483585443 + } + + @Override -+ public void add(Runnable task, boolean yielding) { -+ throw new UnsupportedOperationException(); ++ public @Nullable Runnable pollTiny(BaseThread currentThread) { ++ if (this.span == TaskSpan.TINY) { ++ return this.poll(currentThread); ++ } ++ return null; + } + + @Override -+ public void setThreadPool(AbstractYieldingSignallableThreadPool threadPool) { -+ if (this.signalReason != null) { -+ throw new IllegalStateException(this.getClass().getSimpleName() + ".signalReason was already initialized"); ++ public void add(Runnable task, TaskSpan span) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ /** ++ * To be called when a new task has been added to the underlying storage of this queue. ++ */ ++ public void newTaskWasAdded() { ++ BaseThreadActivation.newTaskWasAdded(this.tier, this.span, onlyNotifyBaseThreadPoolOfNewTasksIfLastTimeIsTooLongAgo); ++ } ++ ++ @Override ++ public void setTier(BaseTaskQueueTier tier) { ++ if (this.tier != null) { ++ throw new IllegalStateException(this.getClass().getSimpleName() + ".tier was already initialized"); + } -+ this.signalReason = SignalReason.createForThreadPoolNewTasks(threadPool, false); ++ this.tier = tier; + } + +} diff --git a/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTickThreadChunkTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTickThreadChunkTaskQueue.java new file mode 100644 -index 0000000000000000000000000000000000000000..dd9f10bbcdfe9b2e5279fb34a913c967d7069fa7 +index 0000000000000000000000000000000000000000..117797c4ac2a81218e9f3e977467b62a18e8136f --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTickThreadChunkTaskQueue.java -@@ -0,0 +1,53 @@ -+// Gale - base thread pools +@@ -0,0 +1,54 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.queue; + @@ -3140,6 +3589,7 @@ index 0000000000000000000000000000000000000000..dd9f10bbcdfe9b2e5279fb34a913c967 +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; ++import org.galemc.gale.executor.TaskSpan; +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; +import org.galemc.gale.executor.annotation.YieldFree; +import org.jetbrains.annotations.Nullable; @@ -3161,7 +3611,7 @@ index 0000000000000000000000000000000000000000..dd9f10bbcdfe9b2e5279fb34a913c967 +public final class AllLevelsScheduledTickThreadChunkTaskQueue extends AllLevelsScheduledTaskQueue { + + AllLevelsScheduledTickThreadChunkTaskQueue() { -+ super(); ++ super(TaskSpan.FREE, true); // TODO Gale could be TINY maybe? But probably not? Should check the type of tasks scheduled + } + + @Override @@ -3186,11 +3636,11 @@ index 0000000000000000000000000000000000000000..dd9f10bbcdfe9b2e5279fb34a913c967 +} diff --git a/src/main/java/org/galemc/gale/executor/queue/AnyTickScheduledServerThreadTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/AnyTickScheduledServerThreadTaskQueue.java new file mode 100644 -index 0000000000000000000000000000000000000000..82f3ae8eea503fb51f5c7b0547e71234cc169a07 +index 0000000000000000000000000000000000000000..690979cb9b7ec3dedbd7d0c45d0c183a2f56d2ec --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/AnyTickScheduledServerThreadTaskQueue.java -@@ -0,0 +1,29 @@ -+// Gale - base thread pools +@@ -0,0 +1,27 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.queue; + @@ -3200,8 +3650,6 @@ index 0000000000000000000000000000000000000000..82f3ae8eea503fb51f5c7b0547e71234 +/** + * This class provides access to, but does not store, the tasks scheduled to be executed on the main thread, + * that must be finished by some time in the future, but not necessarily within the current tick or its spare time. -+ *
-+ * This queue does not support {@link #add}. + * + * @author Martijn Muijsers under AGPL-3.0 + */ @@ -3219,19 +3667,142 @@ index 0000000000000000000000000000000000000000..82f3ae8eea503fb51f5c7b0547e71234 + } + +} +diff --git a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bfcec658cbf381cc793d7dd844a81fac27c43337 +--- /dev/null ++++ b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java +@@ -0,0 +1,116 @@ ++// Gale - base thread pool ++ ++package org.galemc.gale.executor.queue; ++ ++import io.papermc.paper.util.TickThread; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.Entity; ++import org.galemc.gale.executor.thread.AssistThread; ++import org.galemc.gale.executor.thread.ServerThread; ++ ++import java.util.Arrays; ++ ++/** ++ * 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. ++ *
++ * 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}. ++ *
++ * 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. ++ */ ++ SERVER(new AbstractTaskQueue[]{ ++ BaseTaskQueues.deferredToServerThread, ++ BaseTaskQueues.serverThreadTick, ++ BaseTaskQueues.anyTickScheduledServerThread, ++ BaseTaskQueues.allLevelsScheduledChunkCache, ++ BaseTaskQueues.allLevelsScheduledTickThreadChunk ++ }, 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[]{ ++ // The cleaner queue has high priority because it releases resources back to a pool, thereby saving memory ++ BaseTaskQueues.cleaner, ++ BaseTaskQueues.scheduledAsync ++ }, Integer.getInteger("gale.thread.priority.async", 6)); ++ ++ /** ++ * 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. ++ *
++ * 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]); ++ ++} diff --git a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueues.java b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueues.java new file mode 100644 -index 0000000000000000000000000000000000000000..286b893749db38afc49fd03a0ac006598268d26e +index 0000000000000000000000000000000000000000..ed3ccf2e64539363a7be2d507c68c40b5913f75c --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueues.java -@@ -0,0 +1,115 @@ -+// Gale - base thread pools +@@ -0,0 +1,116 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.queue; + +import io.papermc.paper.util.MCUtil; +import io.papermc.paper.util.TickThread; -+import org.galemc.gale.executor.thread.pooled.TickAssistThread; ++import org.galemc.gale.executor.TaskSpan; ++import org.galemc.gale.executor.thread.BaseThread; +import org.galemc.gale.executor.thread.deferral.TickThreadDeferral; +import org.galemc.gale.executor.thread.AbstractYieldingThread; + @@ -3253,7 +3824,7 @@ index 0000000000000000000000000000000000000000..286b893749db38afc49fd03a0ac00659 + * in being started than pending tasks in represent steps in ticking the server, and as such always have the + * higher priority in being started than pending tasks in {@link #serverThreadTick}. + *
-+ * This queue may contain potentially yielding and yield-free tasks. ++ * This queue may contain tasks of every {@link TaskSpan}. + *
+ * This queue's {@link AbstractTaskQueue#add} must not be called from the server thread, + * because the server thread must not defer to itself (because tasks in this queue are assumed to have to run @@ -3262,33 +3833,33 @@ index 0000000000000000000000000000000000000000..286b893749db38afc49fd03a0ac00659 + * Instead, any task that must be deferred to the main thread must instead simply be executed + * when encountered on the main thread. + */ -+ public static final YieldingAndFreeSimpleTaskQueue deferredToServerThread = new YieldingAndFreeSimpleTaskQueue("DeferredToServerThread", true); ++ public static final SimpleTaskQueue deferredToServerThread = SimpleTaskQueue.allSpans("DeferredToServerThread", true); + + /** + * This queue stores the tasks scheduled to be executed on a {@link TickThread}, that are procedures that must run + * on a tick thread, but their completion is currently needed by another task that has started running on a thread + * that was not the main thread at the time of scheduling the main-thread-only procedure. + *
-+ * This queue may contain potentially yielding and yield-free tasks. ++ * This queue may contain tasks of every {@link TaskSpan}. + *
+ * This is currently completely unused, because {@link TickThreadDeferral} simply adds task to + * {@link #deferredToServerThread} instead, since there are currently no special {@link TickThread}s. + */ + @SuppressWarnings("unused") -+ public static final YieldingAndFreeSimpleTaskQueue deferredToUniversalTickThread = new YieldingAndFreeSimpleTaskQueue("DeferredToUniversalTickThread", true); ++ public static final SimpleTaskQueue deferredToUniversalTickThread = SimpleTaskQueue.allSpans("DeferredToUniversalTickThread", true); + + /** + * This queue explicitly stores tasks that represent steps or parts of steps in ticking the server and that must be + * executed on the main thread, and as such always have a higher priority in being started than pending tasks in + * {@link #anyTickScheduledServerThread} and {@link #scheduledAsync}. + *
-+ * This queue may contain potentially yielding and yield-free tasks. ++ * This queue may contain tasks of every {@link TaskSpan}. + *
+ * Tasks in every queue are performed in the order they are added (first-in, first-out). Note that this means + * not all main-thread-only tick tasks are necessarily performed in the order they are added, because they may be + * in different queues: either the queue for potentially yielding tasks or the queue for yield-free tasks. + */ -+ public static final YieldingAndFreeSimpleTaskQueue serverThreadTick = new YieldingAndFreeSimpleTaskQueue("ServerThreadTick"); ++ public static final SimpleTaskQueue serverThreadTick = SimpleTaskQueue.allSpans("ServerThreadTick"); + + /** + * Currently unused: only {@link #anyTickScheduledServerThread} is polled. @@ -3305,16 +3876,16 @@ index 0000000000000000000000000000000000000000..286b893749db38afc49fd03a0ac00659 + + /** + * This queue explicitly stores tasks that represent steps or parts of steps in ticking the server that do not have -+ * to be executed on the main thread (but must be executed on a {@link TickAssistThread}), and have a higher priority ++ * to be executed on the main thread (but must be executed on a {@link BaseThread}), and have a higher priority + * in being started than pending tasks in {@link #scheduledAsync}. + *
-+ * This queue may contain potentially yielding and yield-free tasks. ++ * This queue may contain tasks of every {@link TaskSpan}. + *
+ * Tasks in every queue are performed in the order they are added (first-in, first-out). Note that this means -+ * not all {@link TickAssistThread} tick tasks are necessarily performed in the order they are added, because they may be ++ * not all {@link BaseThread} tick tasks are necessarily performed in the order they are added, because they may be + * in different queues: either the queue for potentially yielding tasks or the queue for yield-free tasks. + */ -+ public static final YieldingAndFreeSimpleTaskQueue tickAssist = new YieldingAndFreeSimpleTaskQueue("TickAssist"); ++ public static final SimpleTaskQueue tickAssist = SimpleTaskQueue.allSpans("TickAssist"); + + /** + * @see AllLevelsScheduledChunkCacheTaskQueue @@ -3329,83 +3900,24 @@ index 0000000000000000000000000000000000000000..286b893749db38afc49fd03a0ac00659 + /** + * This queue stores the tasks posted to {@link MCUtil#cleanerExecutor}. + */ -+ public static final FreeSimpleTaskQueue cleaner = new FreeSimpleTaskQueue("Cleaner"); ++ public static final SingleSpanSimpleTaskQueue cleaner = SimpleTaskQueue.singleSpan("Cleaner", TaskSpan.TINY); + + /** + * This queue stores the tasks scheduled to be executed on any thread, which would usually be stored in various + * executors with a specific purpose. + *
-+ * This queue may contain potentially yielding and yield-free tasks. ++ * This queue may contain tasks of every {@link TaskSpan}. + */ -+ public static final YieldingAndFreeSimpleTaskQueue scheduledAsync = new YieldingAndFreeSimpleTaskQueue("ScheduledAsync"); -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/queue/FreeSimpleTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/FreeSimpleTaskQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7bffa7b358c058edbe33bbd2f31438c804c786ef ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/queue/FreeSimpleTaskQueue.java -@@ -0,0 +1,53 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.queue; -+ -+import org.galemc.gale.concurrent.UnterminableExecutorService; -+import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.concurrent.Executor; -+import java.util.concurrent.ExecutorService; -+ -+/** -+ * A base class for a task queue that contains tasks that are all yield-free. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+@YieldFree -+public class FreeSimpleTaskQueue extends SimpleTaskQueue { -+ -+ FreeSimpleTaskQueue(String name) { -+ super(name, false, true); -+ } -+ -+ FreeSimpleTaskQueue(String name, boolean lifoQueues) { -+ super(name, false, true, lifoQueues); -+ } -+ -+ /** -+ * Schedules a new task to this queue. -+ * -+ * @param task The task to schedule. -+ */ -+ @AnyThreadSafe -+ @YieldFree -+ public void add(Runnable task) { -+ this.add(task, false); -+ } -+ -+ /** -+ * An executor for adding tasks to this queue, -+ * where {@link Executor#execute} calls {@link #add}. -+ */ -+ public final ExecutorService executor = new UnterminableExecutorService() { -+ -+ @Override -+ public void execute(@NotNull Runnable runnable) { -+ add(runnable, false); -+ } -+ -+ }; ++ public static final SimpleTaskQueue scheduledAsync = SimpleTaskQueue.allSpans("ScheduledAsync"); + +} diff --git a/src/main/java/org/galemc/gale/executor/queue/ScheduledServerThreadTaskQueues.java b/src/main/java/org/galemc/gale/executor/queue/ScheduledServerThreadTaskQueues.java new file mode 100644 -index 0000000000000000000000000000000000000000..12f676a679f4331b39e2c17273f0759a9ce1f5f0 +index 0000000000000000000000000000000000000000..3d1a54ad35887e87bf7ad8c75a1f5a9be65c1eb8 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/ScheduledServerThreadTaskQueues.java -@@ -0,0 +1,298 @@ -+// Gale - base thread pools +@@ -0,0 +1,283 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.queue; + @@ -3417,8 +3929,9 @@ index 0000000000000000000000000000000000000000..12f676a679f4331b39e2c17273f0759a +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; +import org.galemc.gale.executor.annotation.Guarded; +import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.thread.pooled.ServerThread; -+import org.galemc.gale.executor.thread.wait.SignalReason; ++import org.galemc.gale.executor.thread.pool.BaseThreadActivation; ++import org.galemc.gale.executor.thread.ServerThread; ++import org.galemc.gale.executor.TaskSpan; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + @@ -3441,8 +3954,8 @@ index 0000000000000000000000000000000000000000..12f676a679f4331b39e2c17273f0759a + * Note that this means not all tasks are necessarily performed in the order they are added, because they may be in + * different queues based on the number of ticks before they have to be executed. + *
-+ * All contained tasks are currently assumed to be potentially yielding: no special distinction for yield-free -+ * tasks is made. ++ * All contained tasks are currently assumed to be {@link TaskSpan#YIELDING}: no special distinction for more ++ * permissive task spans is made. + * + * @author Martijn Muijsers under AGPL-3.0 + */ @@ -3467,6 +3980,11 @@ index 0000000000000000000000000000000000000000..12f676a679f4331b39e2c17273f0759a + public static int HANDLE_DISCONNECT_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + + /** ++ * Will be initialized in {@link TickRequiredScheduledServerThreadTaskQueue#setTier}. ++ */ ++ static BaseTaskQueueTier tier; ++ ++ /** + * A number of queues, with the queue at index i being the queue to be used after another i ticks pass + * even when {@link MinecraftServer#haveTime()} is false. + */ @@ -3487,42 +4005,21 @@ index 0000000000000000000000000000000000000000..12f676a679f4331b39e2c17273f0759a + private static final Lock readLock = lock.readLock(); + public static final Lock writeLock = lock.writeLock(); + -+ static SignalReason signalReason; -+ -+ /** -+ * @return Whether there are any scheduled main thread tasks that could start right now. Tasks that are scheduled -+ * for a later tick will only be regarded as able to be started if both we are not out of spare time this tick -+ * and {@code tryNonCurrentTickQueuesAtAll} is true. -+ *
-+ * This method does not check whether the given thread is or could claim the main thread: whether a task -+ * can start now is thread-agnostic and based purely on the state of the queue. -+ */ -+ public static boolean hasTasksThatCanStartNow(boolean tryNonCurrentTickQueuesAtAll) { -+ return hasTasks(tryNonCurrentTickQueuesAtAll, true); -+ } -+ + /** + * @return Whether there are any scheduled main thread tasks, not counting any tasks that do not have to be + * finished within the current tick if {@code tryNonCurrentTickQueuesAtAll} is false. + */ + public static boolean hasTasks(boolean tryNonCurrentTickQueuesAtAll) { -+ return hasTasks(tryNonCurrentTickQueuesAtAll, false); -+ } -+ -+ /** -+ * Common implementation for {@link #hasTasksThatCanStartNow} and {@link #hasTasks(boolean)}. -+ */ -+ private static boolean hasTasks(boolean tryNonCurrentTickQueuesAtAll, boolean checkCanStartNow) { + //noinspection StatementWithEmptyBody + while (!readLock.tryLock()); + try { + // Try the queue most likely to contain tasks first -+ if (firstQueueWithPotentialTasksIndex == 0 || (tryNonCurrentTickQueuesAtAll && (!checkCanStartNow || !MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking))) { ++ if (firstQueueWithPotentialTasksIndex == 0 || tryNonCurrentTickQueuesAtAll) { + if (!queues[firstQueueWithPotentialTasksIndex].isEmpty()) { + return true; + } + } -+ int checkUpTo = tryNonCurrentTickQueuesAtAll && (!checkCanStartNow || !MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking) ? queues.length : 1; ++ int checkUpTo = tryNonCurrentTickQueuesAtAll ? queues.length : 1; + for (int i = 0; i < checkUpTo; i++) { + if (!queues[i].isEmpty()) { + return true; @@ -3546,7 +4043,7 @@ index 0000000000000000000000000000000000000000..12f676a679f4331b39e2c17273f0759a + */ + public static @Nullable Runnable poll(ServerThread currentThread, boolean tryNonCurrentTickQueuesAtAll) { + // Since we assume the tasks in this queue to be potentially yielding, fail if the thread is restricted -+ if (currentThread.isRestrictedDueToYieldDepth) { ++ if (!currentThread.canStartYieldingTasks) { + return null; + } + pollFromFirstQueueOrOthers: while (true) { @@ -3656,7 +4153,7 @@ index 0000000000000000000000000000000000000000..12f676a679f4331b39e2c17273f0759a + readLock.unlock(); + } + MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks = true; -+ signalReason.signalAnother(); ++ BaseThreadActivation.newTaskWasAdded(tier, TaskSpan.YIELDING); + } + + /** @@ -3705,24 +4202,30 @@ index 0000000000000000000000000000000000000000..12f676a679f4331b39e2c17273f0759a +} diff --git a/src/main/java/org/galemc/gale/executor/queue/SimpleTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/SimpleTaskQueue.java new file mode 100644 -index 0000000000000000000000000000000000000000..7b0de8f921eb8431fa00f1e6a82769165a0efc00 +index 0000000000000000000000000000000000000000..2c910f89f1056d00e5e4a2d832cdd4be4b7527b4 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/SimpleTaskQueue.java -@@ -0,0 +1,173 @@ -+// Gale - base thread pools +@@ -0,0 +1,253 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.queue; + +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; +import org.galemc.gale.collection.FIFOConcurrentLinkedQueue; ++import org.galemc.gale.concurrent.UnterminableExecutorService; ++import org.galemc.gale.executor.TaskSpan; +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; +import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.thread.BaseYieldingThread; -+import org.galemc.gale.executor.thread.pooled.AbstractYieldingSignallableThreadPool; -+import org.galemc.gale.executor.thread.wait.SignalReason; ++import org.galemc.gale.executor.thread.BaseThread; ++import org.galemc.gale.executor.thread.pool.BaseThreadActivation; ++import org.galemc.gale.executor.thread.SignalReason; ++import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + ++import java.util.Arrays; +import java.util.Queue; ++import java.util.concurrent.Executor; ++import java.util.concurrent.ExecutorService; + +/** + * A base class for a task queue that may contain tasks that are @@ -3732,7 +4235,7 @@ index 0000000000000000000000000000000000000000..7b0de8f921eb8431fa00f1e6a8276916 + */ +@AnyThreadSafe +@YieldFree -+public abstract class SimpleTaskQueue implements AbstractTaskQueue { ++public class SimpleTaskQueue implements AbstractTaskQueue { + + /** + * The name of this queue. @@ -3742,153 +4245,299 @@ index 0000000000000000000000000000000000000000..7b0de8f921eb8431fa00f1e6a8276916 + public final String name; + + /** -+ * Whether tasks in this queue can be potentially yielding. ++ * The {@link BaseTaskQueueTier} that contains this queue. ++ * Will be initialized in {@link #setTier} by the constructor of the tier. + */ -+ public final boolean canHaveYieldingTasks; ++ public BaseTaskQueueTier tier; + + /** -+ * Whether tasks in this queue can be yield-free. ++ * Whether tasks in this queue can have a {@link TaskSpan}, indexed by the span's {@link TaskSpan#ordinal}. + */ -+ public final boolean canHaveFreeTasks; ++ public final boolean[] canHaveTasks; + + /** -+ * The queue of potentially yielding tasks, or null if {@link #canHaveYieldingTasks} is false. ++ * The queues of tasks, indexed by their {@link TaskSpan#ordinal}. Individual elements may be null if the ++ * corresponding {@link #canHaveTasks} value is false. + */ -+ private final @Nullable Queue yieldingQueue; ++ @SuppressWarnings("rawtypes") ++ private final @Nullable Queue @NotNull [] queues; + + /** -+ * The queue of yield-free tasks, or null if {@link #canHaveFreeTasks} is false. ++ * An executor for adding tasks of a specific {@link TaskSpan} to this queue, where {@link Executor#execute} ++ * calls {@link #add(Runnable, TaskSpan)}, ++ * or null if the corresponding value in {@link #canHaveTasks} is false. + */ -+ private final @Nullable Queue freeQueue; ++ @SuppressWarnings("FieldCanBeLocal") ++ private final @Nullable ExecutorService @NotNull [] executors; + + /** -+ * The {@link AbstractYieldingSignallableThreadPool} of which the member threads will poll from this queue. -+ * Will be initialized in {@link #setThreadPool} by the constructor of the thread pool. ++ * An executor for adding {@link TaskSpan#YIELDING} tasks to this queue, ++ * where {@link Executor#execute} calls {@link #add}, ++ * or null if the corresponding value in {@link #canHaveTasks} is false. + */ -+ public AbstractYieldingSignallableThreadPool threadPool; ++ public final ExecutorService yieldingExecutor; + + /** -+ * The {@link SignalReason} used when signalling threads due to newly added potentially yielding tasks, -+ * or null if {@link #canHaveYieldingTasks} is false. -+ * Will be initialized in {@link #setThreadPool}. ++ * An executor for adding {@link TaskSpan#FREE} tasks to this queue, ++ * where {@link Executor#execute} calls {@link #add}, ++ * or null if the corresponding value in {@link #canHaveTasks} is false. + */ -+ private @Nullable SignalReason yieldingSignalReason; ++ public final ExecutorService freeExecutor; + + /** -+ * The {@link SignalReason} used when signalling threads due to newly added yield-free tasks, -+ * or null if {@link #canHaveFreeTasks} is false. -+ * Will be initialized in {@link #setThreadPool}. ++ * An executor for adding {@link TaskSpan#TINY} tasks to this queue, ++ * where {@link Executor#execute} calls {@link #add}, ++ * or null if the corresponding value in {@link #canHaveTasks} is false. + */ -+ private @Nullable SignalReason freeSignalReason; ++ public final ExecutorService tinyExecutor; + -+ SimpleTaskQueue(String name, boolean canHaveYieldingTasks, boolean canHaveFreeTasks) { -+ this(name, canHaveYieldingTasks, canHaveFreeTasks, false); ++ SimpleTaskQueue(String name, boolean[] canHaveTasks) { ++ this(name, canHaveTasks, false); + } + + /** + * @param name Value for {@link #getName}. -+ * @param canHaveYieldingTasks Value for {@link #canHaveYieldingTasks}. -+ * @param canHaveFreeTasks Value for {@link #canHaveFreeTasks}. -+ * @param lifoQueues If true, the {@link #yieldingQueue} and {@link #freeQueue} will be LIFO; ++ * @param canHaveTasks Value for {@link #canHaveTasks}. ++ * @param lifoQueues If true, the queues in {@link #queues} will be LIFO; + * otherwise, they will be FIFO. + */ -+ SimpleTaskQueue(String name, boolean canHaveYieldingTasks, boolean canHaveFreeTasks, boolean lifoQueues) { ++ SimpleTaskQueue(String name, boolean[] canHaveTasks, boolean lifoQueues) { + this.name = name; -+ this.canHaveYieldingTasks = canHaveYieldingTasks; -+ this.canHaveFreeTasks = canHaveFreeTasks; -+ this.yieldingQueue = this.canHaveYieldingTasks ? (lifoQueues ? new FIFOConcurrentLinkedQueue<>() : new MultiThreadedQueue<>()) : null; -+ this.freeQueue = this.canHaveFreeTasks ? (lifoQueues ? new FIFOConcurrentLinkedQueue<>() : new MultiThreadedQueue<>()) : null; ++ if (canHaveTasks.length != TaskSpan.length) { ++ throw new IllegalArgumentException(); ++ } ++ this.canHaveTasks = canHaveTasks; ++ this.queues = new Queue[TaskSpan.length]; ++ this.executors = new ExecutorService[TaskSpan.length]; ++ for (int spanOrdinal = 0; spanOrdinal < TaskSpan.length; spanOrdinal++) { ++ if (this.canHaveTasks[spanOrdinal]) { ++ this.queues[spanOrdinal] = lifoQueues ? new FIFOConcurrentLinkedQueue<>() : new MultiThreadedQueue<>(); ++ this.executors[spanOrdinal] = new SpanExecutor(TaskSpan.VALUES[spanOrdinal]); ++ } ++ } ++ this.yieldingExecutor = this.executors[TaskSpan.YIELDING.ordinal]; ++ this.freeExecutor = this.executors[TaskSpan.FREE.ordinal]; ++ this.tinyExecutor = this.executors[TaskSpan.TINY.ordinal]; + } + + @Override -+ public String getName() { ++ public final String getName() { + return this.name; + } + + @Override -+ public boolean hasYieldingTasksThatCanStartNow() { -+ //noinspection ConstantConditions -+ return this.canHaveYieldingTasks && !this.yieldingQueue.isEmpty(); -+ } -+ -+ @Override -+ public boolean hasFreeTasksThatCanStartNow() { -+ //noinspection ConstantConditions -+ return this.canHaveFreeTasks && !this.freeQueue.isEmpty(); -+ } -+ -+ @Override -+ public boolean hasTasks() { -+ //noinspection ConstantConditions -+ return (this.canHaveYieldingTasks && !this.yieldingQueue.isEmpty()) || (this.canHaveFreeTasks && !this.freeQueue.isEmpty()); -+ } -+ -+ @Override -+ public @Nullable Runnable poll(BaseYieldingThread currentThread) { -+ Runnable task; -+ if (!currentThread.isRestrictedDueToYieldDepth && this.canHaveYieldingTasks) { -+ //noinspection ConstantConditions -+ task = this.yieldingQueue.poll(); -+ if (task != null) { -+ /* -+ If the thread was woken up for a different reason, signal for the same reason to wake up another -+ potential thread waiting for the reason. -+ */ -+ SignalReason lastSignalReason = currentThread.lastSignalReason; -+ if (lastSignalReason != null && lastSignalReason != this.yieldingSignalReason) { -+ lastSignalReason.signalAnother(); -+ } -+ return task; ++ public final boolean hasTasks() { ++ for (int spanOrdinal = 0; spanOrdinal < TaskSpan.length; spanOrdinal++) { ++ var queue = this.queues[spanOrdinal]; ++ if (queue != null && !queue.isEmpty()) { ++ return true; + } + } -+ if (this.canHaveFreeTasks) { -+ //noinspection ConstantConditions -+ task = this.freeQueue.poll(); -+ if (task != null) { -+ /* -+ If the thread was woken up for a different reason, signal for the same reason to wake up another -+ potential thread waiting for the reason. -+ */ -+ SignalReason lastSignalReason = currentThread.lastSignalReason; -+ if (lastSignalReason != null && lastSignalReason != this.freeSignalReason) { -+ lastSignalReason.signalAnother(); ++ return false; ++ } ++ ++ @Override ++ public boolean hasTasks(TaskSpan span) { ++ var queue = this.queues[span.ordinal]; ++ return queue != null && !queue.isEmpty(); ++ } ++ ++ @Override ++ public boolean canHaveTasks(TaskSpan span) { ++ return this.queues[span.ordinal] != null; ++ } ++ ++ /** ++ * @see #poll(BaseThread) ++ */ ++ private @Nullable Runnable poll(BaseThread currentThread, int spanOrdinal) { ++ var queue = this.queues[spanOrdinal]; ++ if (queue != null) { ++ if (currentThread.canStartYieldingTasks || TaskSpan.VALUES[spanOrdinal].isNotYielding) { ++ Object task = queue.poll(); ++ if (task != null) { ++ /* ++ If the thread was woken up for a different reason, ++ another thread should be signalled for that reason. ++ */ ++ SignalReason lastSignalReason = currentThread.lastSignalReason; ++ if (lastSignalReason != null && lastSignalReason != SignalReason.TASK) { ++ BaseThreadActivation.callForUpdate(); ++ } ++ return (Runnable) task; + } ++ } ++ } ++ return null; ++ } ++ ++ @Override ++ public final @Nullable Runnable poll(BaseThread currentThread) { ++ Runnable task; ++ for (int spanOrdinal = 0; spanOrdinal < TaskSpan.length; spanOrdinal++) { ++ task = this.poll(currentThread, spanOrdinal); ++ if (task != null) { + return task; + } + } + return null; + } + -+ @SuppressWarnings("ConstantConditions") + @Override -+ public void add(Runnable task, boolean yielding) { -+ if (yielding) { -+ this.yieldingQueue.add(task); -+ this.yieldingSignalReason.signalAnother(); -+ return; -+ } -+ this.freeQueue.add(task); -+ this.freeSignalReason.signalAnother(); ++ public final @Nullable Runnable pollTiny(BaseThread currentThread) { ++ return poll(currentThread, TaskSpan.TINY.ordinal); + } + + @Override -+ public void setThreadPool(AbstractYieldingSignallableThreadPool threadPool) { -+ if (this.threadPool != null) { -+ throw new IllegalStateException("SimpleTaskQueue.threadPool was already initialized"); -+ } -+ this.threadPool = threadPool; -+ this.yieldingSignalReason = SignalReason.createForThreadPoolNewTasks(this.threadPool, true); -+ this.freeSignalReason = SignalReason.createForThreadPoolNewTasks(this.threadPool, false); ++ public final void add(Runnable task, TaskSpan span) { ++ int spanOrdinal = span.ordinal; ++ //noinspection unchecked ++ this.queues[spanOrdinal].add(task); ++ BaseThreadActivation.newTaskWasAdded(this.tier, span); + } + ++ @Override ++ public final void setTier(BaseTaskQueueTier tier) { ++ if (this.tier != null) { ++ throw new IllegalStateException("SimpleTaskQueue.tier was already initialized"); ++ } ++ this.tier = tier; ++ } ++ ++ private class SpanExecutor extends UnterminableExecutorService { ++ ++ private final TaskSpan span; ++ ++ private SpanExecutor(TaskSpan span) { ++ this.span = span; ++ } ++ ++ @Override ++ public void execute(@NotNull Runnable runnable) { ++ SimpleTaskQueue.this.add(runnable, this.span); ++ } ++ ++ } ++ ++ public static SingleSpanSimpleTaskQueue singleSpan(String name, TaskSpan span) { ++ return new SingleSpanSimpleTaskQueue(name, span); ++ } ++ ++ @SuppressWarnings("unused") ++ public static SingleSpanSimpleTaskQueue singleSpan(String name, TaskSpan span, boolean lifoQueues) { ++ return new SingleSpanSimpleTaskQueue(name, span, lifoQueues); ++ } ++ ++ private static boolean[] createCanHaveTasksForDoubleSpan(TaskSpan span1, TaskSpan span2) { ++ boolean[] canHaveTasks = new boolean[TaskSpan.length]; ++ canHaveTasks[span1.ordinal] = true; ++ canHaveTasks[span2.ordinal] = true; ++ return canHaveTasks; ++ } ++ ++ @SuppressWarnings("unused") ++ public static SimpleTaskQueue doubleSpan(String name, TaskSpan span1, TaskSpan span2) { ++ return new SimpleTaskQueue(name, createCanHaveTasksForDoubleSpan(span1, span2)); ++ } ++ ++ @SuppressWarnings("unused") ++ public static SimpleTaskQueue doubleSpan(String name, TaskSpan span1, TaskSpan span2, boolean lifoQueues) { ++ return new SimpleTaskQueue(name, createCanHaveTasksForDoubleSpan(span1, span2), lifoQueues); ++ } ++ ++ private static final boolean[] canHaveTasksForAllSpans = new boolean[TaskSpan.length]; ++ static { ++ Arrays.fill(canHaveTasksForAllSpans, true); ++ } ++ ++ public static SimpleTaskQueue allSpans(String name) { ++ return new SimpleTaskQueue(name, canHaveTasksForAllSpans); ++ } ++ ++ public static SimpleTaskQueue allSpans(String name, boolean lifoQueues) { ++ return new SimpleTaskQueue(name, canHaveTasksForAllSpans, lifoQueues); ++ } ++ ++} +diff --git a/src/main/java/org/galemc/gale/executor/queue/SingleSpanSimpleTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/SingleSpanSimpleTaskQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2251087670d554a7bd5dc81631615aa0728eb315 +--- /dev/null ++++ b/src/main/java/org/galemc/gale/executor/queue/SingleSpanSimpleTaskQueue.java +@@ -0,0 +1,66 @@ ++// Gale - base thread pool ++ ++package org.galemc.gale.executor.queue; ++ ++import org.galemc.gale.concurrent.UnterminableExecutorService; ++import org.galemc.gale.executor.TaskSpan; ++import org.galemc.gale.executor.annotation.YieldFree; ++import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.concurrent.Executor; ++import java.util.concurrent.ExecutorService; ++ ++/** ++ * A base class for a task queue that contains tasks that are all of one {@link TaskSpan}. ++ * ++ * @author Martijn Muijsers under AGPL-3.0 ++ */ ++@YieldFree ++public class SingleSpanSimpleTaskQueue extends SimpleTaskQueue { ++ ++ private static final boolean[][] canHaveTasksPerSpan = new boolean[TaskSpan.length][]; ++ static { ++ for (TaskSpan span : TaskSpan.VALUES) { ++ canHaveTasksPerSpan[span.ordinal] = new boolean[TaskSpan.length]; ++ canHaveTasksPerSpan[span.ordinal][span.ordinal] = true; ++ } ++ } ++ ++ private final TaskSpan span; ++ ++ SingleSpanSimpleTaskQueue(String name, TaskSpan span) { ++ super(name, canHaveTasksPerSpan[span.ordinal]); ++ this.span = span; ++ } ++ ++ SingleSpanSimpleTaskQueue(String name, TaskSpan span, boolean lifoQueues) { ++ super(name, canHaveTasksPerSpan[span.ordinal], lifoQueues); ++ this.span = span; ++ } ++ ++ /** ++ * Schedules a new task to this queue. ++ * ++ * @param task The task to schedule. ++ */ ++ @AnyThreadSafe ++ @YieldFree ++ public void add(Runnable task) { ++ this.add(task, this.span); ++ } ++ ++ /** ++ * An executor for adding tasks to this queue, ++ * where {@link Executor#execute} calls {@link #add}. ++ */ ++ public final ExecutorService executor = new UnterminableExecutorService() { ++ ++ @Override ++ public void execute(@NotNull Runnable runnable) { ++ SingleSpanSimpleTaskQueue.this.add(runnable, SingleSpanSimpleTaskQueue.this.span); ++ } ++ ++ }; ++ +} diff --git a/src/main/java/org/galemc/gale/executor/queue/ThisTickScheduledServerThreadTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/ThisTickScheduledServerThreadTaskQueue.java new file mode 100644 -index 0000000000000000000000000000000000000000..a6bb58410d0bb741b44cf40a43269c848fdc5b51 +index 0000000000000000000000000000000000000000..3cb2b84cb7653ff3e038acdc2e6e11f805bfbbba --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/ThisTickScheduledServerThreadTaskQueue.java -@@ -0,0 +1,29 @@ -+// Gale - base thread pools +@@ -0,0 +1,27 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.queue; + @@ -3898,8 +4547,6 @@ index 0000000000000000000000000000000000000000..a6bb58410d0bb741b44cf40a43269c84 +/** + * This class provides access to, but does not store, the tasks scheduled to be executed on the main thread, + * that must be finished in the current tick or its spare time. -+ *
-+ * This queue does not support {@link #add}. + * + * @author Martijn Muijsers under AGPL-3.0 + */ @@ -3919,25 +4566,26 @@ index 0000000000000000000000000000000000000000..a6bb58410d0bb741b44cf40a43269c84 +} diff --git a/src/main/java/org/galemc/gale/executor/queue/TickRequiredScheduledServerThreadTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/TickRequiredScheduledServerThreadTaskQueue.java new file mode 100644 -index 0000000000000000000000000000000000000000..421ed98e13bfab6348e5f054e6aedeba89180de6 +index 0000000000000000000000000000000000000000..fb4f9c047fc71a9a01aa47871254c6a949753a3a --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/TickRequiredScheduledServerThreadTaskQueue.java -@@ -0,0 +1,61 @@ -+// Gale - base thread pools +@@ -0,0 +1,67 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.queue; + ++import org.galemc.gale.executor.TaskSpan; +import org.galemc.gale.executor.annotation.YieldFree; +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; -+import org.galemc.gale.executor.thread.BaseYieldingThread; -+import org.galemc.gale.executor.thread.pooled.AbstractYieldingSignallableThreadPool; -+import org.galemc.gale.executor.thread.pooled.ServerThread; -+import org.galemc.gale.executor.thread.wait.SignalReason; ++import org.galemc.gale.executor.thread.BaseThread; ++import org.galemc.gale.executor.thread.ServerThread; +import org.jetbrains.annotations.Nullable; + +/** + * A common base class for {@link ThisTickScheduledServerThreadTaskQueue} and + * {@link AnyTickScheduledServerThreadTaskQueue}. ++ *
++ * This queue does not support {@link #add}. + * + * @author Martijn Muijsers under AGPL-3.0 + */ @@ -3952,217 +4600,75 @@ index 0000000000000000000000000000000000000000..421ed98e13bfab6348e5f054e6aedeba + } + + @Override -+ public boolean hasYieldingTasksThatCanStartNow() { -+ return ScheduledServerThreadTaskQueues.hasTasksThatCanStartNow(this.tryNonCurrentTickQueuesAtAll); -+ } -+ -+ @Override + public boolean hasTasks() { + return ScheduledServerThreadTaskQueues.hasTasks(this.tryNonCurrentTickQueuesAtAll); + } + + @Override -+ public boolean hasFreeTasksThatCanStartNow() { -+ return false; ++ public boolean hasTasks(TaskSpan span) { ++ return span == TaskSpan.YIELDING && this.hasTasks(); + } + + @Override -+ public @Nullable Runnable poll(BaseYieldingThread currentThread) { ++ public boolean canHaveTasks(TaskSpan span) { ++ return span == TaskSpan.YIELDING; ++ } ++ ++ @Override ++ public @Nullable Runnable poll(BaseThread currentThread) { + return ScheduledServerThreadTaskQueues.poll((ServerThread) currentThread, this.tryNonCurrentTickQueuesAtAll); + } + + @Override -+ public void add(Runnable task, boolean yielding) { ++ public @Nullable Runnable pollTiny(BaseThread currentThread) { ++ return null; ++ } ++ ++ @Override ++ public void add(Runnable task, TaskSpan span) { + throw new UnsupportedOperationException(); + } + + @Override -+ public void setThreadPool(AbstractYieldingSignallableThreadPool threadPool) { -+ if (ScheduledServerThreadTaskQueues.signalReason == null) { -+ ScheduledServerThreadTaskQueues.signalReason = SignalReason.createForThreadPoolNewTasks(threadPool, true); ++ public void setTier(BaseTaskQueueTier tier) { ++ if (ScheduledServerThreadTaskQueues.tier == null) { ++ ScheduledServerThreadTaskQueues.tier = tier; + } + } + +} -diff --git a/src/main/java/org/galemc/gale/executor/queue/YieldingAndFreeSimpleTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/YieldingAndFreeSimpleTaskQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..95f8f83abd1a252cd1dbf805f7def97bb2455f9e ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/queue/YieldingAndFreeSimpleTaskQueue.java -@@ -0,0 +1,55 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.queue; -+ -+import org.galemc.gale.concurrent.UnterminableExecutorService; -+import org.galemc.gale.executor.annotation.YieldFree; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.concurrent.Executor; -+import java.util.concurrent.ExecutorService; -+ -+/** -+ * A base class for a task queue that contains tasks that are potentially yielding and tasks that are -+ * yield-free. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+@YieldFree -+public class YieldingAndFreeSimpleTaskQueue extends SimpleTaskQueue { -+ -+ YieldingAndFreeSimpleTaskQueue(String name) { -+ super(name, true, true); -+ } -+ -+ YieldingAndFreeSimpleTaskQueue(String name, boolean lifoQueues) { -+ super(name, true, true, lifoQueues); -+ } -+ -+ /** -+ * An executor for adding potentially yielding tasks to this queue, -+ * where {@link Executor#execute} calls {@link #add}. -+ */ -+ public final ExecutorService yieldingExecutor = new UnterminableExecutorService() { -+ -+ @Override -+ public void execute(@NotNull Runnable runnable) { -+ add(runnable, true); -+ } -+ -+ }; -+ -+ /** -+ * An executor for adding yield-free tasks to this queue, -+ * where {@link Executor#execute} calls {@link #add}. -+ */ -+ public final ExecutorService freeExecutor = new UnterminableExecutorService() { -+ -+ @Override -+ public void execute(@NotNull Runnable runnable) { -+ add(runnable, false); -+ } -+ -+ }; -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/queue/YieldingSimpleTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/YieldingSimpleTaskQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..015652cb18405fe44b4f8a5fe4b37549998ca795 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/queue/YieldingSimpleTaskQueue.java -@@ -0,0 +1,53 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.queue; -+ -+import org.galemc.gale.concurrent.UnterminableExecutorService; -+import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.concurrent.Executor; -+import java.util.concurrent.ExecutorService; -+ -+/** -+ * A base class for a task queue that contains tasks that are all potentially yielding. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+@YieldFree -+public class YieldingSimpleTaskQueue extends SimpleTaskQueue { -+ -+ YieldingSimpleTaskQueue(String name) { -+ super(name, true, false); -+ } -+ -+ YieldingSimpleTaskQueue(String name, boolean lifoQueues) { -+ super(name, true, false, lifoQueues); -+ } -+ -+ /** -+ * Schedules a new task to this queue. -+ * -+ * @param task The task to schedule. -+ */ -+ @AnyThreadSafe -+ @YieldFree -+ public void add(Runnable task) { -+ this.add(task, true); -+ } -+ -+ /** -+ * An executor for adding tasks to this queue, -+ * where {@link Executor#execute} calls {@link #add}. -+ */ -+ public final ExecutorService executor = new UnterminableExecutorService() { -+ -+ @Override -+ public void execute(@NotNull Runnable runnable) { -+ add(runnable, true); -+ } -+ -+ }; -+ -+} diff --git a/src/main/java/org/galemc/gale/executor/thread/AbstractYieldingThread.java b/src/main/java/org/galemc/gale/executor/thread/AbstractYieldingThread.java new file mode 100644 -index 0000000000000000000000000000000000000000..b7c51a6dc54f6879aa662c1431da1d684b348d2a +index 0000000000000000000000000000000000000000..bb2055d8da984671762d9a5f8c4dc12f9353ceeb --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/thread/AbstractYieldingThread.java -@@ -0,0 +1,69 @@ -+// Gale - base thread pools +@@ -0,0 +1,43 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.thread; + +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; -+import org.galemc.gale.executor.annotation.PotentiallyYielding; -+import org.galemc.gale.executor.annotation.thread.ThisThreadOnly; +import org.galemc.gale.executor.annotation.YieldFree; +import org.galemc.gale.executor.lock.YieldingLock; -+import org.galemc.gale.executor.thread.pooled.TickAssistThread; +import org.jetbrains.annotations.Nullable; + +import java.util.function.BooleanSupplier; + +/** -+ * An interface for threads that can yield to other tasks in lieu of blocking. ++ * An interface for threads that can yield to other tasks, for example upon encountering a {@link YieldingLock}, ++ * in lieu of blocking. + * + * @author Martijn Muijsers under AGPL-3.0 + */ +public interface AbstractYieldingThread extends SignallableThread { + -+ /** -+ * Yields to tasks: polls and executes tasks while possible and the stop condition is not met. -+ * The stop condition is met if {@code stopCondition} is not null and returns true, or alternatively, -+ * if {@code stopCondition} is null, and {@code yieldingLock} is successfully acquired. -+ * When no tasks can be polled, this thread will block, waiting for either a task that can be executed by this -+ * thread to become available, or for the {@code yieldingLock}, if given, to be released. -+ *
-+ * Exactly one of {@code stopCondition} and {@code yieldingLock} must be non-null. -+ */ -+ @ThisThreadOnly -+ @PotentiallyYielding("this method is meant to yield") ++ + void yieldUntil(@Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock); + -+ /** -+ * This method will keep attempting to find a task to do, and execute it, 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. -+ * The loop is broken as soon as the stop condition becomes true, or the given lock is successfully acquired. -+ *
-+ * The above is the same as {@link #yieldUntil}, except it may be called in situations that is not 'yielding', -+ * for instance the endless loop polling tasks performed by a {@link TickAssistThread}. The difference with -+ * {@link #yieldUntil} is that this method does not increment or decrement things like the yield depth of this -+ * thread, if relevant. -+ * -+ * @see #yieldUntil -+ */ -+ @ThisThreadOnly -+ @PotentiallyYielding("may yield further if an executed task is potentially yielding") ++ + void runTasksUntil(@Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock); + -+ /** -+ * @return The current thread if it is a {@link AbstractYieldingThread}, or null otherwise. -+ */ ++ + @AnyThreadSafe + @YieldFree + static @Nullable AbstractYieldingThread currentYieldingThread() { @@ -4172,6 +4678,7 @@ index 0000000000000000000000000000000000000000..b7c51a6dc54f6879aa662c1431da1d68 + /** + * @return Whether the current thread is a {@link AbstractYieldingThread}. + */ ++ @SuppressWarnings("unused") + @AnyThreadSafe + @YieldFree + static boolean isYieldingThread() { @@ -4179,48 +4686,130 @@ index 0000000000000000000000000000000000000000..b7c51a6dc54f6879aa662c1431da1d68 + } + +} -diff --git a/src/main/java/org/galemc/gale/executor/thread/BaseYieldingThread.java b/src/main/java/org/galemc/gale/executor/thread/BaseYieldingThread.java +diff --git a/src/main/java/org/galemc/gale/executor/thread/AssistThread.java b/src/main/java/org/galemc/gale/executor/thread/AssistThread.java new file mode 100644 -index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea8e45e15f +index 0000000000000000000000000000000000000000..a5605765f6be0b75e5df5613e8b393b64f88fc3c --- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/BaseYieldingThread.java -@@ -0,0 +1,680 @@ -+// Gale - base thread pools ++++ b/src/main/java/org/galemc/gale/executor/thread/AssistThread.java +@@ -0,0 +1,78 @@ ++// Gale - base thread pool ++ ++package org.galemc.gale.executor.thread; ++ ++import org.galemc.gale.executor.annotation.YieldFree; ++import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; ++import org.galemc.gale.executor.annotation.thread.BaseThreadOnly; ++import org.galemc.gale.executor.annotation.thread.ThisThreadOnly; ++import org.galemc.gale.executor.thread.pool.BaseThreadPool; ++import org.jetbrains.annotations.Nullable; ++ ++/** ++ * 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(() -> 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(); ++ } ++ ++} +diff --git a/src/main/java/org/galemc/gale/executor/thread/BaseThread.java b/src/main/java/org/galemc/gale/executor/thread/BaseThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1024c45be3d867af6563d4f6984e716cc0bc12d2 +--- /dev/null ++++ b/src/main/java/org/galemc/gale/executor/thread/BaseThread.java +@@ -0,0 +1,682 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.thread; + +import io.papermc.paper.util.TickThread; +import net.minecraft.server.MinecraftServer; ++import org.galemc.gale.executor.TaskSpan; +import org.galemc.gale.executor.annotation.Access; ++import org.galemc.gale.executor.annotation.PotentiallyYielding; +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; +import org.galemc.gale.executor.annotation.Guarded; +import org.galemc.gale.executor.annotation.PotentiallyBlocking; -+import org.galemc.gale.executor.annotation.thread.BaseYieldingThreadOnly; +import org.galemc.gale.executor.annotation.thread.ThisThreadOnly; +import org.galemc.gale.executor.annotation.YieldFree; +import org.galemc.gale.executor.lock.YieldingLock; +import org.galemc.gale.executor.queue.AbstractTaskQueue; -+import org.galemc.gale.executor.thread.pooled.ConditionalSurplusThread; -+import org.galemc.gale.executor.thread.wait.SignalReason; -+import org.galemc.gale.executor.thread.wait.WaitingThreadSet; ++import org.galemc.gale.executor.queue.BaseTaskQueueTier; ++import org.galemc.gale.executor.thread.pool.*; +import org.jetbrains.annotations.Nullable; + -+import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BooleanSupplier; -+import java.util.stream.Collectors; + +/** + * An abstract base class implementing {@link AbstractYieldingThread}, + * that provides implementation that is common between -+ * {@link TickThread} and {@link ConditionalSurplusThread}. ++ * {@link TickThread} and {@link AssistThread}. + * + * @author Martijn Muijsers under AGPL-3.0 + */ -+public abstract class BaseYieldingThread extends Thread implements AbstractYieldingThread { ++public abstract class BaseThread extends Thread implements AbstractYieldingThread { + + /** + * The minimum time to wait as the {@link MinecraftServer#serverThread} when performing a timed wait. @@ -4237,72 +4826,66 @@ index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea + public static final long SERVER_THREAD_WAIT_NANOS_DURING_OVERSLEEP_WITH_DELAYED_TASKS = 50_000; + + /** -+ * The {@link SignalReason} to pass to {@link #signal(SignalReason)} when this thread becomes allowed -+ * to poll tasks after having not been allowed to. ++ * The index of this thread, as needed as an argument to ++ * {@link BaseThreadPool#getThreadByBaseIndex(int)}. + */ -+ public static final SignalReason becomesAllowedToPollSignalReason = SignalReason.createNonRetrying(); ++ public final int baseThreadIndex; + + /** -+ * The queues that this thread can poll from, in the order they should be attempted to be polled from. -+ */ -+ public final AbstractTaskQueue[] taskQueues; -+ -+ /** -+ * The {@link WaitingThreadSet} to add this thread to while waiting for new potentially yielding tasks. -+ */ -+ private final WaitingThreadSet threadsWaitingForYieldingTasks; -+ -+ /** -+ * The {@link WaitingThreadSet} to add this thread to while waiting for new yield-free tasks. -+ */ -+ private final WaitingThreadSet threadsWaitingForFreeTasks; -+ -+ /** -+ * Whether this thread keeps track of the yield depth in {@link #yieldDepth}. -+ */ -+ private final boolean trackYieldDepth; -+ -+ /** -+ * The maximum yield depth. While this thread has a yield depth equal to or greater than this value, -+ * it can not start more potentially yielding tasks. -+ *
-+ * This value has no effect if {@link #trackYieldDepth} is false. ++ * The maximum yield depth for this thread, ++ * which equals 1 for a {@link ServerThread} ++ * and {@link AssistThread#MAXIMUM_YIELD_DEPTH} for an {@link AssistThread}. + */ + public final int maximumYieldDepth; + + /** + * The current yield depth of this thread. -+ *
-+ * This value is always 0 if {@link #trackYieldDepth} is false. + */ + @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) + public volatile int yieldDepth = 0; + + /** -+ * Whether this thread is currently restricted ++ * Whether this thread can currently start yielding tasks with respect to being restricted + * due to {@link #yieldDepth} being at least {@link #maximumYieldDepth}. + *
-+ * This value is always false if {@link #trackYieldDepth} is false. ++ * This is updated after {@link #yieldDepth} is updated. + */ + @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) -+ public volatile boolean isRestrictedDueToYieldDepth = false; ++ public volatile boolean canStartYieldingTasks = true; ++ ++ /** ++ * The highest {@link BaseTaskQueueTier} of any task on the yielding execution stack of this thread, ++ * or null if there is no task being executed on this thread. ++ */ ++ @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) ++ public volatile @Nullable BaseTaskQueueTier highestTierOfTaskOnStack; ++ ++ /** ++ * The {@link BaseTaskQueueTier} that the last non-null return value of {@link #pollTask} was polled from, ++ * or null if {@link #pollTask} has never been called yet. ++ */ ++ @ThisThreadOnly ++ private @Nullable BaseTaskQueueTier lastPolledTaskTier; + + /** + * The lock to guard this thread's sleeping and waking actions. + */ -+ protected final Lock waitLock = new ReentrantLock(); ++ private final Lock waitLock = new ReentrantLock(); + + /** + * The condition to wait for a signal, when this thread has to wait for something to do. + */ -+ protected final Condition waitCondition = waitLock.newCondition(); ++ private final Condition waitCondition = waitLock.newCondition(); + + /** + * Whether this thread is currently not working on the content of a task, but instead + * attempting to poll a next task to do, checking whether it can accept tasks at all, or -+ * attempting to acquire a {@link YieldingLock}. ++ * attempting to acquire a {@link YieldingLock}, or waiting (although the fact that this value is true during ++ * waiting is irrelevant, because at such a time, {@link #isWaiting} will be true, and this value will no longer ++ * have any effect due to the implementation of {@link #signal}). + *
-+ * This value is used to determine whether to set {@link #skipNextWait} when {@link #signal} is called. ++ * This value is used to determine whether to set {@link #skipNextWait} when {@link #signal} is called ++ * and {@link #isWaiting} is false. + */ + @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) + private volatile boolean isPollingTaskOrCheckingStopCondition = true; @@ -4312,20 +4895,48 @@ index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea + * but instead try polling a task again. + */ + @AnyThreadSafe -+ private volatile boolean skipNextWait = false; ++ public volatile boolean skipNextWait = false; + + /** + * Whether this thread is currently waiting for something to do. ++ *
++ * This is set to true at some point before actually starting to wait in a blocking fashion, ++ * and set to false at some point after no longer waiting in a blocking fashion. So, at some point, ++ * this value may be true while the thread is not blocked yet, or anymore. ++ * Even more so, extra checks for whether the thread should block will be performed in between ++ * the moment this value is set to true and the moment the thread potentially blocks. This means that if the ++ * checks fail, this value may be set to true and then false again, without actually ever blocking. + */ + @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) + @Guarded(value = "#waitLock", fieldAccess = Access.WRITE) + public volatile boolean isWaiting = false; + + /** -+ * If {@link #isWaiting} is true, whether this thread is registered to be signalled when new tasks are added. -+ * This value is meaningless while {@link #isWaiting} is false. ++ * Whether {@link #isWaiting} is irrelevant because this thread has already ++ * been signalled via {@link #signal} to wake up. + */ -+ public volatile boolean isRegisteredForNewTasks; ++ @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) ++ @Guarded(value = "#waitLock", fieldAccess = Access.WRITE) ++ public volatile boolean mayBeStillWaitingButHasBeenSignalled = false; ++ ++ /** ++ * The {@link YieldingLock} that this thread is waiting for, ++ * or null if this thread is not waiting for a {@link YieldingLock}. ++ * This value only has meaning while {@link #isWaiting} is true. ++ */ ++ @AnyThreadSafe(Access.READ) @ThisThreadOnly(Access.WRITE) ++ @Guarded(value = "#waitLock", fieldAccess = Access.WRITE) ++ public volatile @Nullable YieldingLock lockWaitingFor = null; ++ ++ /** ++ * A special flag, used after changing {@link #isWaiting}, when the lock must be temporarily released to ++ * call {@link BaseThreadActivation#callForUpdate()} (to avoid deadlocks in {@link #signal} calls), ++ * and we wish the pool to regard this thread as waiting ++ * (which it will, because {@link #isWaiting} will be true), but we must still ++ * know not to signal the underlying {@link #waitCondition}, but set {@link #skipNextWait} to true, ++ * when {@link #signal} is called at some point during the short release of {@link #waitLock}. ++ */ ++ public volatile boolean isNotActuallyWaitingYet = false; + + /** + * The last reason this thread was signalled before the current poll attempt, or null if the current @@ -4333,70 +4944,76 @@ index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea + */ + public volatile @Nullable SignalReason lastSignalReason = null; + -+ protected BaseYieldingThread(Runnable target, String name, AbstractTaskQueue[] taskQueues, WaitingThreadSet threadsWaitingForYieldingTasks, WaitingThreadSet threadsWaitingForFreeTasks, boolean trackYieldDepth, int maximumYieldDepth) { ++ protected BaseThread(Runnable target, String name, int baseThreadIndex, int maximumYieldDepth) { + super(target, name); -+ this.taskQueues = taskQueues; -+ this.threadsWaitingForYieldingTasks = threadsWaitingForYieldingTasks; -+ this.threadsWaitingForFreeTasks = threadsWaitingForFreeTasks; -+ this.trackYieldDepth = trackYieldDepth; ++ this.baseThreadIndex = baseThreadIndex; + this.maximumYieldDepth = maximumYieldDepth; + } + + /** -+ * To be called when this thread wants to poll a task. ++ * This method is based on {@link #signal}. + * -+ * @return Whether the polling will be allowed to proceed. If false, the thread will start waiting -+ * until signalled by {@link #becomesAllowedToPollSignalReason} or another reason that is not a new task being -+ * added to a queue (for example a {@link YieldingLock} it is trying to acquire being released). ++ * @see #signal + */ -+ protected abstract boolean wantsToPoll(); -+ -+ /** -+ * To be called when this thread could not poll a task and is going to wait. -+ * This is not called when the thread goes to sleep due to receiving a {@code false} result from -+ * {@link #wantsToPoll}. -+ * -+ * @return Whether this thread should register as waiting for new tasks to be added. -+ */ -+ protected abstract boolean willWaitAfterPollFailure(); -+ -+ /** -+ * To be called when this thread has been signalled by a reason other than -+ * {@link #becomesAllowedToPollSignalReason}, and waking up from {@link Condition#await}. -+ */ -+ protected abstract void wokeUpAfterNonBecomeAllowedToPollSignal(); -+ -+ /** -+ * To be called right before this thread will begin to block. -+ */ -+ protected abstract void preBlockThread(); -+ -+ /** -+ * To be called right after this thread blocked. -+ */ -+ protected abstract void postBlockThread(); -+ -+ @Override -+ public void yieldUntil(@Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock) { -+ if (this.trackYieldDepth) { -+ //noinspection NonAtomicOperationOnVolatileField -+ this.yieldDepth++; -+ if (!this.isRestrictedDueToYieldDepth && this.yieldDepth >= this.maximumYieldDepth) { -+ this.isRestrictedDueToYieldDepth = true; ++ @SuppressWarnings("RedundantIfStatement") ++ public boolean isWaitingAndNeedsSignal() { ++ if (this.isWaiting) { ++ if (this.isNotActuallyWaitingYet) { ++ if (!this.skipNextWait) { ++ return true; ++ } ++ return false; ++ } ++ if (!this.mayBeStillWaitingButHasBeenSignalled) { ++ return true; ++ } ++ } else if (this.isPollingTaskOrCheckingStopCondition) { ++ if (!this.skipNextWait) { ++ return true; + } + } ++ return false; ++ } ++ ++ /** ++ * Yields to tasks: polls and executes tasks while possible and the stop condition is not met. ++ * The stop condition is met if {@code stopCondition} is not null and returns true, or alternatively, ++ * if {@code stopCondition} is null, and {@code yieldingLock} is successfully acquired. ++ * When no tasks can be polled, this thread will block, waiting for either a task that can be executed by this ++ * thread to become available, or for the {@code yieldingLock}, if given, to be released. ++ *
++ * Exactly one of {@code stopCondition} and {@code yieldingLock} must be non-null. ++ */ ++ @ThisThreadOnly ++ @PotentiallyYielding("this method is meant to yield") ++ public final void yieldUntil(@Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock) { ++ //noinspection NonAtomicOperationOnVolatileField ++ this.yieldDepth++; ++ if (this.canStartYieldingTasks && this.yieldDepth >= this.maximumYieldDepth) { ++ this.canStartYieldingTasks = false; ++ } + this.runTasksUntil(stopCondition, yieldingLock); -+ if (this.trackYieldDepth) { -+ //noinspection NonAtomicOperationOnVolatileField -+ this.yieldDepth--; -+ if (this.isRestrictedDueToYieldDepth && this.yieldDepth < this.maximumYieldDepth) { -+ this.isRestrictedDueToYieldDepth = false; -+ } ++ //noinspection NonAtomicOperationOnVolatileField ++ this.yieldDepth--; ++ if (!this.canStartYieldingTasks && this.yieldDepth < this.maximumYieldDepth) { ++ this.canStartYieldingTasks = true; + } + } + -+ @Override -+ public void runTasksUntil(@Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock) { ++ /** ++ * This method will keep attempting to find a task to do, and execute it, and if none is found, start waiting ++ * until signalled by {@link BaseThreadPool} or by a {@link YieldingLock}. ++ * The loop is broken as soon as the stop condition becomes true, or the given lock is successfully acquired. ++ *
++ * The above is the same as {@link #yieldUntil}, except it may be called in situations that is not 'yielding', ++ * for instance the endless loop polling tasks performed by a n{@link AssistThread}. The difference with ++ * {@link #yieldUntil} is that this method does not increment or decrement things the yield depth of this thread. ++ * ++ * @see #yieldUntil ++ */ ++ @ThisThreadOnly ++ @PotentiallyYielding("may yield further if an executed task is potentially yielding") ++ public final void runTasksUntil(@Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock) { + this.isPollingTaskOrCheckingStopCondition = true; + + /* @@ -4422,14 +5039,7 @@ index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea + } + } + -+ // Check if this thread is allowed to poll -+ if (!wantsToPoll()) { -+ // If not, the thread will wait until it is allowed, and then try the loop again -+ this.waitUntilSignalled(yieldingLock, false, false); -+ continue; -+ } -+ -+ // If this is the server thread, update isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking ++ // If this is the original server thread, update isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking + if (this == MinecraftServer.serverThread) { + MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking = MinecraftServer.isInSpareTime && MinecraftServer.blockingCount == 0 && !MinecraftServer.SERVER.haveTime(); + } @@ -4441,29 +5051,63 @@ index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea + if (task != null) { + + // If this is the server thread, potentially set nextTimeAssumeWeMayHaveDelayedTasks to true -+ if (this == MinecraftServer.serverThread && !MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks && AbstractTaskQueue.taskQueuesHaveTasks(this.taskQueues)) { ++ if (this == MinecraftServer.serverThread && !MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks && AbstractTaskQueue.taskQueuesHaveTasks(BaseTaskQueueTier.SERVER.taskQueues)) { + MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks = true; + } + ++ // Update highestTierOfTaskOnStack and the thread priority ++ var highestTierBeforeTask = this.highestTierOfTaskOnStack; ++ var threadPriorityBeforeTask = this.getPriority(); ++ //noinspection DataFlowIssue ++ var newHighestTier = highestTierBeforeTask == null ? this.lastPolledTaskTier : highestTierBeforeTask.ordinal < this.lastPolledTaskTier.ordinal ? highestTierBeforeTask : this.lastPolledTaskTier; ++ //noinspection DataFlowIssue ++ var newThreadPriority = newHighestTier.threadPriority; ++ if (newHighestTier != highestTierBeforeTask) { ++ this.highestTierOfTaskOnStack = newHighestTier; ++ BaseThreadActivation.callForUpdate(); ++ if (threadPriorityBeforeTask != newThreadPriority) { ++ this.setPriority(newThreadPriority); ++ } ++ } ++ + this.isPollingTaskOrCheckingStopCondition = false; + task.run(); + + // If this is the server thread, execute some chunk tasks + if (this == MinecraftServer.serverThread) { ++ if (newHighestTier != BaseTaskQueueTier.SERVER) { ++ newHighestTier = BaseTaskQueueTier.SERVER; ++ this.highestTierOfTaskOnStack = newHighestTier; ++ BaseThreadActivation.callForUpdate(); ++ if (newThreadPriority != newHighestTier.threadPriority) { ++ newThreadPriority = newHighestTier.threadPriority; ++ this.setPriority(newThreadPriority); ++ } ++ } + MinecraftServer.SERVER.executeMidTickTasks(); // Paper - execute chunk tasks mid tick + } + ++ // Reset highestTierOfTaskOnStack and the thread priority ++ if (newHighestTier != highestTierBeforeTask) { ++ this.highestTierOfTaskOnStack = highestTierBeforeTask; ++ BaseThreadActivation.callForUpdate(); ++ if (threadPriorityBeforeTask != newThreadPriority) { ++ this.setPriority(threadPriorityBeforeTask); ++ } ++ } ++ + this.isPollingTaskOrCheckingStopCondition = true; + continue; + + } + + /* -+ If no task that can be started was found, wait for one to become available, ++ If no task that can be started by this thread was found, wait for a task that we are allowed ++ to poll to become available (when that happens, the BaseThreadPool will signal this thread), + or for the given yielding lock to be released. This is the only time we should ever block inside + a potentially yielding procedure. + */ -+ this.waitUntilSignalled(yieldingLock, true, true); ++ this.waitUntilSignalled(yieldingLock); + + } + @@ -4471,16 +5115,61 @@ index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea + + /* + If the thread was signalled for another reason than the lock, but we acquired the lock instead, -+ another thread should be signalled. ++ another thread should be signalled for that reason. + */ + SignalReason lastSignalReason = this.lastSignalReason; -+ if (lastSignalReason != null && yieldingLock != null && lastSignalReason != yieldingLock.getSignalReason()) { -+ lastSignalReason.signalAnother(); ++ if (lastSignalReason != null && yieldingLock != null && lastSignalReason != SignalReason.YIELDING_LOCK) { ++ BaseThreadActivation.callForUpdate(); + } + + } + + /** ++ * @see #pollTask() ++ */ ++ @ThisThreadOnly ++ @YieldFree ++ private @Nullable Runnable pollTaskFromTier(BaseTaskQueueTier tier, boolean tinyOnly) { ++ for (var queue : tier.taskQueues) { ++ Runnable task = tinyOnly ? queue.pollTiny(this) : queue.poll(this); ++ if (task != null) { ++ /* ++ Check if the tier has run out of tasks for a span, ++ in order to update BaseThreadActivation#thereMayBeTasks. ++ */ ++ for (int spanI = 0; spanI < TaskSpan.length; spanI++) { ++ TaskSpan span = TaskSpan.VALUES[spanI]; ++ if (queue.canHaveTasks(span)) { ++ int oldTasks = BaseThreadActivation.thereMayBeTasks[tier.ordinal][spanI].get(); ++ if (oldTasks > 0) { ++ if (!queue.hasTasks(span)) { ++ boolean tierHasNoTasksForSpan = true; ++ for (AbstractTaskQueue otherTierQueue : tier.taskQueues) { ++ // We already know there are no tasks in this queue ++ if (otherTierQueue == queue) { ++ continue; ++ } ++ if (otherTierQueue.hasTasks(span)) { ++ tierHasNoTasksForSpan = false; ++ break; ++ } ++ } ++ if (tierHasNoTasksForSpan) { ++ // Set thereMayBeTasks to false, but only if it did not change in the meantime ++ BaseThreadActivation.thereMayBeTasks[tier.ordinal][spanI].compareAndSet(oldTasks, 0); ++ } ++ } ++ } ++ } ++ } ++ this.lastPolledTaskTier = tier; ++ return task; ++ } ++ } ++ return null; ++ } ++ ++ /** + * Polls a task from any queue this thread can currently poll from, and returns it. + * Polling potentially yielding tasks is attempted before yield-free tasks. + * @@ -4488,12 +5177,58 @@ index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea + */ + @ThisThreadOnly + @YieldFree -+ protected @Nullable Runnable pollTask() { -+ for (var queue : this.taskQueues) { -+ Runnable task = queue.poll(this); ++ private @Nullable Runnable pollTask() { ++ /* ++ * If this is a server thread, poll from SERVER, and poll tiny tasks from other tiers. ++ * Note that when polling on the ServerThread, we do not check whether we would be allowed to do so ++ * by the BaseThreadPool, as we consider keeping the ServerThread in the Thread.State.RUNNABLE state for ++ * as long as possible to be more important than the off-chance of for example starting a TINY ASYNC task ++ * on the server thread while no ASYNC tasks are allowed to be polled by other threads at the moment. ++ */ ++ if (this instanceof ServerThread) { ++ // Poll from the SERVER queues ++ Runnable task = this.pollTaskFromTier(BaseTaskQueueTier.SERVER, false); + if (task != null) { + return task; + } ++ // Poll tiny tasks from other tiers ++ for (var tier : BaseTaskQueueTier.VALUES_EXCEPT_SERVER) { ++ task = this.pollTaskFromTier(tier, true); ++ if (task != null) { ++ return task; ++ } ++ } ++ // We failed to poll any task ++ return null; ++ } ++ // If this is not a server thread, poll from all queues except SERVER ++ for (var tier : BaseTaskQueueTier.VALUES_EXCEPT_SERVER) { ++ /* ++ Make sure that we are allowed to poll from the tier, according to the presence of an excess number of ++ threads working on tasks from that tier during the last BaseThreadActivation#update call. ++ In the case this check's result is too optimistic, and a task is started when ideally it wouldn't have been, ++ then so be it - it is not terrible. Whenever this happens, enough threads will surely be allocated ++ by the BaseThreadPool for the task tier that is more in demand anyway, so it does not matter much. ++ In the case this check's result is too pessimistic, the polling fails and this thread will start to sleep, ++ but before doing this, will make a call to BaseThreadActivation#callForUpdate that re-activated this ++ thread if necessary, so no harm is done. ++ In the case this check causes this thread to go to sleep, the call to BaseThreadActivation#callForUpdate ++ while isWaiting is true will make sure the BaseThreadPool has the ability to correctly activate a ++ different thread (that is able to start tasks of a higher tier) if needed. ++ Here, we do not even make an exception for TINY tasks, since there may already be ongoing avoidable ++ context-switching due to excess threads that we can solve by letting this thread go to sleep. ++ */ ++ if (tier.ordinal < BaseThreadActivation.tierInExcessOrdinal) { ++ /* ++ Tasks of a certain tier may yield to tasks of the same or a higher ++ tier, and they may also yield to tiny tasks of a lower tier. ++ */ ++ var tierYieldingFrom = this.highestTierOfTaskOnStack; ++ Runnable task = this.pollTaskFromTier(tier, tierYieldingFrom != null && tier.ordinal > tierYieldingFrom.ordinal); ++ if (task != null) { ++ return task; ++ } ++ } + } + // We failed to poll any task + return null; @@ -4504,28 +5239,27 @@ index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea + * + * @param yieldingLock A {@link YieldingLock} to register with, or null if this thread is not waiting for + * a yielding lock. -+ * @param registerForTasks Whether to register to wait for new tasks being added to the task queues. -+ * @param callWillWait Whether to call {@link #willWaitAfterPollFailure()} + */ + @ThisThreadOnly + @PotentiallyBlocking -+ private void waitUntilSignalled(@Nullable YieldingLock yieldingLock, boolean registerForTasks, boolean callWillWait) { ++ private void waitUntilSignalled(@Nullable YieldingLock yieldingLock) { + -+ if (callWillWait) { -+ if (!this.willWaitAfterPollFailure()) { -+ registerForTasks = false; ++ // Remember whether we registered to wait with the lock, to unregister later ++ // Register this thread with the lock if necessary ++ boolean registeredAsWaitingWithLock = false; ++ if (yieldingLock != null) { ++ // No point in registering if we're not going to wait anyway ++ if (!this.skipNextWait) { ++ yieldingLock.incrementWaitingThreads(); ++ registeredAsWaitingWithLock = true; + } + } + -+ // Remember whether we registered to wait, to unregister later -+ boolean registeredAsWaiting = false; -+ this.isRegisteredForNewTasks = false; -+ // No point in registering if we're not going to wait anyway -+ if (!this.skipNextWait) { -+ // Register this thread with parties that may signal it -+ this.registerAsWaiting(yieldingLock, registerForTasks); -+ registeredAsWaiting = true; -+ } ++ /* ++ Remember whether we changed anything that requires a BaseThreadPool#update call ++ (after the last call to that method). ++ */ ++ boolean mustCallPoolUpdateAtEnd = false; + + /* + If we cannot acquire the lock, we can assume this thread is being signalled, @@ -4539,206 +5273,124 @@ index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea + break waitWithLock; + } + -+ // Do a quick last check to not wait if a new task that can be polled was added in the meantime -+ if (registerForTasks && AbstractTaskQueue.taskQueuesHaveTasksCouldStart(this.taskQueues, this)) { -+ break waitWithLock; -+ } -+ -+ // Register as waiting -+ if (!registeredAsWaiting) { -+ this.registerAsWaiting(yieldingLock, registerForTasks); -+ registeredAsWaiting = true; -+ } -+ + // Mark this thread as waiting ++ this.lockWaitingFor = yieldingLock; ++ this.mayBeStillWaitingButHasBeenSignalled = false; + this.isWaiting = true; -+ -+ // Wait -+ try { -+ -+ /* -+ Check if we should wait with a timeout: this only happens if this thread is the server thread, in -+ which case we do not want to wait past the start of the next tick. -+ */ -+ boolean waitedWithTimeout = false; -+ if (this == MinecraftServer.serverThread) { -+ // -1 indicates to not use a timeout (this value is not later set to any other negative value) -+ long waitForNanos = -1; -+ if (MinecraftServer.isWaitingUntilNextTick) { -+ /* -+ During waiting until the next tick, we wait until the next tick start. -+ If it already passed, we do not have to use a timeout, because we will be notified -+ when the stop condition becomes true. -+ */ -+ waitForNanos = MinecraftServer.nextTickStartNanoTime - System.nanoTime(); -+ if (waitForNanos < 0) { -+ waitForNanos = -1; -+ } -+ } else if (MinecraftServer.SERVER.isOversleep) { -+ /* -+ During this phase, MinecraftServer#mayHaveDelayedTasks() is checked, and we may not -+ be notified when it changes. Therefore, if the next tick start has not passed, we will -+ wait until then, but if it has, we wait for a short interval to make sure we keep -+ checking the stop condition (but not for longer than until the last time we can be -+ executing extra delayed tasks). -+ */ -+ waitForNanos = MinecraftServer.nextTickStartNanoTime - System.nanoTime(); -+ if (waitForNanos < 0) { -+ waitForNanos = Math.min(Math.max(0, MinecraftServer.delayedTasksMaxNextTickNanoTime - System.nanoTime()), SERVER_THREAD_WAIT_NANOS_DURING_OVERSLEEP_WITH_DELAYED_TASKS); -+ } -+ } -+ if (waitForNanos >= 0) { -+ // Set the last signal reason to null in case the timeout elapses without a signal -+ this.lastSignalReason = null; -+ // Wait, but at most for the determined time -+ waitedWithTimeout = true; -+ // Skip if the time is too short -+ if (waitForNanos >= SERVER_THREAD_WAIT_NANOS_MINIMUM) { -+ this.preBlockThread(); -+ //noinspection ResultOfMethodCallIgnored -+ this.waitCondition.await(waitForNanos, TimeUnit.NANOSECONDS); -+ this.postBlockThread(); -+ } -+ } -+ } -+ -+ /* -+ If we did not wait with a timeout, wait indefinitely. If this thread is the server thread, -+ and the intended start time of the next tick has already passed, but the stop condition to stop -+ running tasks is still not true, this thread must be signalled when a change in conditions causes -+ the stop condition to become true. -+ */ -+ if (!waitedWithTimeout) { -+ this.preBlockThread(); -+ this.waitCondition.await(); -+ this.postBlockThread(); -+ } -+ -+ } catch (InterruptedException e) { -+ throw new IllegalStateException(e); -+ } -+ -+ // Unmark this thread as waiting -+ this.isWaiting = false; ++ // But actually we are not waiting yet, signal has no effect yet during the next short lock release ++ this.isNotActuallyWaitingYet = true; + + } finally { + this.waitLock.unlock(); + } ++ ++ // Update the pool ++ BaseThreadActivation.callForUpdate(); ++ ++ /* ++ If we cannot acquire the lock, we can assume this thread is being signalled, ++ so there is no reason to start waiting. ++ */ ++ if (this.waitLock.tryLock()) { ++ try { ++ ++ // We passed the short lock release ++ this.isNotActuallyWaitingYet = false; ++ ++ // If it was set that this thread should skip the wait in the meantime, skip it ++ if (this.skipNextWait) { ++ this.isWaiting = false; ++ this.lockWaitingFor = null; ++ mustCallPoolUpdateAtEnd = true; ++ break waitWithLock; ++ } ++ ++ // Wait ++ try { ++ ++ /* ++ Check if we should wait with a timeout: this only happens if this thread is the server thread, in ++ which case we do not want to wait past the start of the next tick. ++ */ ++ boolean waitedWithTimeout = false; ++ if (this == MinecraftServer.serverThread) { ++ // -1 indicates to not use a timeout (this value is not later set to any other negative value) ++ long waitForNanos = -1; ++ if (MinecraftServer.isWaitingUntilNextTick) { ++ /* ++ During waiting until the next tick, we wait until the next tick start. ++ If it already passed, we do not have to use a timeout, because we will be notified ++ when the stop condition becomes true. ++ */ ++ waitForNanos = MinecraftServer.nextTickStartNanoTime - System.nanoTime(); ++ if (waitForNanos < 0) { ++ waitForNanos = -1; ++ } ++ } else if (MinecraftServer.SERVER.isOversleep) { ++ /* ++ During this phase, MinecraftServer#mayHaveDelayedTasks() is checked, and we may not ++ be notified when it changes. Therefore, if the next tick start has not passed, we will ++ wait until then, but if it has, we wait for a short interval to make sure we keep ++ checking the stop condition (but not for longer than until the last time we can be ++ executing extra delayed tasks). ++ */ ++ waitForNanos = MinecraftServer.nextTickStartNanoTime - System.nanoTime(); ++ if (waitForNanos < 0) { ++ waitForNanos = Math.min(Math.max(0, MinecraftServer.delayedTasksMaxNextTickNanoTime - System.nanoTime()), SERVER_THREAD_WAIT_NANOS_DURING_OVERSLEEP_WITH_DELAYED_TASKS); ++ } ++ } ++ if (waitForNanos >= 0) { ++ // Set the last signal reason to null in case the timeout elapses without a signal ++ this.lastSignalReason = null; ++ // Wait, but at most for the determined time ++ waitedWithTimeout = true; ++ // Skip if the time is too short ++ if (waitForNanos >= SERVER_THREAD_WAIT_NANOS_MINIMUM) { ++ //noinspection ResultOfMethodCallIgnored ++ this.waitCondition.await(waitForNanos, TimeUnit.NANOSECONDS); ++ } ++ } ++ } ++ ++ /* ++ If we did not wait with a timeout, wait indefinitely. If this thread is the server thread, ++ and the intended start time of the next tick has already passed, but the stop condition to stop ++ running tasks is still not true, this thread must be signalled when a change in conditions causes ++ the stop condition to become true. ++ */ ++ if (!waitedWithTimeout) { ++ this.waitCondition.await(); ++ } ++ ++ } catch (InterruptedException e) { ++ throw new IllegalStateException(e); ++ } ++ ++ // Unmark this thread as waiting ++ this.isWaiting = false; ++ this.lockWaitingFor = null; ++ mustCallPoolUpdateAtEnd = true; ++ ++ } finally { ++ this.waitLock.unlock(); ++ } ++ } ++ + } + -+ if (this.lastSignalReason != becomesAllowedToPollSignalReason) { -+ this.wokeUpAfterNonBecomeAllowedToPollSignal(); -+ } -+ -+ // Unregister this thread from the parties it was registered with before -+ if (registeredAsWaiting) { -+ this.unregisterAsWaiting(yieldingLock); ++ // Unregister this thread from the lock if necessary ++ if (registeredAsWaitingWithLock) { ++ yieldingLock.decrementWaitingThreads(); + } + + // Reset skipping the next wait + this.skipNextWait = false; + -+ } ++ // Update the pool if necessary ++ if (mustCallPoolUpdateAtEnd) { ++ BaseThreadActivation.callForUpdate(); ++ } + -+ /** -+ * Registers this thread in various places that will help to identify this thread as a candidate to signal -+ * in case of changes that allow this thread to continue work. -+ * -+ * @param yieldingLock A {@link YieldingLock} to register with, or null if this thread is not waiting for -+ * a yielding lock. -+ * @param registerForTasks Whether to register to wait for new tasks being added to the task queues. -+ */ -+ @ThisThreadOnly -+ @YieldFree -+ private void registerAsWaiting(@Nullable YieldingLock yieldingLock, boolean registerForTasks) { -+ // Register with the queues that we may poll tasks from -+ if (registerForTasks && !this.isRegisteredForNewTasks) { -+ this.registerAsWaitingForTasks(false); -+ } -+ // Register with the lock -+ if (yieldingLock != null) { -+ yieldingLock.addWaitingThread(this); -+ } -+ } -+ -+ /** -+ * Unregisters this thread as waiting for new tasks be added. -+ *
-+ * This must either be called from this thread itself while {@link #isWaiting} is false, -+ * with {@code mustAcquireLock} given as false, -+ * or be called from any thread while {@link #isWaiting} is true, -+ * with {@code mustAcquireLock} given as true. -+ *
-+ * This must only be called when {@link #isRegisteredForNewTasks} is false. -+ * -+ * @see #registerAsWaiting -+ */ -+ @YieldFree -+ public void registerAsWaitingForTasks(boolean mustAcquireLock) { -+ if (mustAcquireLock) { -+ //noinspection StatementWithEmptyBody -+ while (!this.waitLock.tryLock()); -+ } -+ try { -+ this.threadsWaitingForYieldingTasks.add(this); -+ if (!this.isRestrictedDueToYieldDepth) { -+ this.threadsWaitingForFreeTasks.add(this); -+ } -+ this.isRegisteredForNewTasks = true; -+ } finally { -+ if (mustAcquireLock) { -+ this.waitLock.unlock(); -+ } -+ } -+ } -+ -+ /** -+ * Unregisters this thread from the places it was registered with in {@link #registerAsWaiting}. -+ * -+ * @see #registerAsWaiting -+ */ -+ @ThisThreadOnly -+ @YieldFree -+ private void unregisterAsWaiting(@Nullable YieldingLock yieldingLock) { -+ // Unregister from the task queues -+ if (this.isRegisteredForNewTasks) { -+ this.unregisterAsWaitingForTasks(false); -+ } -+ // Unregister from the lock -+ if (yieldingLock != null) { -+ yieldingLock.removeWaitingThread(this); -+ } -+ } -+ -+ /** -+ * Unregisters this thread as waiting for new tasks be added. -+ *
-+ * This must either be called from this thread itself while {@link #isWaiting} is false, -+ * with {@code mustAcquireLock} given as false, -+ * or be called from any thread while {@link #isWaiting} is true, -+ * with {@code mustAcquireLock} given as true. -+ *
-+ * This must only be called when {@link #isRegisteredForNewTasks} is true. -+ * -+ * @see #unregisterAsWaiting -+ */ -+ @YieldFree -+ public void unregisterAsWaitingForTasks(boolean mustAcquireLock) { -+ if (mustAcquireLock) { -+ //noinspection StatementWithEmptyBody -+ while (!this.waitLock.tryLock()); -+ } -+ try { -+ this.threadsWaitingForYieldingTasks.remove(this); -+ if (!this.isRestrictedDueToYieldDepth) { -+ this.threadsWaitingForFreeTasks.remove(this); -+ } -+ this.isRegisteredForNewTasks = false; -+ } finally { -+ if (mustAcquireLock) { -+ this.waitLock.unlock(); -+ } -+ } + } + + /** @@ -4753,70 +5405,31 @@ index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea + */ + @AnyThreadSafe + @YieldFree -+ public boolean signal(@Nullable SignalReason reason) { ++ public final boolean signal(@Nullable SignalReason reason) { + //noinspection StatementWithEmptyBody -+ while (!this.waitLock.tryLock()); ++ while (!this.waitLock.tryLock()); // TODO Gale use a wait-free system here by using a sort of leave-a-message-at-the-door Atomic class system + try { + if (this.isWaiting) { -+ this.lastSignalReason = reason; -+ this.waitCondition.signal(); -+ return true; -+ } else if (this.isPollingTaskOrCheckingStopCondition && !this.skipNextWait) { -+ this.lastSignalReason = reason; -+ this.skipNextWait = true; -+ return true; -+ } -+ return false; -+ } finally { -+ this.waitLock.unlock(); -+ } -+ } -+ -+ /** -+ * Signals this thread to wake up. Has no effect if {@link #isWaiting} is false. -+ * -+ * @param reason The reason why this thread was signalled, or null if it is irrelevant (e.g. when the signal -+ * will never need to be repeated because there is only thread waiting for this specific event -+ * to happen). -+ * @return Whether this thread was sleeping before, and has woken up now. -+ */ -+ @AnyThreadSafe -+ @YieldFree -+ public boolean signalIfWaiting(@Nullable SignalReason reason) { -+ //noinspection StatementWithEmptyBody -+ while (!this.waitLock.tryLock()); -+ try { -+ if (this.isWaiting) { -+ this.lastSignalReason = reason; -+ this.waitCondition.signal(); -+ return true; -+ } -+ return false; -+ } finally { -+ this.waitLock.unlock(); -+ } -+ } -+ -+ /** -+ * Allows this thread to poll, either by registering it to listen for tasks if it was not registered, -+ * or by signalling it if tasks that can be started are already present. -+ * -+ * @return Whether this thread was not active before, and is active now. -+ */ -+ @AnyThreadSafe -+ @YieldFree -+ public boolean activateIfNotListeningForTasksAndNotRestricted() { -+ //noinspection StatementWithEmptyBody -+ while (!this.waitLock.tryLock()); -+ try { -+ if (this.isWaiting && !this.isRegisteredForNewTasks && !this.isRestrictedDueToYieldDepth) { -+ if (AbstractTaskQueue.taskQueuesHaveTasks(this.taskQueues)) { -+ this.lastSignalReason = becomesAllowedToPollSignalReason; -+ this.waitCondition.signal(); -+ } else { -+ this.registerAsWaitingForTasks(false); ++ if (this.isNotActuallyWaitingYet) { ++ if (!this.skipNextWait) { ++ this.lastSignalReason = reason; ++ this.skipNextWait = true; ++ return true; ++ } ++ return false; ++ } ++ if (!this.mayBeStillWaitingButHasBeenSignalled) { ++ this.lastSignalReason = reason; ++ this.mayBeStillWaitingButHasBeenSignalled = true; ++ this.waitCondition.signal(); ++ return true; ++ } ++ } else if (this.isPollingTaskOrCheckingStopCondition) { ++ if (!this.skipNextWait) { ++ this.lastSignalReason = reason; ++ this.skipNextWait = true; ++ return true; + } -+ return true; + } + return false; + } finally { @@ -4825,58 +5438,37 @@ index 0000000000000000000000000000000000000000..4502683211202b56bfc0988a74ba63ea + } + + /** -+ * Unregisters this thread to listen for tasks if it was not registered. -+ * -+ * @return Whether this thread was sleeping and registered to listen for tasks before, -+ * and was unregistered to listen for tasks. ++ * @return The current thread if it is a {@link BaseThread}, or null otherwise. + */ ++ @SuppressWarnings("unused") + @AnyThreadSafe + @YieldFree -+ public boolean deactivateIfListeningForTasks() { -+ //noinspection StatementWithEmptyBody -+ while (!this.waitLock.tryLock()); -+ try { -+ if (this.isWaiting && this.isRegisteredForNewTasks) { -+ this.unregisterAsWaitingForTasks(false); -+ return true; -+ } -+ return false; -+ } finally { -+ this.waitLock.unlock(); -+ } -+ } -+ /** -+ * 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(() -> false, null); ++ public static @Nullable BaseThread currentBaseThread() { ++ return Thread.currentThread() instanceof BaseThread baseThread ? baseThread : null; + } + + /** -+ * A method that simply acquires the {@link BaseYieldingThread} that is the current thread, and calls -+ * {@link #runForever()} on it. ++ * @return Whether the current thread is a {@link BaseThread}. + */ -+ @BaseYieldingThreadOnly -+ protected static void getCurrentBaseYieldingThreadAndRunForever() { -+ ((BaseYieldingThread) Thread.currentThread()).runForever(); ++ @SuppressWarnings("unused") ++ @AnyThreadSafe ++ @YieldFree ++ public static boolean isBaseThread() { ++ return Thread.currentThread() instanceof BaseThread; + } + +} diff --git a/src/main/java/org/galemc/gale/executor/thread/OriginalServerThread.java b/src/main/java/org/galemc/gale/executor/thread/OriginalServerThread.java new file mode 100644 -index 0000000000000000000000000000000000000000..c44607129a79884b1e4f3d9fb8b57cdfa527d267 +index 0000000000000000000000000000000000000000..ced372b40e8b3a5c43dabf5bb547a71e3c713d2f --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/thread/OriginalServerThread.java -@@ -0,0 +1,21 @@ -+// Gale - base thread pools +@@ -0,0 +1,20 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.thread; + +import net.minecraft.server.MinecraftServer; -+import org.galemc.gale.executor.thread.pooled.ServerThread; +import org.spigotmc.WatchdogThread; + +/** @@ -4892,19 +5484,104 @@ index 0000000000000000000000000000000000000000..c44607129a79884b1e4f3d9fb8b57cdf + } + +} +diff --git a/src/main/java/org/galemc/gale/executor/thread/ServerThread.java b/src/main/java/org/galemc/gale/executor/thread/ServerThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7d58d995d8e74cd5f51f85f123166bf884deed92 +--- /dev/null ++++ b/src/main/java/org/galemc/gale/executor/thread/ServerThread.java +@@ -0,0 +1,51 @@ ++// Gale - base thread pool ++ ++package org.galemc.gale.executor.thread; ++ ++import io.papermc.paper.util.TickThread; ++import net.minecraft.server.MinecraftServer; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import org.spigotmc.WatchdogThread; ++ ++/** ++ * A {@link TickThread} that provides an implementation for {@link BaseThread}, ++ * that is shared between the {@link MinecraftServer#serverThread} and {@link WatchdogThread#instance}. ++ * ++ * @author Martijn Muijsers under AGPL-3.0 ++ */ ++public class ServerThread extends TickThread { ++ ++ protected ServerThread(final String name) { ++ super(name); ++ } ++ ++ protected ServerThread(final Runnable run, final String name) { ++ super(run, name); ++ } ++ ++ /** ++ * This method must not be called while {@link MinecraftServer#isConstructed} is false. ++ * ++ * @return The global {@link ServerThread} instance, which is either ++ * {@link MinecraftServer#serverThread}, or {@link WatchdogThread#instance} while the server is shutting ++ * down and the {@link WatchdogThread} was responsible. ++ */ ++ public static @NotNull ServerThread getInstance() { ++ if (MinecraftServer.SERVER.hasStopped) { ++ if (MinecraftServer.SERVER.shutdownThread == WatchdogThread.instance) { ++ return WatchdogThread.instance; ++ } ++ } ++ return MinecraftServer.serverThread; ++ } ++ ++ /** ++ * @return The same value as {@link #getInstance()} if {@link MinecraftServer#isConstructed} is true, ++ * or null otherwise. ++ */ ++ public static @Nullable ServerThread getInstanceIfConstructed() { ++ return MinecraftServer.isConstructed ? getInstance() : null; ++ } ++ ++} +diff --git a/src/main/java/org/galemc/gale/executor/thread/SignalReason.java b/src/main/java/org/galemc/gale/executor/thread/SignalReason.java +new file mode 100644 +index 0000000000000000000000000000000000000000..436b0a8249290d833472da58ec01f9690be2fb95 +--- /dev/null ++++ b/src/main/java/org/galemc/gale/executor/thread/SignalReason.java +@@ -0,0 +1,23 @@ ++// Gale - base thread pool ++ ++package org.galemc.gale.executor.thread; ++ ++import org.galemc.gale.executor.lock.YieldingLock; ++ ++/** ++ * A reason of a call to {@link SignallableThread#signal}. ++ * ++ * @author Martijn Muijsers under AGPL-3.0 ++ */ ++public enum SignalReason { ++ ++ /** ++ * A task that the signalled thread could poll and start is available. ++ */ ++ TASK, ++ /** ++ * The {@link YieldingLock} that the signalled thread was waiting for was released. ++ */ ++ YIELDING_LOCK ++ ++} diff --git a/src/main/java/org/galemc/gale/executor/thread/SignallableThread.java b/src/main/java/org/galemc/gale/executor/thread/SignallableThread.java new file mode 100644 -index 0000000000000000000000000000000000000000..0d6cf71da1100c51e3b8aaf8e28765bc82e7eeb5 +index 0000000000000000000000000000000000000000..a73aafc64dc60b57e2e5a91565e1aff612da6703 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/thread/SignallableThread.java -@@ -0,0 +1,32 @@ -+// Gale - base thread pools +@@ -0,0 +1,31 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.thread; + +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; +import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.thread.wait.SignalReason; +import org.jetbrains.annotations.Nullable; + +/** @@ -4932,20 +5609,21 @@ index 0000000000000000000000000000000000000000..0d6cf71da1100c51e3b8aaf8e28765bc +} diff --git a/src/main/java/org/galemc/gale/executor/thread/deferral/ServerThreadDeferral.java b/src/main/java/org/galemc/gale/executor/thread/deferral/ServerThreadDeferral.java new file mode 100644 -index 0000000000000000000000000000000000000000..9d02c2f4360bf19f379b7012206b72abffda886a +index 0000000000000000000000000000000000000000..57f672f9f81aeb6ce0ef44fa47db80602b03a5d4 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/thread/deferral/ServerThreadDeferral.java -@@ -0,0 +1,136 @@ -+// Gale - base thread pools +@@ -0,0 +1,151 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.thread.deferral; + +import io.papermc.paper.util.TickThread; +import org.galemc.gale.concurrent.UnterminableExecutorService; ++import org.galemc.gale.executor.TaskSpan; +import org.galemc.gale.executor.queue.BaseTaskQueues; +import org.galemc.gale.executor.thread.AbstractYieldingThread; -+import org.galemc.gale.executor.thread.pooled.ServerThread; -+import org.galemc.gale.executor.thread.pooled.TickAssistThread; ++import org.galemc.gale.executor.thread.BaseThread; ++import org.galemc.gale.executor.thread.ServerThread; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + @@ -4956,7 +5634,7 @@ index 0000000000000000000000000000000000000000..9d02c2f4360bf19f379b7012206b72ab + +/** + * This class provides functionality to allow any thread, -+ * including but not limited to a {@link TickAssistThread}, ++ * including but not limited to a {@link BaseThread}, + * to defer blocks of code to a {@link ServerThread}, and wait for its completion. + *
+ * Using deferral from a {@link TickThread} that is not the correct thread already is highly discouraged @@ -4966,15 +5644,16 @@ index 0000000000000000000000000000000000000000..9d02c2f4360bf19f379b7012206b72ab + * + * @author Martijn Muijsers under AGPL-3.0 + */ ++@SuppressWarnings("unused") +public final class ServerThreadDeferral { + + private ServerThreadDeferral() {} + + /** -+ * @see #defer(Supplier, boolean) ++ * @see #defer(Supplier, TaskSpan) + */ -+ public static void defer(Runnable task, boolean yielding) { -+ deferInternal(task, null, yielding); ++ public static void defer(Runnable task, TaskSpan span) { ++ deferInternal(task, null, span); + } + + /** @@ -4993,17 +5672,17 @@ index 0000000000000000000000000000000000000000..9d02c2f4360bf19f379b7012206b72ab + * If this thread is already an appropriate thread to run the task on, the task is performed on this thread. + * + * @param task The task to run. -+ * @param yielding Whether the task is potentially yielding. ++ * @param span The {@link TaskSpan} of the task. + */ -+ public static T defer(Supplier task, boolean yielding) { -+ return deferInternal(null, task, yielding); ++ public static T defer(Supplier task, TaskSpan span) { ++ return deferInternal(null, task, span); + } + + /** -+ * Common implementation for {@link #defer(Runnable, boolean)} and {@link #defer(Supplier, boolean)}. ++ * Common implementation for {@link #defer(Runnable, TaskSpan)} and {@link #defer(Supplier, TaskSpan)}. + * Exactly one of {@code runnable} or {@code supplier} must be non-null. + */ -+ private static T deferInternal(@Nullable Runnable runnable, @Nullable Supplier supplier, boolean yielding) { ++ private static T deferInternal(@Nullable Runnable runnable, @Nullable Supplier supplier, TaskSpan span) { + // Check if we are the right thread + if (TickThread.isTickThread()) { + if (runnable == null) { @@ -5027,7 +5706,7 @@ index 0000000000000000000000000000000000000000..9d02c2f4360bf19f379b7012206b72ab + future.complete(null); + } + yieldingThread.signal(null); -+ }, yielding); ++ }, span); + yieldingThread.yieldUntil(future::isDone, null); + return future.getNow(null); + } else { @@ -5040,33 +5719,46 @@ index 0000000000000000000000000000000000000000..9d02c2f4360bf19f379b7012206b72ab + runnable.run(); + future.complete(null); + } -+ }, yielding); ++ }, span); + return future.join(); + } + } + + /** -+ * An executor for deferring potentially yielding, tasks to the main thread, ++ * An executor for deferring {@link TaskSpan#YIELDING} tasks to the main thread, + * where {@link Executor#execute} calls {@link #defer}. + */ + public static final ExecutorService yieldingExecutor = new UnterminableExecutorService() { + + @Override + public void execute(@NotNull Runnable runnable) { -+ defer(runnable, true); ++ defer(runnable, TaskSpan.YIELDING); + } + + }; + + /** -+ * An executor for deferring yield-free, tasks to the main thread, ++ * An executor for deferring {@link TaskSpan#FREE} tasks to the main thread, + * where {@link Executor#execute} calls {@link #defer}. + */ + public static final ExecutorService freeExecutor = new UnterminableExecutorService() { + + @Override + public void execute(@NotNull Runnable runnable) { -+ defer(runnable, false); ++ defer(runnable, TaskSpan.FREE); ++ } ++ ++ }; ++ ++ /** ++ * An executor for deferring {@link TaskSpan#TINY} tasks to the main thread, ++ * where {@link Executor#execute} calls {@link #defer}. ++ */ ++ public static final ExecutorService tinyExecutor = new UnterminableExecutorService() { ++ ++ @Override ++ public void execute(@NotNull Runnable runnable) { ++ defer(runnable, TaskSpan.TINY); + } + + }; @@ -5074,11 +5766,11 @@ index 0000000000000000000000000000000000000000..9d02c2f4360bf19f379b7012206b72ab +} diff --git a/src/main/java/org/galemc/gale/executor/thread/deferral/TickThreadDeferral.java b/src/main/java/org/galemc/gale/executor/thread/deferral/TickThreadDeferral.java new file mode 100644 -index 0000000000000000000000000000000000000000..74319026715d9a31e2d34fcba251d6d0e828ffe5 +index 0000000000000000000000000000000000000000..77fe10e51b00115da520cfc211bf84badbc027be --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/thread/deferral/TickThreadDeferral.java -@@ -0,0 +1,143 @@ -+// Gale - base thread pools +@@ -0,0 +1,159 @@ ++// Gale - base thread pool + +package org.galemc.gale.executor.thread.deferral; + @@ -5086,8 +5778,10 @@ index 0000000000000000000000000000000000000000..74319026715d9a31e2d34fcba251d6d0 +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import org.galemc.gale.concurrent.UnterminableExecutorService; ++import org.galemc.gale.executor.TaskSpan; +import org.galemc.gale.executor.thread.AbstractYieldingThread; -+import org.galemc.gale.executor.thread.pooled.TickAssistThread; ++import org.galemc.gale.executor.thread.BaseThread; ++import org.galemc.gale.executor.queue.BaseTaskQueueTier; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Executor; @@ -5096,7 +5790,7 @@ index 0000000000000000000000000000000000000000..74319026715d9a31e2d34fcba251d6d0 + +/** + * This class provides functionality to allow any thread, -+ * including but not limited to a {@link TickAssistThread}, ++ * including but not limited to a {@link BaseThread}, + * to defer blocks of code to a {@link TickThread}, and wait for its completion. + * In other words, instead of the typical paradigm where a block of code is executed + * while a lock is held by the thread, we do not acquire a lock, but instead schedule the code @@ -5107,7 +5801,7 @@ index 0000000000000000000000000000000000000000..74319026715d9a31e2d34fcba251d6d0 + * we can only make limited assumptions) and wait for that to finish. + *
+ * This has a number of advantages. -+ * When we require running code that checks whether it is being run on an appropriate @link TickThread}, ++ * When we require running code that checks whether it is being run on an appropriate {@link TickThread}, + * we can run it this way. Since these parts of code are always performed on a {@link TickThread} + * regardless of the thread requesting them to be run, there is no chance of deadlock occurring + * from two different locks being desired in a different order on two of the original threads @@ -5123,52 +5817,53 @@ index 0000000000000000000000000000000000000000..74319026715d9a31e2d34fcba251d6d0 + * + * @author Martijn Muijsers under AGPL-3.0 + */ ++@SuppressWarnings("unused") +public final class TickThreadDeferral { + + private TickThreadDeferral() {} + + /** -+ * This may be useful in the future. See the documentation of {@link TickThread#taskQueues}. ++ * This may be useful in the future. See the documentation of {@link BaseTaskQueueTier#SERVER}. + * -+ * @see #defer(Runnable, boolean) ++ * @see #defer(Runnable, TaskSpan) + */ -+ public static void defer(final ServerLevel world, final int chunkX, final int chunkZ, Runnable task, boolean yielding) { -+ defer(task, yielding); ++ public static void defer(final ServerLevel world, final int chunkX, final int chunkZ, Runnable task, TaskSpan span) { ++ defer(task, span); + } + + /** -+ * This may be useful in the future. See the documentation of {@link TickThread#taskQueues}. ++ * This may be useful in the future. See the documentation of {@link BaseTaskQueueTier#SERVER}. + * -+ * @see #defer(Supplier, boolean) ++ * @see #defer(Supplier, TaskSpan) + */ -+ public static T defer(final ServerLevel world, final int chunkX, final int chunkZ, Supplier task, boolean yielding) { -+ return defer(task, yielding); ++ public static T defer(final ServerLevel world, final int chunkX, final int chunkZ, Supplier task, TaskSpan span) { ++ return defer(task, span); + } + + /** -+ * This may be useful in the future. See the documentation of {@link TickThread#taskQueues}. ++ * This may be useful in the future. See the documentation of {@link BaseTaskQueueTier#SERVER}. + * -+ * @see #defer(Runnable, boolean) ++ * @see #defer(Runnable, TaskSpan) + */ -+ public static void defer(final Entity entity, Runnable task, boolean yielding) { -+ defer(task, yielding); ++ public static void defer(final Entity entity, Runnable task, TaskSpan span) { ++ defer(task, span); + } + + /** -+ * This may be useful in the future. See the documentation of {@link TickThread#taskQueues}. ++ * This may be useful in the future. See the documentation of {@link BaseTaskQueueTier#SERVER}. + * -+ * @see #defer(Supplier, boolean) ++ * @see #defer(Supplier, TaskSpan) + */ -+ public static T defer(final Entity entity, Supplier task, boolean yielding) { -+ return defer(task, yielding); ++ public static T defer(final Entity entity, Supplier task, TaskSpan span) { ++ return defer(task, span); + } + + /** -+ * @see #defer(Supplier, boolean) ++ * @see #defer(Supplier, TaskSpan) + */ -+ public static void defer(Runnable task, boolean yielding) { ++ public static void defer(Runnable task, TaskSpan span) { + // Current implementation uses ServerThreadDeferral -+ ServerThreadDeferral.defer(task, yielding); ++ ServerThreadDeferral.defer(task, span); + } + + /** @@ -5187,942 +5882,645 @@ index 0000000000000000000000000000000000000000..74319026715d9a31e2d34fcba251d6d0 + * If this thread is already an appropriate thread to run the task on, the task is performed on this thread. + * + * @param task The task to run. -+ * @param yielding Whether the task is potentially yielding. ++ * @param span The {@link TaskSpan} of the task. + */ -+ public static T defer(Supplier task, boolean yielding) { ++ public static T defer(Supplier task, TaskSpan span) { + // Current implementation uses ServerThreadDeferral -+ return ServerThreadDeferral.defer(task, yielding); ++ return ServerThreadDeferral.defer(task, span); + } + + /** -+ * An executor for deferring potentially yielding, tasks to the main thread, ++ * An executor for deferring {@link TaskSpan#YIELDING} tasks to the main thread, + * where {@link Executor#execute} calls {@link #defer}. + */ + public static final ExecutorService yieldingExecutor = new UnterminableExecutorService() { + + @Override + public void execute(@NotNull Runnable runnable) { -+ defer(runnable, true); ++ defer(runnable, TaskSpan.YIELDING); + } + + }; + + /** -+ * An executor for deferring yield-free, tasks to the main thread, ++ * An executor for deferring {@link TaskSpan#FREE} tasks to the main thread, + * where {@link Executor#execute} calls {@link #defer}. + */ + public static final ExecutorService freeExecutor = new UnterminableExecutorService() { + + @Override + public void execute(@NotNull Runnable runnable) { -+ defer(runnable, false); ++ defer(runnable, TaskSpan.FREE); ++ } ++ ++ }; ++ ++ /** ++ * An executor for deferring {@link TaskSpan#TINY} tasks to the main thread, ++ * where {@link Executor#execute} calls {@link #defer}. ++ */ ++ public static final ExecutorService tinyExecutor = new UnterminableExecutorService() { ++ ++ @Override ++ public void execute(@NotNull Runnable runnable) { ++ defer(runnable, TaskSpan.TINY); + } + + }; + +} -diff --git a/src/main/java/org/galemc/gale/executor/thread/pooled/AbstractYieldingSignallableThreadPool.java b/src/main/java/org/galemc/gale/executor/thread/pooled/AbstractYieldingSignallableThreadPool.java +diff --git a/src/main/java/org/galemc/gale/executor/thread/pool/BaseThreadActivation.java b/src/main/java/org/galemc/gale/executor/thread/pool/BaseThreadActivation.java new file mode 100644 -index 0000000000000000000000000000000000000000..b939e9d227650dc2d321cfe3abfaaff3ed1801d6 +index 0000000000000000000000000000000000000000..4c5600945e57525a21fa0a39c6b627e9794afad7 --- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/pooled/AbstractYieldingSignallableThreadPool.java -@@ -0,0 +1,31 @@ -+// Gale - base thread pools ++++ b/src/main/java/org/galemc/gale/executor/thread/pool/BaseThreadActivation.java +@@ -0,0 +1,506 @@ ++// Gale - base thread pool + -+package org.galemc.gale.executor.thread.pooled; ++package org.galemc.gale.executor.thread.pool; + -+import org.galemc.gale.executor.thread.AbstractYieldingThread; -+import org.galemc.gale.executor.thread.wait.SignalReason; -+import org.galemc.gale.executor.thread.wait.WaitingThreadSet; -+ -+/** -+ * An interface for thread pools of {@link AbstractYieldingThread}s. -+ * This thread pool can be signalled when new tasks for its threads are added. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+public interface AbstractYieldingSignallableThreadPool { -+ -+ /** -+ * Attempts to signal one thread in this pool that is waiting for new tasks. -+ * -+ * @return Whether a thread was signalled (this return value is always accurate). -+ * -+ * @see WaitingThreadSet#pollAndSignal -+ */ -+ boolean signalNewTasks(SignalReason reason,boolean yielding); -+ -+ /** -+ * @return The number of threads in this pool that are in the {@link Thread.State#RUNNABLE} state. -+ */ -+ int getRunnableThreadCount(); -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/pooled/AsyncThread.java b/src/main/java/org/galemc/gale/executor/thread/pooled/AsyncThread.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7fff5afd87ab9d4aeb0c80d0fced6098569ad1bb ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/pooled/AsyncThread.java -@@ -0,0 +1,39 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.pooled; -+ -+import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; ++import net.minecraft.server.MinecraftServer; ++import org.galemc.gale.executor.TaskSpan; ++import org.galemc.gale.executor.lock.YieldingLock; ++import org.galemc.gale.executor.queue.BaseTaskQueueTier; ++import org.galemc.gale.executor.thread.BaseThread; ++import org.galemc.gale.executor.thread.SignalReason; ++import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.List; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.atomic.AtomicLong; ++ +/** -+ * This class allows using instanceof to quickly check if the current thread is a {@link AsyncThread} -+ * and provides some thread-local data. ++ * A class providing the static functionality needed to activate more threads in the {@link BaseThreadPool} ++ * when needed. + * + * @author Martijn Muijsers under AGPL-3.0 + */ -+public final class AsyncThread extends ConditionalSurplusThread { ++public final class BaseThreadActivation { + -+ AsyncThread(AsyncThreadPool pool, int index) { -+ super(pool, index); ++ private BaseThreadActivation() {} ++ ++ /** ++ * The delay in nanoseconds that is applied to {@link System#nanoTime()} ++ * when computing {@link #nextAllowedFrequentSignalNewTasksTime}. ++ */ ++ public static final long FREQUENT_SIGNAL_NEW_TASKS_INTERVAL = 100_000; ++ ++ /** ++ * The last time {@link #newTaskWasAdded}'s content was actually run. ++ * This value is useful to limit the number of runs of the method by potential frequent callers, ++ * such as the chunk task executors. ++ */ ++ private static final AtomicLong nextAllowedFrequentSignalNewTasksTime = new AtomicLong(System.nanoTime() - 1L); ++ ++ /** ++ * @see #update() ++ */ ++ static final AtomicBoolean isUpdateOngoing = new AtomicBoolean(); ++ ++ /** ++ * @see #update() ++ */ ++ private static final AtomicInteger newUpdateCallsReceived = new AtomicInteger(); ++ ++ /** ++ * A re-usable array for use inside {@link #update()}. ++ */ ++ private static final int[] numberOfThreadsActiveForTier = new int[BaseTaskQueueTier.length]; ++ ++ /** ++ * A re-usable array for use inside {@link #update()}. ++ */ ++ @SuppressWarnings("unchecked") ++ private static final List[] threadsWaitingForLockForTier = new List[BaseTaskQueueTier.length]; ++ static { ++ for (int tierI = 0; tierI < BaseTaskQueueTier.length; tierI++) { ++ threadsWaitingForLockForTier[tierI] = new ArrayList<>(); ++ } + } + + /** -+ * @return The current thread if it is a {@link AsyncThread}, or null otherwise. ++ * A re-usable array for use inside {@link #update()}. + */ -+ @AnyThreadSafe -+ @YieldFree -+ public static @Nullable AsyncThread currentAsyncThread() { -+ return Thread.currentThread() instanceof AsyncThread asyncThread ? asyncThread : null; ++ private static final int[] numberOfThreadsActiveForLowerThanTier = new int[BaseTaskQueueTier.length]; ++ ++ /** ++ * A re-usable array for use inside {@link #update()}. ++ */ ++ private static final int[] numberOfThreadsActiveForHigherThanTier = new int[BaseTaskQueueTier.length]; ++ ++ /** ++ * A re-usable array for use inside {@link #update()}. ++ */ ++ private static final int[] numberOfThreadsIntendedToBeActiveForTier = new int[BaseTaskQueueTier.length]; ++ ++ /** ++ * An array indicating per {@link BaseTaskQueueTier} (indexed by their {@link BaseTaskQueueTier#ordinal}) ++ * per {@link TaskSpan} (indexed by their {@link TaskSpan#ordinal}) whether there may be tasks ++ * for that tier and span, indicated by whether the value is positive (indicating true) or 0 (indicating false). ++ * It is always incremented before calling {@link #update()} due to new tasks being added. ++ * If it is 0, it is certain that either there are no queued task for the tier, or ++ * a task has just been added to the queue and this value has not yet been set to true, but will be due ++ * to a {@link #newTaskWasAdded} call, which is then followed by a {@link #callForUpdate()} call. ++ */ ++ public static final AtomicInteger[][] thereMayBeTasks = new AtomicInteger[BaseTaskQueueTier.length][TaskSpan.length]; ++ static { ++ for (int tierI = 0; tierI < BaseTaskQueueTier.length; tierI++) { ++ for (int spanI = 0; spanI < TaskSpan.length; spanI++) { ++ thereMayBeTasks[tierI][spanI] = new AtomicInteger(); ++ } ++ } + } + + /** -+ * @return Whether the current thread is a {@link AsyncThread}. ++ * The {@link BaseTaskQueueTier#ordinal} of the highest tier (which means the lowest ++ * {@link BaseTaskQueueTier#ordinal}) for which the number of present threads ++ * have been determined by the last call to {@link #update()} to be in excess. This value is ++ * {@link BaseTaskQueueTier#length} when no threads are in excess. + */ -+ @AnyThreadSafe -+ @YieldFree -+ public static boolean isAsyncThread() { -+ return Thread.currentThread() instanceof AsyncThread; ++ public static volatile int tierInExcessOrdinal = BaseTaskQueueTier.length; ++ ++ private static long updateNextAllowedFrequentSignalNewTasksTime(long value) { ++ long newValue = System.nanoTime() + FREQUENT_SIGNAL_NEW_TASKS_INTERVAL; ++ return newValue - value >= 0 ? newValue : value; ++ } ++ ++ /** ++ * @see #newTaskWasAdded(BaseTaskQueueTier, TaskSpan, boolean) ++ */ ++ public static void newTaskWasAdded(BaseTaskQueueTier tier, TaskSpan span) { ++ newTaskWasAdded(tier, span, false); ++ } ++ ++ /** ++ * This method is to be called when a new task has become available to be polled. ++ * The task must already have been added to the data structure that a thread would poll from, ++ * in a way that is visible to any thread (for example by adding it to a concurrent data structure). ++ * Otherwise, the resulting attempt at activating threads would not be able to observe these new tasks yet. ++ *
++ * When a task is added that is not important enough to warrant doing a full {@link #update}, ++ * calling this method may be skipped. ++ *
++ * Additionally, this method may be called when no new task has been added, but there is a suspicion of new tasks ++ * existing for which no {@link #update} was called. A concrete example of this is when a thread is activated ++ * due to tasks it can poll being available, but then upon activation, acquiring a {@link YieldingLock} it was ++ * waiting for instead. ++ */ ++ public static void newTaskWasAdded(BaseTaskQueueTier tier, TaskSpan span, boolean onlyIfLastTimeIsTooLongAgo) { ++ ++ if (thereMayBeTasks[tier.ordinal][span.ordinal].getAndIncrement() == 0) { ++ // Always call update() if we just set the thereMayBeTasks value to true ++ onlyIfLastTimeIsTooLongAgo = false; ++ } ++ ++ // Check and update nextAllowedFrequentSignalNewTasksTime ++ if (!onlyIfLastTimeIsTooLongAgo || System.nanoTime() - nextAllowedFrequentSignalNewTasksTime.get() >= 0) { ++ nextAllowedFrequentSignalNewTasksTime.updateAndGet(BaseThreadActivation::updateNextAllowedFrequentSignalNewTasksTime); ++ // Update ++ callForUpdate(); ++ } else { ++ // Do not start an update, but do increment the received calls ++ newUpdateCallsReceived.incrementAndGet(); ++ } ++ ++ } ++ ++ /** ++ * This method is to be called when a {@link YieldingLock} has been released. ++ * The lock must already have been unlocked. Otherwise, the resulting attempt at activating ++ * threads would not be able to observe the lock being released yet. ++ */ ++ public static void yieldingLockWithWaitingThreadsWasUnlocked() { ++ // Update ++ callForUpdate(); ++ } ++ ++ /** ++ * Either starts an {@link #update()}, or lets another thread that is already doing an update know ++ * that it will have to do another one. ++ *
++ * Only one thread can be performing an update at a time. ++ * If a second thread calls this method while an update is ongoing (signified by {@link #isUpdateOngoing}), ++ * the thread performing an update will perform another update after finishing the current one, due to the ++ * second thread incrementing {@link #newUpdateCallsReceived}. ++ *
++ * After a thread property (or another property that is used in a similar way) ++ * that is used within {@link #update()} is changed, this method must be called. ++ * This currently equates to the following values: ++ *
    ++ *
  • {@link BaseThread#highestTierOfTaskOnStack}
  • ++ *
  • ++ * {@link BaseThread#isWaiting} and {@link BaseThread#lockWaitingFor}, ++ * which are always updated in tandem, and {@link BaseThread#isNotActuallyWaitingYet} and ++ * {@link BaseThread#skipNextWait}, which are set at similar times as {@link BaseThread#isWaiting}. ++ *
  • ++ *
  • ++ * {@link BaseThread#yieldDepth} and {@link BaseThread#canStartYieldingTasks}, ++ * which are always updated in tandem. ++ *
  • ++ *
++ * This specifically does not include: ++ *
    ++ *
  • ++ * The following values that are only used ++ * in the meta-handling of {@link #update()}, not in the activation of threads: ++ *
      ++ *
    • {@link #newUpdateCallsReceived}
    • ++ *
    • {@link #isUpdateOngoing}
    • ++ *
    ++ *
  • ++ *
  • ++ * The following values that are never changed outside of {@link #update()}: ++ *
      ++ *
    • {@link #numberOfThreadsActiveForTier}
    • ++ *
    • {@link #threadsWaitingForLockForTier}
    • ++ *
    • {@link #numberOfThreadsActiveForLowerThanTier}
    • ++ *
    • {@link #numberOfThreadsActiveForHigherThanTier}
    • ++ *
    • {@link #numberOfThreadsIntendedToBeActiveForTier}
    • ++ *
    ++ *
  • ++ *
  • ++ * {@link #thereMayBeTasks}, which is only set to 0 outside of {@link #update()} ++ * (specifically, in {@link BaseThread}), which will only prevent the {@link #update()} call from ++ * exploring the existence of tasks for a specific {@link BaseTaskQueueTier} and {@link TaskSpan} when ++ * there are in fact no such tasks, thereby not causing any reason to do another update. ++ *
  • ++ *
++ */ ++ public static void callForUpdate() { ++ // Make sure the updating thread repeats (must be set before evaluating isUpdateOngoing) ++ newUpdateCallsReceived.incrementAndGet(); ++ // Start the update ourselves if not ongoing ++ if (!isUpdateOngoing.getAndSet(true)) { ++ // Perform an update ++ do { ++ try { ++ update(); ++ } finally { ++ isUpdateOngoing.set(false); ++ } ++ /* ++ If newUpdateCallsReceived is positive, it was increased between it being set to 0 and ++ isUpdateOngoing being set to false, so we must repeat. ++ */ ++ } while (newUpdateCallsReceived.get() > 0); ++ } ++ } ++ ++ /** ++ * Activates threads as necessary, and computes whether threads must de-activate themselves when they can. ++ *
++ * This method is called from {@link #callForUpdate()} if necessary. ++ */ ++ static void update() { ++ MinecraftServer.THREAD_DEBUG_LOGGER.ifPresent(it -> it.info("update")); ++ boolean madeChangesInLastIteration = false; ++ int numberOfUpdateCallsAtStartOfLastIteration = -1; ++ boolean isFirstIteration = true; ++ /* ++ Keep updating while necessary (while marked to repeat by another call, ++ or while this update itself made some change in the previous iteration, ++ to be sure we only stop when we found no more changes to make). ++ */ ++ updateWhileNecessary: ++ while (true) { ++ MinecraftServer.THREAD_DEBUG_LOGGER.ifPresent(it -> it.info("iteration of update")); ++ ++ // Break the loop if needed ++ if (isFirstIteration) { ++ // Always run an iteration if this is the first one ++ isFirstIteration = false; ++ numberOfUpdateCallsAtStartOfLastIteration = newUpdateCallsReceived.decrementAndGet(); ++ } else { ++ if (madeChangesInLastIteration) { ++ /* ++ If we made changes in the last iteration, ++ we can quit only if no more update calls have been received at all. ++ */ ++ int oldNewUpdateCallsReceived = newUpdateCallsReceived.getAndUpdate(value -> value == 0 ? 0 : value - 1); ++ if (oldNewUpdateCallsReceived == 0) { ++ break; ++ } ++ numberOfUpdateCallsAtStartOfLastIteration = oldNewUpdateCallsReceived - 1; ++ } else { ++ /* ++ If we made no changes in the last iteration, ++ we can quit if no update calls were received in the meantime. ++ In that case, we can reset newUpdateCallsReceived as we have finished all necessary updates. ++ */ ++ final int finalNumberOfUpdateCallsAtStartOfLastIteration = numberOfUpdateCallsAtStartOfLastIteration; ++ int oldNewUpdateCallsReceived = newUpdateCallsReceived.getAndUpdate(value -> value == finalNumberOfUpdateCallsAtStartOfLastIteration ? 0 : value - 1); ++ if (oldNewUpdateCallsReceived == numberOfUpdateCallsAtStartOfLastIteration) { ++ break; ++ } ++ numberOfUpdateCallsAtStartOfLastIteration = oldNewUpdateCallsReceived - 1; ++ } ++ } ++ ++ // Reset madeChangesInLastIteration ++ madeChangesInLastIteration = false; ++ ++ // Get the threads ++ @Nullable BaseThread @NotNull [] threads = BaseThreadPool.getBaseThreads(); ++ ++ /* ++ Compute for each tier, for how many threads ++ the highest tier of any task on their stack is that tier. ++ Additionally, compute the threads for each tier that are waiting for some YieldingLock ++ (threads with no tasks on their stack cannot be waiting for a YieldingLock). ++ Additionally, compute the number of threads that are active (i.e. not waiting) ++ but that are not executing a task (i.e. do not have any tasks on their stack). ++ */ ++ Arrays.fill(numberOfThreadsActiveForTier, 0); ++ for (int tierI = 0; tierI < BaseTaskQueueTier.length; tierI++) { ++ threadsWaitingForLockForTier[tierI].clear(); ++ } ++ int activeAssistThreadsWithoutTask = 0; ++ for (BaseThread thread : threads) { ++ if (thread != null) { ++ BaseTaskQueueTier tier = thread.highestTierOfTaskOnStack; ++ if (tier == null) { ++ // The thread is doing nothing ++ if (thread.baseThreadIndex > 0 && !thread.isWaitingAndNeedsSignal()) { ++ // If it is doing nothing but not waiting, it is available to do anything ++ activeAssistThreadsWithoutTask++; ++ } ++ } else { ++ numberOfThreadsActiveForTier[tier.ordinal]++; ++ if (thread.lockWaitingFor != null) { ++ threadsWaitingForLockForTier[tier.ordinal].add(thread); ++ } ++ } ++ } ++ } ++ ++ /* ++ Compute the exclusive cumulative value of numberOfThreadsActiveForTier from above, ++ as being for how many threads the highest tier of any task on their stack ++ is a strictly lower priority tier. ++ */ ++ System.arraycopy(numberOfThreadsActiveForTier, 1, numberOfThreadsActiveForLowerThanTier, 0, BaseTaskQueueTier.length - 1); ++ for (int tierI = BaseTaskQueueTier.length - 2; tierI >= 0; tierI--) { ++ numberOfThreadsActiveForLowerThanTier[tierI] += numberOfThreadsActiveForLowerThanTier[tierI + 1]; ++ } ++ ++ /* ++ Compute the exclusive cumulative value of numberOfThreadsActiveForTier from below, ++ as being for how many threads the highest tier of any task on their stack ++ is a strictly higher priority tier. ++ */ ++ System.arraycopy(numberOfThreadsActiveForTier, 0, numberOfThreadsActiveForHigherThanTier, 1, BaseTaskQueueTier.length - 1); ++ for (int tierI = 2; tierI < BaseTaskQueueTier.length; tierI++) { ++ numberOfThreadsActiveForHigherThanTier[tierI] += numberOfThreadsActiveForHigherThanTier[tierI - 1]; ++ } ++ ++ /* ++ For each tier, compute the number of threads that should be active if there were tasks. ++ This can then later be compared to the actual number of active threads for that tier. ++ */ ++ for (int tierI = 0; tierI < BaseTaskQueueTier.length; tierI++) { ++ numberOfThreadsIntendedToBeActiveForTier[tierI] = BaseThreadPool.targetParallelism - (tierI == 0 ? 0 : activeAssistThreadsWithoutTask) - numberOfThreadsActiveForHigherThanTier[tierI] - Math.min(numberOfThreadsActiveForLowerThanTier[tierI], BaseThreadPool.maxUndisturbedLowerTierThreadCount); ++ } ++ // There can only be one active server thread ++ numberOfThreadsIntendedToBeActiveForTier[0] = Math.min(numberOfThreadsIntendedToBeActiveForTier[0], 1); ++ ++ { ++ final int finalActiveAssistThreadsWithoutTask = activeAssistThreadsWithoutTask; ++ MinecraftServer.THREAD_DEBUG_LOGGER.ifPresent(it -> it.info("Target parallelism = " + BaseThreadPool.targetParallelism + ", active threads without task = " + finalActiveAssistThreadsWithoutTask + ", active threads for tiers = " + Arrays.toString(numberOfThreadsActiveForTier) + ", number of threads intended to be active for tiers = " + Arrays.toString(numberOfThreadsIntendedToBeActiveForTier))); ++ } ++ ++ /* ++ * Determine the highest tier for which the number of threads that are active exceeds ++ * the number of threads that should be active if there were tasks. ++ * If none, set tierInExcessOrdinal to BaseTaskQueueTier#length. ++ */ ++ for (int tierI = 0;; tierI++) { ++ if (tierI == BaseTaskQueueTier.length || numberOfThreadsActiveForTier[tierI] > numberOfThreadsIntendedToBeActiveForTier[tierI]) { ++ tierInExcessOrdinal = tierI; ++ break; ++ } ++ } ++ ++ /* ++ Try to activate a thread, for higher to lower priority tier tasks, in order: ++ if a thread is activated, we continue with another update iteration, so that we make a ++ good-as-possible attempt to activate threads for higher priority tier tasks first. ++ */ ++ for (int tierI = 0; tierI < BaseTaskQueueTier.length; tierI++) { ++ // Only if we need to activate threads ++ if (numberOfThreadsActiveForTier[tierI] < numberOfThreadsIntendedToBeActiveForTier[tierI]) { ++ /* ++ Only if there may be tasks at all (which, if true, will be the reason provided when signalling a ++ thread), or if there is a thread already at this exact tier that is waiting for a YieldingLock. ++ */ ++ boolean thereAreTasks = false; ++ for (int spanI = 0; spanI < TaskSpan.length; spanI++) { ++ if (thereMayBeTasks[tierI][spanI].get() > 0) { ++ thereAreTasks = true; ++ break; ++ } ++ } ++ if (thereAreTasks || !threadsWaitingForLockForTier[tierI].isEmpty()) { ++ ++ /* ++ * We attempt to wake up a thread that is sleeping, ++ * or add a new thread to start running. ++ * Of course, we can only choose a thread that could poll a task. ++ * We only choose a thread that can accept yielding tasks, even if ++ * the added task is yield-free, so that we have a lower chance of ++ * the chosen thread getting stuck again quickly. ++ * Out of the possible threads, we attempt to choose one that is waiting for a YieldingLock ++ * that is available, so that we have a thread owning this lock as quickly as possible again, ++ * making the next time it is released again sooner as well. ++ * Out of the possible threads that are not waiting for a lock, ++ * we attempt to choose one with non-zero yield depth over any with zero yield depth, ++ * since we must later wake up this thread anyway. Then, we attempt to choose one with the ++ * lowest possible yield depth, so that it can still keep yielding as much as possible. ++ */ ++ /* ++ Special case: only the server thread can start SERVER tasks, ++ and we never activate it for other tiers, because it could only start tiny tasks. ++ */ ++ int tryThreadsStart, tryThreadsEnd; ++ if (tierI == 0) { ++ tryThreadsStart = 0; ++ tryThreadsEnd = 1; ++ } else { ++ tryThreadsStart = 1; ++ tryThreadsEnd = threads.length; ++ } ++ while (true) { ++ // Find the best thread to activate ++ int threadIToUpdate = -1; ++ boolean threadIToUpdateIsWaitingForAvailableYieldingLock = false; ++ int threadIToUpdateYieldDepth = 0; ++ int threadIToUpdateTierOrdinalOrLength = 0; ++ for (int threadI = tryThreadsStart; threadI < tryThreadsEnd; threadI++) { ++ BaseThread thread = threads[threadI]; ++ if (thread != null) { ++ if (thread.isWaitingAndNeedsSignal() && thread.canStartYieldingTasks) { ++ /* ++ Tasks of a certain tier may yield to tasks of the same or a higher ++ tier, and they may also yield to tiny tasks of a lower tier. ++ We do not want to wake up a thread just for tiny tasks ++ unless it has zero yield depth, ++ so we only activate threads that have either no tasks on their stack, ++ or only tasks of the same or a lower tier, where a lower priority tier ++ is preferred (but not as important as the yield depth). ++ Of course, this only takes into account tasks, and we may also ++ activate threads due to them waiting on an available YieldingLock. ++ */ ++ var highestTierOfTaskOnStack = thread.highestTierOfTaskOnStack; ++ var highestTierOfTaskOnStackOrdinalOrLength = highestTierOfTaskOnStack == null ? BaseTaskQueueTier.length : highestTierOfTaskOnStack.ordinal; ++ @Nullable YieldingLock lockWaitingFor = thread.lockWaitingFor; ++ boolean isThreadWaitingForAvailableYieldingLock = lockWaitingFor != null && !lockWaitingFor.isLocked(); ++ if (isThreadWaitingForAvailableYieldingLock || highestTierOfTaskOnStack == null || highestTierOfTaskOnStack.ordinal >= tierI) { ++ boolean isBestChoice = false; ++ int yieldDepth = thread.yieldDepth; ++ if (threadIToUpdate == -1) { ++ isBestChoice = true; ++ } else if (isThreadWaitingForAvailableYieldingLock != threadIToUpdateIsWaitingForAvailableYieldingLock) { ++ isBestChoice = isThreadWaitingForAvailableYieldingLock; ++ } else if (threadIToUpdateYieldDepth == 0 && yieldDepth != 0) { ++ isBestChoice = true; ++ } else if (yieldDepth != 0) { ++ if (yieldDepth < threadIToUpdateYieldDepth) { ++ isBestChoice = true; ++ } else if (highestTierOfTaskOnStackOrdinalOrLength > threadIToUpdateTierOrdinalOrLength) { ++ isBestChoice = true; ++ } ++ } ++ if (isBestChoice) { ++ threadIToUpdate = threadI; ++ threadIToUpdateIsWaitingForAvailableYieldingLock = isThreadWaitingForAvailableYieldingLock; ++ threadIToUpdateYieldDepth = yieldDepth; ++ threadIToUpdateTierOrdinalOrLength = highestTierOfTaskOnStackOrdinalOrLength; ++ } ++ } ++ } ++ } ++ } ++ if (threadIToUpdate == -1) { ++ // No valid thread was found ++ break; ++ } ++ // Check if the thread still seems valid and attempt to activate it ++ BaseThread thread = threads[threadIToUpdate]; ++ if (thread.isWaitingAndNeedsSignal() && thread.canStartYieldingTasks) { ++ // Wake up the thread ++ if (thread.signal(thereAreTasks ? SignalReason.TASK : SignalReason.YIELDING_LOCK)) { ++ // Do another update ++ madeChangesInLastIteration = true; ++ continue updateWhileNecessary; ++ } ++ } ++ /* ++ The thread was not valid to activate anymore, or not activated, ++ so we attempt to find a valid thread again. ++ */ ++ } ++ ++ // Because no thread was activated, we add one (only if we were looking for an AssistThread) ++ if (tierI != 0) { ++ BaseThreadPool.addAssistThread(); ++ // Do another update ++ madeChangesInLastIteration = true; ++ continue updateWhileNecessary; ++ } ++ ++ } ++ } ++ } ++ ++ } + } + +} -diff --git a/src/main/java/org/galemc/gale/executor/thread/pooled/AsyncThreadPool.java b/src/main/java/org/galemc/gale/executor/thread/pooled/AsyncThreadPool.java +diff --git a/src/main/java/org/galemc/gale/executor/thread/pool/BaseThreadPool.java b/src/main/java/org/galemc/gale/executor/thread/pool/BaseThreadPool.java new file mode 100644 -index 0000000000000000000000000000000000000000..b86516e034c920f1c551764cd5cc2cc338ce5535 +index 0000000000000000000000000000000000000000..36c9faaf2d431dbc5a173a3821752725b497dd6c --- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/pooled/AsyncThreadPool.java -@@ -0,0 +1,66 @@ -+// Gale - base thread pools ++++ b/src/main/java/org/galemc/gale/executor/thread/pool/BaseThreadPool.java +@@ -0,0 +1,219 @@ ++// Gale - base thread pool + -+package org.galemc.gale.executor.thread.pooled; ++package org.galemc.gale.executor.thread.pool; + -+import io.papermc.paper.util.TickThread; +import net.minecraft.server.MinecraftServer; -+import org.galemc.gale.executor.queue.AbstractTaskQueue; -+import org.galemc.gale.executor.queue.BaseTaskQueues; ++import org.galemc.gale.executor.queue.BaseTaskQueueTier; ++import org.galemc.gale.executor.thread.AssistThread; ++import org.galemc.gale.executor.thread.BaseThread; ++import org.galemc.gale.executor.thread.ServerThread; ++import org.galemc.gale.util.CPUCoresEstimation; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.Arrays; + +/** -+ * A {@link ConditionalSurplusThreadPool} with threads that -+ * can perform tasks that must be executed on an {@link AsyncThread}. -+ * These tasks could in principle be executed on a {@link ServerThread}, {@link TickThread} or -+ * {@link TickAssistThread}, but should not be, because these tasks can take a long time and may block the server -+ * from progressing to the next tick if performed on one of these types of threads. ++ * A pool of threads that can perform tasks to assist the current {@link ServerThread}. These tasks can be of ++ * different {@linkplain BaseTaskQueueTier tiers}. + *
+ * This pool intends to keep {@link #targetParallelism} threads active at any time, -+ * but subtracts 1 for each active {@link ServerThread}, {@link TickThread} or {@link TickAssistThread}. ++ * which includes a potentially active {@link ServerThread}. ++ *
++ * As such, this pool is closely intertwined with the {@link ServerThread}. This pool can not control the ++ * {@link ServerThread} in any way, but it is responsible for signalling the {@link ServerThread} when tasks become ++ * available in a {@link BaseTaskQueueTier#SERVER} task queue, and for listening for when the {@link ServerThread} ++ * becomes (in)active in order to update the number of active {@link AssistThread}s accordingly. ++ *

++ * Updates to the threads in this pool are done in a lock-free manner that attempts to do the right thing with ++ * the volatile information that is available. In some cases, this may cause a thread to be woken up when it ++ * should not have been, and so on, but the updates being lock-free is more significant than the updates being ++ * optimal in a high-contention environment. The environment is not expected to have high enough contention for ++ * this to have much of an impact. Additionally, the suboptimalities in updates are always optimistic in terms of ++ * making/keeping threads active rather than inactive, and can not a situation where a thread was intended ++ * to be active, but ends but not being active. + * + * @author Martijn Muijsers under AGPL-3.0 + */ -+public final class AsyncThreadPool extends ConditionalSurplusThreadPool { ++public final class BaseThreadPool { + -+ public static final String targetParallelismEnvironmentVariable = "gale.threads.async"; ++ private BaseThreadPool() {} + -+ public static final int ASYNC_THREAD_PRIORITY = Integer.getInteger("gale.thread.priority.async", 6); -+ -+ public static final int MAXIMUM_YIELD_DEPTH = Integer.getInteger("gale.maxyielddepth.async", 100); ++ public static final String targetParallelismEnvironmentVariable = "gale.threads.target"; ++ public static final String maxUndisturbedLowerTierThreadCountEnvironmentVariable = "gale.threads.undisturbed"; + + /** -+ * The target number of threads that will be actively in use by this pool. -+ * Any active threads with higher priority will be dynamically subtracted from this target. ++ * The target number of threads that will be actively in use by this pool, ++ * which includes a potentially active {@link ServerThread}. + *
+ * This value is always positive. + *
-+ * Determined by {@link TargetParallelism#computeTargetParallelism(String)}, which can be overridden using -+ * the environment variable {@link #targetParallelismEnvironmentVariable}. -+ */ -+ public static final int targetParallelism = TargetParallelism.computeTargetParallelism(targetParallelismEnvironmentVariable); -+ -+ public static final AsyncThreadPool instance = new AsyncThreadPool(); -+ -+ private AsyncThreadPool() { -+ super(new AsyncThread[0], "Base Async Thread", new AbstractTaskQueue[] { -+ // The cleaner queue has high priority because it releases resources back to a pool, thereby saving memory -+ BaseTaskQueues.cleaner, -+ BaseTaskQueues.scheduledAsync -+ }, MAXIMUM_YIELD_DEPTH); -+ } -+ -+ @Override -+ public AsyncThread createThread(int index) { -+ AsyncThread thread = new AsyncThread(this, index); -+ thread.setPriority(ASYNC_THREAD_PRIORITY); -+ return thread; -+ } -+ -+ @Override -+ protected int computeIntendedActiveThreadCount() { -+ if (!MinecraftServer.isConstructed) { -+ return targetParallelism; -+ } -+ return targetParallelism - ServerThreadPool.instance.getRunnableThreadCount() - TickAssistThreadPool.instance.getRunnableThreadCount(); -+ } -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/pooled/ConditionalSurplusThread.java b/src/main/java/org/galemc/gale/executor/thread/pooled/ConditionalSurplusThread.java -new file mode 100644 -index 0000000000000000000000000000000000000000..16061701d6df1a372d89046fd2065915892b214f ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/pooled/ConditionalSurplusThread.java -@@ -0,0 +1,102 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.pooled; -+ -+import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; -+import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.thread.BaseYieldingThread; -+ -+import java.util.concurrent.atomic.AtomicBoolean; -+ -+/** -+ * A member thread of a {@link ConditionalSurplusThreadPool}. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+@AnyThreadSafe -+@YieldFree -+public abstract class ConditionalSurplusThread extends BaseYieldingThread { -+ -+ private final ConditionalSurplusThreadPool pool; -+ -+ /** -+ * Prevent race conditions in checks that are done before changing the active thread count, to make sure -+ * a thread does not check a condition in order to change the active thread count, and the condition is changed -+ * and the active thread count changed by another thread before the thread can initiate the change itself. ++ * The value is currently automatically determined according to the following table: ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ * ++ *
system corescores spared
≤ 30
[4, 14]1
[15, 23]2
[24, 37]3
[38, 54]4
[55, 74]5
[75, 99]6
[100, 127]7
[128, 158]8
[159, 193]9
[194, 232]10
[233, 274]11
≥ 27512
++ * Then target parallelism = system cores - cores spared. + *
-+ * This value must always be switched appropriately before the active thread count is changed. ++ * The computed value above can be overridden using the {@link #targetParallelismEnvironmentVariable}. + */ -+ public final AtomicBoolean isCountedInPoolActiveThreadCount = new AtomicBoolean(); -+ -+ /** -+ * The auto-incremented 0-index index this thread has within this pool. -+ */ -+ final int index; -+ -+ protected ConditionalSurplusThread(ConditionalSurplusThreadPool pool, int index) { -+ super(BaseYieldingThread::getCurrentBaseYieldingThreadAndRunForever, pool.threadName + " " + index, pool.taskQueues, pool.threadsWaitingForYieldingTasks, pool.threadsWaitingForFreeTasks, true, pool.maximumYieldDepth); -+ this.pool = pool; -+ this.index = index; -+ } -+ -+ @Override -+ protected boolean wantsToPoll() { -+ // If this thread is an excess thread, just pause here -+ for (;;) { -+ // Set isCountedInPoolActiveThreadCount to false in anticipation -+ if (this.isCountedInPoolActiveThreadCount.getAndSet(false)) { -+ if (this.pool.decrementActiveThreadCountIfLargerThanIntended()) { -+ return false; -+ } -+ // Undo the earlier isCountedInPoolActiveThreadCount set -+ if (!this.isCountedInPoolActiveThreadCount.getAndSet(true)) { -+ return true; -+ } -+ // Strange race condition occurrence -+ continue; -+ } -+ // Strange race condition occurrence -+ return false; -+ } -+ } -+ -+ @Override -+ public boolean willWaitAfterPollFailure() { -+ if (this.isRestrictedDueToYieldDepth) { -+ if (this.isCountedInPoolActiveThreadCount.getAndSet(false)) { -+ /* -+ This thread will not be attempted to be activated by the updateActiveThreads caused below, -+ because it only activates threads for which isRestrictedDueToYieldDepth is false. -+ */ -+ this.pool.decrementActiveThreadCountAndUpdateActiveThreads(); -+ } -+ return false; -+ } -+ return true; -+ } -+ -+ @Override -+ public void wokeUpAfterNonBecomeAllowedToPollSignal() { -+ /* -+ If the signal was becomesAllowedToPollSignalReason, the activeThreadCount will already have been -+ incremented in updateActiveThreads, but the signal is different. If this thread woke up and was not -+ registered for tasks before, that means it went from inactive to active. -+ */ -+ if (!this.isRegisteredForNewTasks) { -+ if (!this.isCountedInPoolActiveThreadCount.getAndSet(true)) { -+ this.pool.incrementActiveThreadCountAndUpdateActiveThreads(); -+ } -+ } -+ } -+ -+ @Override -+ protected void preBlockThread() { -+ this.pool.incrementRunnableThreadCount(); -+ } -+ -+ @Override -+ protected void postBlockThread() { -+ this.pool.decrementRunnableThreadCount(); -+ } -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/pooled/ConditionalSurplusThreadPool.java b/src/main/java/org/galemc/gale/executor/thread/pooled/ConditionalSurplusThreadPool.java -new file mode 100644 -index 0000000000000000000000000000000000000000..22c3ad33a784cb8d6bcdcb8c787029f4922f6123 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/pooled/ConditionalSurplusThreadPool.java -@@ -0,0 +1,408 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.pooled; -+ -+import org.galemc.gale.concurrent.Mutex; -+import org.galemc.gale.executor.annotation.Access; -+import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; -+import org.galemc.gale.executor.annotation.Guarded; -+import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.lock.YieldingLock; -+import org.galemc.gale.executor.queue.AbstractTaskQueue; -+import org.galemc.gale.executor.thread.BaseYieldingThread; -+import org.galemc.gale.executor.thread.wait.IndexArrayWaitingThreadSet; -+import org.galemc.gale.executor.thread.wait.SignalReason; -+import org.galemc.gale.executor.thread.wait.WaitingThreadSet; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.Arrays; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.concurrent.atomic.AtomicInteger; -+ -+/** -+ * A pool of threads that can perform specific types of tasks. -+ * The threads in this thread pool perform an endless loop, polling and executing tasks. -+ *
-+ * This thread pool has a dynamic amount of threads that are intended to be polling or running tasks at a given moment: -+ * we refer to such threads as active below. If a thread is listening for new tasks to be added, and it is not -+ * restricted (i.e. it could accept any task any newly spawned thread in this pool could accept) then it counts as -+ * active too (since it will be signalled as soon as new tasks are added and there would be no use in replacing it -+ * with a newly spawned thread). -+ * If the number of active threads becomes lower than this value, surplus threads become allowed, and are signalled -+ * (and will be spawned and added to the thread pool if not present yet) to start polling tasks, in order to -+ * reach the intended amount of active threads again. -+ * If the number of active threads becomes higher than this value, some excess threads will not be able -+ * to poll new tasks until allowed again. Potentially, The threads will stay in the {@link Thread.State#RUNNABLE} -+ * state until they finish a task they were already executing. -+ *
-+ * While a thread is not allowed to poll new tasks, it can still be signalled for other reasons, such as a -+ * {@link YieldingLock} they were waiting for being released. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+@AnyThreadSafe -+@YieldFree -+public abstract class ConditionalSurplusThreadPool implements AbstractYieldingSignallableThreadPool { -+ -+ /** -+ * The name for newly spawned threads. The index of the thread within this pool will be appended. -+ */ -+ final String threadName; -+ -+ /** -+ * The value of {@link BaseYieldingThread#taskQueues} that will be passed to member threads. -+ */ -+ final AbstractTaskQueue[] taskQueues; -+ -+ /** -+ * A {@link WaitingThreadSet} of member threads waiting for potentially yielding tasks to be added. -+ */ -+ final WaitingThreadSet threadsWaitingForYieldingTasks = new WaitingMemberThreadSet(); -+ -+ /** -+ * A {@link WaitingThreadSet} of member threads waiting for yield-free tasks to be added. -+ */ -+ final WaitingThreadSet threadsWaitingForFreeTasks = new WaitingMemberThreadSet() { -+ -+ @Override -+ public boolean pollAndSignal(SignalReason reason) { -+ if (super.pollAndSignal(reason)) { -+ return true; -+ } -+ /* -+ Waiting for potentially yielding tasks implies waiting for yield-free tasks, -+ so we signal threads waiting for potentially yielding tasks too. -+ */ -+ return threadsWaitingForYieldingTasks.pollAndSignal(reason); -+ } -+ -+ }; -+ -+ /** -+ * The value of {@link BaseYieldingThread#maximumYieldDepth} that will be passed to member threads. -+ */ -+ final int maximumYieldDepth; -+ -+ /** -+ * An array of the threads in this pool, indexed by their {@link ConditionalSurplusThread#index}. -+ */ -+ @Guarded(value = "#threadsLock", fieldAccess = Access.WRITE) -+ private volatile T[] threads; -+ -+ private final Mutex threadsLock = Mutex.create(); -+ -+ /** -+ * The last computed number of intended active threads. -+ *
-+ * This value is updated in {@link #updateActiveThreads()}, where its value will be based on -+ * {@link #computeIntendedActiveThreadCount}. -+ */ -+ public volatile int lastComputedIntendedActiveThreadCount; -+ -+ /** -+ * The actual number of threads that are active now, i.e. the number of threads that are either runnable and -+ * polling or executing tasks, or not restricted and awaiting new tasks to be added. -+ */ -+ private final AtomicInteger activeThreadCount = new AtomicInteger(); -+ -+ /** -+ * The number of threads that are in the {@link Thread.State#NEW}, {@link Thread.State#RUNNABLE} or -+ * {@link Thread.State#TERMINATED} states. -+ */ -+ private final AtomicInteger runnableThreadCount = new AtomicInteger(); -+ -+ /** -+ * Whether a call to {@link #updateActiveThreads} is ongoing. -+ */ -+ private final AtomicBoolean updateActiveThreadsIsOngoing = new AtomicBoolean(); -+ -+ /** -+ * Whether to repeat the outermost loop within {@link #updateActiveThreads} due to a second call to -+ * {@link #updateActiveThreads} happening while {@link #updateActiveThreadsIsOngoing} was already true. -+ */ -+ private volatile boolean repeatUpdateActiveThreadLoop = false; -+ -+ protected ConditionalSurplusThreadPool(T[] emptyThreadArray, String threadName, AbstractTaskQueue[] taskQueues, int maximumYieldDepth) { -+ this.threads = emptyThreadArray; -+ this.threadName = threadName; -+ this.taskQueues = taskQueues; -+ for (AbstractTaskQueue queue : this.taskQueues) { -+ queue.setThreadPool(this); -+ } -+ this.maximumYieldDepth = maximumYieldDepth; -+ this.intendedActiveThreadCountMayHaveChanged(); -+ this.updateActiveThreads(); -+ } -+ -+ /** -+ * @return A newly constructed member thread with the given index within this pool. -+ */ -+ public abstract T createThread(int index); -+ -+ /** -+ * @return The number of threads that are intended to be active now. -+ */ -+ protected abstract int computeIntendedActiveThreadCount(); -+ -+ @Override -+ public boolean signalNewTasks(SignalReason reason, boolean yielding) { -+ // First attempt without updating the active threads, then attempt after updating the active threads -+ for (int attempt = 0; attempt < 2; attempt++) { -+ if (attempt == 1) { -+ this.updateActiveThreads(); -+ } -+ for (T thread : this.threads) { -+ if (thread.isWaiting && thread.isRegisteredForNewTasks) { -+ if (thread.signal(reason)) { -+ return true; -+ } -+ } -+ } -+ } -+ return false; -+ } -+ -+ @Override -+ public int getRunnableThreadCount() { -+ return this.runnableThreadCount.get(); -+ } -+ -+ public void incrementRunnableThreadCount() { -+ this.runnableThreadCount.incrementAndGet(); -+ } -+ -+ public void decrementRunnableThreadCount() { -+ this.runnableThreadCount.decrementAndGet(); -+ } -+ -+ public T getThreadByIndex(int index) { -+ return this.threads[index]; -+ } -+ -+ public void decrementActiveThreadCountAndUpdateActiveThreads() { -+ this.activeThreadCount.decrementAndGet(); -+ this.updateActiveThreads(); -+ } -+ -+ public void incrementActiveThreadCountAndUpdateActiveThreads() { -+ this.activeThreadCount.incrementAndGet(); -+ this.updateActiveThreads(); -+ } -+ -+ /** -+ * This implementation is based on the implementation of {@link AtomicInteger#updateAndGet}. -+ * -+ * @return Whether {@link #activeThreadCount} was updated by this method. -+ */ -+ public boolean decrementActiveThreadCountIfLargerThanIntended() { -+ for (;;) { -+ int prev = this.activeThreadCount.get(); -+ if (prev <= this.lastComputedIntendedActiveThreadCount) { -+ return false; -+ } -+ int next = prev - 1; -+ if (this.activeThreadCount.weakCompareAndSetVolatile(prev, next)) { -+ return true; -+ } -+ } -+ } -+ -+ /** -+ * This implementation is based on the implementation of {@link AtomicInteger#updateAndGet}. -+ * -+ * @return Whether {@link #activeThreadCount} was updated by this method. -+ */ -+ public boolean incrementActiveThreadCountIfSmallerThanIntended() { -+ for (;;) { -+ int prev = this.activeThreadCount.get(); -+ if (prev >= this.lastComputedIntendedActiveThreadCount) { -+ return false; -+ } -+ int next = prev + 1; -+ if (this.activeThreadCount.weakCompareAndSetVolatile(prev, next)) { -+ return true; -+ } -+ } -+ } -+ -+ /** -+ * Re-computes the number of threads that should be active, and modifies the thread pool accordingly. May add -+ * new surplus threads or allow or disallow threads to listen for new tasks. -+ *
-+ * Must be called after the value of {@link #computeIntendedActiveThreadCount()} may have changed, and must -+ * be called after the value of {@link #activeThreadCount} has changed. -+ */ -+ void updateActiveThreads() { -+ if (!updateActiveThreadsIsOngoing.getAndSet(true)) { -+ try { -+ // Keep (de-)activating threads while necessary -+ for (;;) { -+ int previousActiveThreadCount = this.activeThreadCount.get(); -+ if (previousActiveThreadCount == this.lastComputedIntendedActiveThreadCount) { -+ // The number of active threads is perfect -+ return; -+ } -+ // Remember if we made any changes -+ boolean madeChanges = false; -+ // There is no point in activating more threads if there are no taskss -+ boolean previousHadTasksForNewThreads = AbstractTaskQueue.taskQueuesHaveTasks(this.taskQueues); -+ if (previousActiveThreadCount < this.lastComputedIntendedActiveThreadCount && previousHadTasksForNewThreads) { -+ // Allow some threads to listen to new tasks, or signal them if there are already tasks available -+ for (byte alsoTryZeroYieldDepth = 0; alsoTryZeroYieldDepth < 2; alsoTryZeroYieldDepth++) { -+ for (T thread : this.threads) { -+ // Speculatively verify that the thread is waiting and not registered for new tasks -+ if (thread.isWaiting && !thread.isRegisteredForNewTasks) { -+ // Speculatively check that the thread is non-restricted -+ if (!thread.isRestrictedDueToYieldDepth) { -+ // First attempt to choose a thread that already has a positive yield depth -+ if (alsoTryZeroYieldDepth == 1 || thread.yieldDepth > 0) { -+ // Continue if the thread is successfully marked as counted active -+ if (!thread.isCountedInPoolActiveThreadCount.getAndSet(true)) { -+ boolean mustUndoSetCountedInPoolActiveThreadCount = true; -+ try { -+ // Continue if the active thread count is successfully incremented -+ if (this.incrementActiveThreadCountIfSmallerThanIntended()) { -+ // Activate the thread if possible -+ if (!thread.activateIfNotListeningForTasksAndNotRestricted()) { -+ // If not possible, undo the active thread count increment -+ if (this.activeThreadCount.decrementAndGet() == this.lastComputedIntendedActiveThreadCount) { -+ // The number of active threads matches, return -+ return; -+ } -+ } else { -+ madeChanges = true; -+ mustUndoSetCountedInPoolActiveThreadCount = false; -+ if (this.activeThreadCount.get() == this.lastComputedIntendedActiveThreadCount) { -+ // The number of active threads matches, return -+ return; -+ } -+ } -+ } -+ } finally { -+ if (mustUndoSetCountedInPoolActiveThreadCount) { -+ // Undo setting isCountedInPoolActiveThreadCount -+ thread.isCountedInPoolActiveThreadCount.set(false); -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ // Add more active threads by adding threads to the thread pool -+ int surplusThreadsToAdd = this.lastComputedIntendedActiveThreadCount - this.activeThreadCount.get(); -+ if (surplusThreadsToAdd == 0) { -+ // The number of active threads matches, return -+ return; -+ } else if (surplusThreadsToAdd > 0) { -+ // We need to add surplus threads -+ //noinspection StatementWithEmptyBody -+ while (!this.threadsLock.tryAcquire()) ; -+ try { -+ surplusThreadsToAdd = this.lastComputedIntendedActiveThreadCount - this.activeThreadCount.get(); -+ int oldThreadsLength = this.threads.length; -+ int newThreadsLength = oldThreadsLength + surplusThreadsToAdd; -+ T[] newThreads = Arrays.copyOf(this.threads, newThreadsLength); -+ for (int i = oldThreadsLength; i < newThreadsLength; i++) { -+ newThreads[i] = createThread(i); -+ } -+ madeChanges = true; -+ this.threads = newThreads; -+ for (int i = oldThreadsLength; i < newThreadsLength; i++) { -+ if (!newThreads[i].isCountedInPoolActiveThreadCount.getAndSet(true)) { -+ this.activeThreadCount.incrementAndGet(); -+ } -+ this.incrementRunnableThreadCount(); -+ newThreads[i].start(); -+ } -+ } finally { -+ this.threadsLock.release(); -+ } -+ } -+ } else { -+ // Stop some threads from listening to new tasks -+ for (byte alsoTryPositiveYieldDepth = 0; alsoTryPositiveYieldDepth < 2; alsoTryPositiveYieldDepth++) { -+ for (T thread : this.threads) { -+ // Speculatively verify that the thread is waiting and registered for new tasks -+ if (thread.isWaiting && thread.isRegisteredForNewTasks) { -+ // First attempt to choose a thread that has a yield depth of zero -+ if (alsoTryPositiveYieldDepth == 1 || thread.yieldDepth == 0) { -+ // Continue if the thread is successfully unmarked as counted active -+ if (thread.isCountedInPoolActiveThreadCount.getAndSet(false)) { -+ boolean mustUndoSetCountedInPoolActiveThreadCount = true; -+ try { -+ // Continue if the active thread count is successfully incremented -+ if (this.decrementActiveThreadCountIfLargerThanIntended()) { -+ // Activate the thread if possible -+ if (!thread.deactivateIfListeningForTasks()) { -+ // If not possible, undo the active thread count decrement -+ if (this.activeThreadCount.incrementAndGet() == this.lastComputedIntendedActiveThreadCount) { -+ // The number of active threads matches, return -+ return; -+ } -+ } else { -+ madeChanges = true; -+ mustUndoSetCountedInPoolActiveThreadCount = false; -+ if (this.activeThreadCount.get() == this.lastComputedIntendedActiveThreadCount) { -+ // The number of active threads matches, return -+ return; -+ } -+ } -+ } -+ } finally { -+ if (mustUndoSetCountedInPoolActiveThreadCount) { -+ // Undo setting isCountedInPoolActiveThreadCount -+ thread.isCountedInPoolActiveThreadCount.set(true); -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ // Stop if nothing interesting changed -+ if (!this.repeatUpdateActiveThreadLoop && !madeChanges && this.activeThreadCount.get() == previousActiveThreadCount && (previousHadTasksForNewThreads || !AbstractTaskQueue.taskQueuesHaveTasks(this.taskQueues))) { -+ return; -+ } -+ this.repeatUpdateActiveThreadLoop = false; -+ } -+ } finally { -+ this.updateActiveThreadsIsOngoing.set(false); -+ } -+ } else { -+ this.repeatUpdateActiveThreadLoop = true; -+ } -+ } -+ -+ /** -+ * To be called when the result of {@link #computeIntendedActiveThreadCount()} may have changed. -+ */ -+ public void intendedActiveThreadCountMayHaveChanged() { -+ int previousLastComputedIntendedActiveThreadCount = this.lastComputedIntendedActiveThreadCount; -+ int newComputedIntendedActiveThreadCount = this.computeIntendedActiveThreadCount(); -+ if (previousLastComputedIntendedActiveThreadCount != newComputedIntendedActiveThreadCount) { -+ this.lastComputedIntendedActiveThreadCount = newComputedIntendedActiveThreadCount; -+ this.updateActiveThreads(); -+ } -+ } -+ -+ /** -+ * A {@link WaitingThreadSet} of member threads waiting for tasks to be added. -+ */ -+ public class WaitingMemberThreadSet extends IndexArrayWaitingThreadSet { -+ -+ @Override -+ protected int getThreadIndex(T thread) { -+ return thread.index; -+ } -+ -+ @Override -+ protected @NotNull T getThreadByIndex(int index) { -+ return ConditionalSurplusThreadPool.this.threads[index]; -+ } -+ -+ } -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/pooled/ServerThread.java b/src/main/java/org/galemc/gale/executor/thread/pooled/ServerThread.java -new file mode 100644 -index 0000000000000000000000000000000000000000..048c33d025fcd0b12780c08e53498a10380136e5 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/pooled/ServerThread.java -@@ -0,0 +1,77 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.pooled; -+ -+import io.papermc.paper.util.TickThread; -+import net.minecraft.server.MinecraftServer; -+import org.galemc.gale.executor.thread.BaseYieldingThread; -+import org.jetbrains.annotations.NotNull; -+import org.spigotmc.WatchdogThread; -+ -+/** -+ * A {@link TickThread} that provides an implementation for {@link BaseYieldingThread}, -+ * that is shared between the {@link MinecraftServer#serverThread} and {@link WatchdogThread#instance}. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+public class ServerThread extends TickThread { -+ -+ /** -+ * Whether this thread is currently blocked, specifically meaning: whether this thread has at some earlier point -+ * been in the {@link Thread.State#RUNNABLE} state, but is now (or about to be, or just was) in a state that is -+ * not {@link Thread.State#NEW}, {@link Thread.State#RUNNABLE} or {@link Thread.State#TERMINATED}. -+ */ -+ public volatile boolean isBlocked; -+ -+ protected ServerThread(final String name) { -+ super(name); -+ } -+ -+ protected ServerThread(final Runnable run, final String name) { -+ super(run, name); -+ } -+ -+ @Override -+ protected boolean wantsToPoll() { -+ return true; -+ } -+ -+ @Override -+ protected boolean willWaitAfterPollFailure() { -+ return true; -+ } -+ -+ @Override -+ protected void wokeUpAfterNonBecomeAllowedToPollSignal() {} -+ -+ @Override -+ protected void preBlockThread() { -+ this.isBlocked = true; -+ TickAssistThreadPool.instance.intendedActiveThreadCountMayHaveChanged(); -+ AsyncThreadPool.instance.intendedActiveThreadCountMayHaveChanged(); -+ } -+ -+ @Override -+ protected void postBlockThread() { -+ this.isBlocked = false; -+ TickAssistThreadPool.instance.intendedActiveThreadCountMayHaveChanged(); -+ AsyncThreadPool.instance.intendedActiveThreadCountMayHaveChanged(); -+ } -+ -+ /** -+ * This method must not be called while {@link MinecraftServer#isConstructed} is false. -+ * -+ * @return The global {@link ServerThread} instance, which is either -+ * {@link MinecraftServer#serverThread}, or {@link WatchdogThread#instance} while the server is shutting -+ * down and the {@link WatchdogThread} was responsible. -+ */ -+ public static @NotNull ServerThread getInstance() { -+ if (MinecraftServer.SERVER.hasStopped) { -+ if (MinecraftServer.SERVER.shutdownThread == WatchdogThread.instance) { -+ return WatchdogThread.instance; -+ } -+ } -+ return MinecraftServer.serverThread; -+ } -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/pooled/ServerThreadPool.java b/src/main/java/org/galemc/gale/executor/thread/pooled/ServerThreadPool.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ec0b6055cd653ef4b7f9e86267a563fe857d8942 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/pooled/ServerThreadPool.java -@@ -0,0 +1,95 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.pooled; -+ -+import io.papermc.paper.util.TickThread; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.entity.Entity; -+import org.galemc.gale.executor.queue.AbstractTaskQueue; -+import org.galemc.gale.executor.queue.BaseTaskQueues; -+import org.galemc.gale.executor.thread.wait.SignalReason; -+import org.galemc.gale.executor.thread.wait.WaitingServerThreadSet; -+import org.galemc.gale.executor.thread.wait.WaitingThreadSet; -+ -+/** -+ * An implementation of {@link AbstractYieldingSignallableThreadPool} for {@link ServerThread}s. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+public final class ServerThreadPool implements AbstractYieldingSignallableThreadPool { -+ -+ /** -+ * The queues that {@link ServerThread}s poll from. -+ *
-+ * 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. -+ */ -+ public static final AbstractTaskQueue[] taskQueues = { -+ BaseTaskQueues.deferredToServerThread, -+ BaseTaskQueues.serverThreadTick, -+ BaseTaskQueues.anyTickScheduledServerThread, -+ BaseTaskQueues.allLevelsScheduledChunkCache, -+ BaseTaskQueues.allLevelsScheduledTickThreadChunk -+ }; -+ -+ /** -+ * A {@link WaitingThreadSet} of {@link ServerThread}s waiting for potentially yielding tasks to be added. -+ */ -+ public final static WaitingThreadSet threadsWaitingForYieldingTasks = new WaitingServerThreadSet(); -+ -+ /** -+ * A {@link WaitingThreadSet} of {@link ServerThread}s waiting for yield-free tasks to be added. -+ */ -+ public final static WaitingThreadSet threadsWaitingForFreeTasks = new WaitingServerThreadSet() { -+ -+ @Override -+ public boolean pollAndSignal(SignalReason reason) { -+ if (super.pollAndSignal(reason)) { -+ return true; -+ } -+ /* -+ Waiting for potentially yielding tasks implies waiting for yield-free tasks, -+ so we signal threads waiting for potentially yielding tasks too. -+ */ -+ return threadsWaitingForYieldingTasks.pollAndSignal(reason); -+ } -+ -+ }; -+ -+ public static final ServerThreadPool instance = new ServerThreadPool(); -+ -+ private ServerThreadPool() { -+ for (AbstractTaskQueue queue : taskQueues) { -+ queue.setThreadPool(this); -+ } -+ } -+ -+ @Override -+ public boolean signalNewTasks(SignalReason reason, boolean yielding) { -+ ServerThread serverThread = ServerThread.getInstance(); -+ if (!yielding || !serverThread.isRestrictedDueToYieldDepth) { -+ return serverThread.signal(reason); -+ } -+ return false; -+ } -+ -+ @Override -+ public int getRunnableThreadCount() { -+ return ServerThread.getInstance().isBlocked ? 0 : 1; -+ } -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/pooled/TargetParallelism.java b/src/main/java/org/galemc/gale/executor/thread/pooled/TargetParallelism.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9a4c05bb45b6bef224e42883d7fd6ee7309bf63c ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/pooled/TargetParallelism.java -@@ -0,0 +1,75 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.pooled; -+ -+import org.galemc.gale.util.CPUCoresEstimation; -+ -+/** -+ * A utility class to determine the target parallelism for thread pools by the number of available CPU cores. -+ *
-+ * The value is currently automatically determined according to the following table: -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ *
system corescores spared
≤ 30
[4, 14]1
[15, 23]2
[24, 37]3
[38, 54]4
[55, 74]5
[75, 99]6
[100, 127]7
[128, 158]8
[159, 193]9
[194, 232]10
[233, 274]11
≥ 27512
-+ * Then target parallelism = system cores - cores spared. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+public final class TargetParallelism { -+ -+ private TargetParallelism() {} -+ -+ static int computeTargetParallelism(String environmentVariable) { -+ int parallelismByEnvironmentVariable = Integer.getInteger(environmentVariable, -1); ++ public static final int targetParallelism; ++ static { ++ int parallelismByEnvironmentVariable = Integer.getInteger(targetParallelismEnvironmentVariable, -1); + int targetParallelismBeforeSetAtLeastOne; + if (parallelismByEnvironmentVariable >= 0) { + targetParallelismBeforeSetAtLeastOne = parallelismByEnvironmentVariable; @@ -6158,636 +6556,118 @@ index 0000000000000000000000000000000000000000..9a4c05bb45b6bef224e42883d7fd6ee7 + } + targetParallelismBeforeSetAtLeastOne = systemCores - coresSpared; + } -+ return Math.max(1, targetParallelismBeforeSetAtLeastOne); -+ } -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/pooled/TickAssistThread.java b/src/main/java/org/galemc/gale/executor/thread/pooled/TickAssistThread.java -new file mode 100644 -index 0000000000000000000000000000000000000000..da9265f13dea8a0b971402fa7e09d30f44f38be4 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/pooled/TickAssistThread.java -@@ -0,0 +1,39 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.pooled; -+ -+import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; -+import org.galemc.gale.executor.annotation.YieldFree; -+import org.jetbrains.annotations.Nullable; -+ -+/** -+ * This class allows using instanceof to quickly check if the current thread is a {@link TickAssistThread} -+ * and provides some thread-local data. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+public final class TickAssistThread extends ConditionalSurplusThread { -+ -+ TickAssistThread(TickAssistThreadPool pool, int index) { -+ super(pool, index); ++ targetParallelism = Math.max(1, targetParallelismBeforeSetAtLeastOne); + } + + /** -+ * @return The current thread if it is a {@link TickAssistThread}, or null otherwise. -+ */ -+ @AnyThreadSafe -+ @YieldFree -+ public static @Nullable TickAssistThread currentTickAssistThread() { -+ return Thread.currentThread() instanceof TickAssistThread tickAssistThread ? tickAssistThread : null; -+ } -+ -+ /** -+ * @return Whether the current thread is a {@link TickAssistThread}. -+ */ -+ @AnyThreadSafe -+ @YieldFree -+ public static boolean isTickAssistThread() { -+ return Thread.currentThread() instanceof TickAssistThread; -+ } -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/pooled/TickAssistThreadPool.java b/src/main/java/org/galemc/gale/executor/thread/pooled/TickAssistThreadPool.java -new file mode 100644 -index 0000000000000000000000000000000000000000..51a94ffbd039260bdb84ee80131ae18280ce6064 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/pooled/TickAssistThreadPool.java -@@ -0,0 +1,116 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.pooled; -+ -+import io.papermc.paper.util.TickThread; -+import net.minecraft.server.MinecraftServer; -+import org.galemc.gale.executor.queue.AbstractTaskQueue; -+import org.galemc.gale.executor.queue.BaseTaskQueues; -+ -+import java.util.concurrent.atomic.AtomicInteger; -+ -+/** -+ * A {@link ConditionalSurplusThreadPool} with threads that -+ * can perform tasks that are part of ticking to assist the main ticking thread(s) in doing so. -+ * Such tasks could in principle be executed on a {@link ServerThread} or {@link TickThread}, but should not be, -+ * to keep {@link ServerThread}s and {@link TickThread}s immediately available to run tasks specially for them. -+ *
-+ * This pool intends to keep {@link #targetParallelism} threads active at any time, -+ * but subtracts 1 for each active {@link ServerThread} or {@link TickThread}. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+public final class TickAssistThreadPool extends ConditionalSurplusThreadPool { -+ -+ public static final String targetParallelismEnvironmentVariable = "gale.threads.tickassist"; -+ -+ public static final int TICK_ASSIST_THREAD_PRIORITY = Integer.getInteger("gale.thread.priority.tickassist", 7); -+ -+ public static final int MAXIMUM_YIELD_DEPTH = Integer.getInteger("gale.maxyielddepth.tickassist", 100); -+ -+ /** -+ * The target number of threads that will be actively in use by this pool. -+ * Any active threads with higher priority will be dynamically subtracted from this target. ++ * The maximum number of threads to be executing tasks, that only have tasks on their thread that are strictly ++ * below a certain tier, before a thread wishing to execute such tasks gets activated regardless. ++ * If this threshold of lower tier threads is not exceeded, activating a thread to execute a higher tier task ++ * will be delayed until one of the active threads finishes execution of their stack or blocks for another ++ * reason. + *
-+ * This value is always positive. ++ * This value is always nonnegative. + *
-+ * Determined by {@link TargetParallelism#computeTargetParallelism(String)}, which can be overridden using -+ * the environment variable {@link #targetParallelismEnvironmentVariable}. ++ * This value is currently automatically determined according to the following rule: ++ *
    ++ *
  • 0, if {@link #targetParallelism} = 1
  • ++ *
  • {@code max(1, floor(2/5 * }{@link #targetParallelism}{@code ))}
  • ++ *
++ * The computed value above can be overridden using the {@link #maxUndisturbedLowerTierThreadCountEnvironmentVariable}. + */ -+ public static final int targetParallelism = TargetParallelism.computeTargetParallelism(targetParallelismEnvironmentVariable); -+ -+ /** -+ * This counter makes sure we do not constantly notify {@link AsyncThreadPool} of changes in the -+ * number of threads in this pool during {@link #updateActiveThreads()}. -+ */ -+ public static final AtomicInteger doNotNotifyOfThreadCountChangesCounter = new AtomicInteger(); -+ -+ /** -+ * Whether this pool skipped notifying {@link AsyncThreadPool} due to -+ * {@link #doNotNotifyOfThreadCountChangesCounter}. -+ */ -+ public static volatile boolean skippedNotifyOfThreadCountChanges = false; -+ -+ public static final TickAssistThreadPool instance; ++ public static final int maxUndisturbedLowerTierThreadCount; + static { -+ // Make sure we do not notify the AsyncThreadPool while instance is not set yet -+ doNotNotifyOfThreadCountChangesCounter.incrementAndGet(); -+ instance = new TickAssistThreadPool(); -+ if (doNotNotifyOfThreadCountChangesCounter.decrementAndGet() == 0 && skippedNotifyOfThreadCountChanges) { -+ skippedNotifyOfThreadCountChanges = false; -+ AsyncThreadPool.instance.intendedActiveThreadCountMayHaveChanged(); -+ } ++ int maxUndisturbedLowerTierThreadCountByEnvironmentVariable = Integer.getInteger(maxUndisturbedLowerTierThreadCountEnvironmentVariable, -1); ++ maxUndisturbedLowerTierThreadCount = maxUndisturbedLowerTierThreadCountByEnvironmentVariable >= 0 ? maxUndisturbedLowerTierThreadCountByEnvironmentVariable : targetParallelism == 1 ? 0 : Math.max(1, targetParallelism * 2 / 5); + } + -+ private TickAssistThreadPool() { -+ super(new TickAssistThread[0], "Tick Assist Thread", new AbstractTaskQueue[] { -+ BaseTaskQueues.tickAssist -+ }, MAXIMUM_YIELD_DEPTH); -+ } -+ -+ @Override -+ public TickAssistThread createThread(int index) { -+ TickAssistThread thread = new TickAssistThread(this, index); -+ thread.setPriority(TICK_ASSIST_THREAD_PRIORITY); -+ return thread; -+ } -+ -+ @Override -+ protected int computeIntendedActiveThreadCount() { -+ if (!MinecraftServer.isConstructed) { -+ return 0; -+ } -+ return targetParallelism - ServerThreadPool.instance.getRunnableThreadCount(); -+ } -+ -+ @Override -+ public void incrementRunnableThreadCount() { -+ super.incrementRunnableThreadCount(); -+ if (doNotNotifyOfThreadCountChangesCounter.get() == 0) { -+ AsyncThreadPool.instance.intendedActiveThreadCountMayHaveChanged(); -+ } else { -+ skippedNotifyOfThreadCountChanges = true; -+ } -+ } -+ -+ @Override -+ public void decrementRunnableThreadCount() { -+ super.decrementRunnableThreadCount(); -+ if (doNotNotifyOfThreadCountChangesCounter.get() == 0) { -+ AsyncThreadPool.instance.intendedActiveThreadCountMayHaveChanged(); -+ } else { -+ skippedNotifyOfThreadCountChanges = true; -+ } -+ } -+ -+ @Override -+ void updateActiveThreads() { -+ doNotNotifyOfThreadCountChangesCounter.incrementAndGet(); -+ super.updateActiveThreads(); -+ if (doNotNotifyOfThreadCountChangesCounter.decrementAndGet() == 0 && skippedNotifyOfThreadCountChanges) { -+ skippedNotifyOfThreadCountChanges = false; -+ AsyncThreadPool.instance.intendedActiveThreadCountMayHaveChanged(); -+ } -+ } -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/wait/IndexArrayWaitingThreadSet.java b/src/main/java/org/galemc/gale/executor/thread/wait/IndexArrayWaitingThreadSet.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a3c11208a831a8e55dfd0cfc6698ea571f602e75 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/wait/IndexArrayWaitingThreadSet.java -@@ -0,0 +1,161 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.wait; -+ -+import org.galemc.gale.concurrent.Mutex; -+import org.galemc.gale.executor.annotation.Access; -+import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; -+import org.galemc.gale.executor.annotation.Guarded; -+import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.lock.YieldingLock; -+import org.galemc.gale.executor.thread.SignallableThread; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.Arrays; -+ -+/** -+ * A set of waiting threads. This is used to collect the threads that are all waiting for new work, -+ * e.g. for new tasks to be added to a queue or for a {@link YieldingLock} to be released. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+@AnyThreadSafe -+@YieldFree -+public abstract class IndexArrayWaitingThreadSet implements WaitingThreadSet { -+ + /** -+ * A set of the indices of the threads in this collection. ++ * An array of the {@link AssistThread}s in this pool, indexed by their {@link AssistThread#assistThreadIndex}. ++ *
++ * This field must only ever be changed from within {@link #addAssistThread}. + */ -+ @Guarded(value = "#lock", fieldAccess = Access.WRITE) -+ private volatile boolean[] elements = new boolean[0]; ++ private static volatile AssistThread[] assistThreads = new AssistThread[0]; + + /** -+ * An index in {@link #elements} that is likely to contain {@code true}, or an arbitrary but valid index otherwise. ++ * An array of the {@link BaseThread}s in this pool, indexed by their {@link BaseThread#baseThreadIndex}. ++ *
++ * This field must not be referenced anywhere outside {@link #addAssistThread} or {@link #getBaseThreads()}: ++ * it only holds the last computed value. + */ -+ @Guarded(value = "#lock", fieldAccess = Access.WRITE) -+ private volatile int potentialLowestPresentElementIndex; -+ -+ private final Mutex lock = Mutex.create(); -+ -+ @Override -+ public void add(Thread thread) { -+ @SuppressWarnings("unchecked") -+ int index = this.getThreadIndex((T) thread); -+ // Store field in local variable for non-volatile access -+ boolean[] elements = this.elements; -+ if (elements.length > index && elements[index]) { -+ return; -+ } -+ //noinspection StatementWithEmptyBody -+ while (!this.lock.tryAcquire()); -+ try { -+ // Store field in local variable for non-volatile access -+ elements = this.elements; -+ if (elements.length <= index) { -+ this.elements = elements = Arrays.copyOf(elements, index + 1); -+ } -+ elements[index] = true; -+ this.potentialLowestPresentElementIndex = index; -+ } finally { -+ this.lock.release(); -+ } -+ } -+ -+ @Override -+ public void remove(Thread thread) { -+ @SuppressWarnings("unchecked") -+ int index = this.getThreadIndex((T) thread); -+ // Store field in local variable for non-volatile access -+ boolean[] elements = this.elements; -+ if (elements.length <= index || !elements[index]) { -+ return; -+ } -+ //noinspection StatementWithEmptyBody -+ while (!this.lock.tryAcquire()); -+ try { -+ this.elements[index] = false; -+ } finally { -+ this.lock.release(); -+ } -+ } -+ -+ @Override -+ public boolean pollAndSignal(SignalReason reason) { -+ @Nullable T polled = this.poll(); -+ //noinspection SimplifiableConditionalExpression -+ return polled != null ? polled.signal(reason) : false; -+ } -+ -+ private @Nullable T poll() { -+ if (this.elements.length == 0) { -+ return null; -+ } -+ // Attempt to poll an element while avoiding acquiring the lock for as long as possible -+ attemptWhileAvoidingLock: while (true) { -+ // Store field in local variable for non-volatile access -+ boolean[] elements = this.elements; -+ int indexToTry = this.potentialLowestPresentElementIndex; -+ if (!elements[indexToTry]) { -+ indexToTry = 0; -+ // On breaking, we found an index with a true value, let's acquire the lock and try -+ while (!elements[indexToTry]) { -+ indexToTry++; -+ if (indexToTry == elements.length) { -+ // We tried every index, stop attempting to poll an element while avoiding acquiring the lock -+ break attemptWhileAvoidingLock; -+ } -+ } -+ } -+ //noinspection StatementWithEmptyBody -+ while (!this.lock.tryAcquire()); -+ try { -+ // Store field in local variable for non-volatile access -+ elements = this.elements; -+ if (elements[indexToTry]) { -+ elements[indexToTry] = false; -+ return this.getThreadByIndex(indexToTry); -+ } -+ } finally { -+ this.lock.release(); -+ } -+ } -+ /* -+ Polling an element while avoiding acquiring the lock for as long as possible failed, -+ we will now quickly try again with the lock to be sure. -+ */ -+ //noinspection StatementWithEmptyBody -+ while (!this.lock.tryAcquire()); -+ try { -+ // Store fields in local variables for non-volatile access -+ boolean[] elements = this.elements; -+ int potentialLowestPresentElementIndex = this.potentialLowestPresentElementIndex; -+ if (elements[potentialLowestPresentElementIndex]) { -+ elements[potentialLowestPresentElementIndex] = false; -+ return this.getThreadByIndex(potentialLowestPresentElementIndex); -+ } -+ for (int index = 0; index < elements.length; index++) { -+ if (elements[index]) { -+ elements[index] = false; -+ return this.getThreadByIndex(index); -+ } -+ } -+ } finally { -+ this.lock.release(); -+ } -+ // Polling an element failed -+ return null; -+ } ++ private static volatile @Nullable BaseThread @NotNull [] lastComputedBaseThreads = new BaseThread[1]; + + /** -+ * @return The index of the given thread, to be used as input to {@link #getThreadByIndex(int)} later. ++ * Creates a new {@link AssistThread}, adds it to this pool and starts it. ++ *
++ * Must only be called from within {@link BaseThreadActivation#update()} while ++ * {@link BaseThreadActivation#isUpdateOngoing} is true. + */ -+ protected abstract int getThreadIndex(T thread); ++ public static void addAssistThread() { ++ int oldThreadsLength = assistThreads.length; ++ int newThreadsLength = oldThreadsLength + 1; ++ // Expand the thread array ++ AssistThread[] newAssistThreads = Arrays.copyOf(assistThreads, newThreadsLength); ++ // Create the new thread ++ AssistThread newThread = newAssistThreads[oldThreadsLength] = new AssistThread(oldThreadsLength); ++ // Save the new thread array ++ assistThreads = newAssistThreads; ++ // Update the assist threads in baseThreads ++ @SuppressWarnings("NonAtomicOperationOnVolatileField") ++ BaseThread[] newLastComputedBaseThreads = lastComputedBaseThreads = Arrays.copyOf(lastComputedBaseThreads, newThreadsLength + 1); ++ newLastComputedBaseThreads[newThreadsLength] = newThread; ++ // Start the thread ++ newThread.start(); ++ MinecraftServer.THREAD_DEBUG_LOGGER.ifPresent(it -> it.info("Added assist thread " + newAssistThreads.length)); ++ } + + /** -+ * @return The thread with the given {@link #getThreadIndex}. This is always non-null -+ * as this method will never be called with an invalid index. ++ * The {@link BaseThread}s ({@link ServerThread}s and {@link AssistThread}s) in this thread pool, ++ * specifically for the purpose of easy iteration. ++ *
++ * Note that the {@link ServerThread} at index 0 may be null if {@link MinecraftServer#isConstructed} is false. ++ *
++ * Must only be called from within {@link BaseThreadActivation#update()} while ++ * {@link BaseThreadActivation#isUpdateOngoing} is true. + */ -+ protected abstract @NotNull T getThreadByIndex(int index); -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/wait/SignalReason.java b/src/main/java/org/galemc/gale/executor/thread/wait/SignalReason.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e6189d5b796e369566ed6322a41f57cf4e347981 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/wait/SignalReason.java -@@ -0,0 +1,56 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.wait; -+ -+import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; -+import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.thread.SignallableThread; -+import org.galemc.gale.executor.thread.pooled.AbstractYieldingSignallableThreadPool; -+ -+/** -+ * An interface to indicate the reason of a call to {@link SignallableThread#signal}. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+public interface SignalReason { ++ static @Nullable BaseThread @NotNull [] getBaseThreads() { ++ // Store in a non-local volatile ++ @Nullable BaseThread @NotNull [] baseThreads = lastComputedBaseThreads; ++ // Update the server thread if necessary ++ baseThreads[0] = ServerThread.getInstanceIfConstructed(); ++ // Return the value ++ return baseThreads; ++ } + + /** -+ * Signals another {@link SignallableThread} that is waiting for the same reason. ++ * This method must not be called with {@code index} = 0 while {@link MinecraftServer#isConstructed} is false. + * -+ * @return Whether any thread was signalled. ++ * @return The {@link BaseThread} with the given {@link BaseThread#baseThreadIndex}. ++ * This must not be called + */ -+ boolean signalAnother(); -+ -+ @AnyThreadSafe -+ @YieldFree -+ static SignalReason createForThreadPoolNewTasks(AbstractYieldingSignallableThreadPool threadPool, boolean yielding) { -+ return new SignalReason() { -+ -+ @Override -+ public boolean signalAnother() { -+ return threadPool.signalNewTasks(this, yielding); -+ } -+ -+ }; -+ } -+ -+ @AnyThreadSafe -+ @YieldFree -+ static SignalReason createForWaitingThreadSet(WaitingThreadSet waitingThreads) { -+ return new SignalReason() { -+ -+ @Override -+ public boolean signalAnother() { -+ return waitingThreads.pollAndSignal(this); -+ } -+ -+ }; -+ } -+ -+ @AnyThreadSafe -+ @YieldFree -+ static SignalReason createNonRetrying() { -+ return () -> false; -+ } -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/wait/WaitingBaseThreadSet.java b/src/main/java/org/galemc/gale/executor/thread/wait/WaitingBaseThreadSet.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d0cabea27a7cb1f18ed9ffe46409968cbbd8f891 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/wait/WaitingBaseThreadSet.java -@@ -0,0 +1,121 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.wait; -+ -+import org.galemc.gale.concurrent.Mutex; -+import org.galemc.gale.executor.thread.BaseYieldingThread; -+import org.galemc.gale.executor.thread.pooled.AsyncThread; -+import org.galemc.gale.executor.thread.pooled.AsyncThreadPool; -+import org.galemc.gale.executor.thread.pooled.ServerThread; -+import org.galemc.gale.executor.thread.pooled.TickAssistThread; -+import org.galemc.gale.executor.thread.pooled.TickAssistThreadPool; -+import org.jetbrains.annotations.Nullable; -+ -+/** -+ * A set of waiting {@link BaseYieldingThread}s that are an instance of either {@link ServerThread}, -+ * {@link TickAssistThread} or {@link AsyncThread}. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+public final class WaitingBaseThreadSet implements WaitingThreadSet { -+ -+ /** -+ * The {@link ServerThread}s in this collection, -+ * or null if no {@link ServerThread} was ever added to this collection. -+ */ -+ private @Nullable WaitingServerThreadSet serverThreads; -+ -+ /** -+ * The {@link TickAssistThread}s in this collection, -+ * or null if no {@link ServerThread} was ever added to this collection. -+ */ -+ private @Nullable WaitingThreadSet tickAssistThreads; -+ -+ /** -+ * The {@link AsyncThread}s in this collection, -+ * or null if no {@link ServerThread} was ever added to this collection. -+ */ -+ private @Nullable WaitingThreadSet asyncThreads; -+ -+ /** -+ * A simple lock for initializing the subsets in this collection. -+ */ -+ private final Mutex initializeSubsetsLock = Mutex.create(); -+ -+ @Override -+ public void add(Thread thread) { -+ if (thread instanceof ServerThread) { -+ if (this.serverThreads == null) { -+ //noinspection StatementWithEmptyBody -+ while (!this.initializeSubsetsLock.tryAcquire()); -+ try { -+ if (this.serverThreads == null) { -+ this.serverThreads = new WaitingServerThreadSet(); -+ } -+ } finally { -+ this.initializeSubsetsLock.release(); -+ } -+ } -+ this.serverThreads.add(thread); -+ } else if (thread instanceof TickAssistThread) { -+ if (this.tickAssistThreads == null) { -+ //noinspection StatementWithEmptyBody -+ while (!this.initializeSubsetsLock.tryAcquire()); -+ try { -+ if (this.tickAssistThreads == null) { -+ this.tickAssistThreads = TickAssistThreadPool.instance.new WaitingMemberThreadSet(); -+ } -+ } finally { -+ this.initializeSubsetsLock.release(); -+ } -+ } -+ this.tickAssistThreads.add(thread); -+ } else if (thread instanceof AsyncThread) { -+ if (this.asyncThreads == null) { -+ //noinspection StatementWithEmptyBody -+ while (!this.initializeSubsetsLock.tryAcquire()); -+ try { -+ if (this.asyncThreads == null) { -+ this.asyncThreads = AsyncThreadPool.instance.new WaitingMemberThreadSet(); -+ } -+ } finally { -+ this.initializeSubsetsLock.release(); -+ } -+ } -+ this.asyncThreads.add(thread); ++ public static @NotNull BaseThread getThreadByBaseIndex(int index) { ++ if (index == 0) { ++ return ServerThread.getInstance(); + } -+ throw new IllegalArgumentException("WaitingBaseThreadSet.add received a thread that is not a base thread"); ++ return assistThreads[index - 1]; + } + -+ @Override -+ public void remove(Thread thread) { -+ if (thread instanceof ServerThread) { -+ if (this.serverThreads != null) { -+ this.serverThreads.remove(thread); -+ } -+ } else if (thread instanceof TickAssistThread) { -+ if (this.tickAssistThreads != null) { -+ this.tickAssistThreads.add(thread); -+ } -+ } else if (thread instanceof AsyncThread) { -+ if (this.asyncThreads != null) { -+ this.asyncThreads.remove(thread); -+ } -+ } -+ throw new IllegalArgumentException("WaitingBaseThreadSet.remove received a thread that is not a base thread"); -+ } -+ -+ @SuppressWarnings("RedundantIfStatement") -+ @Override -+ public boolean pollAndSignal(SignalReason reason) { -+ if (this.serverThreads != null && this.serverThreads.pollAndSignal(reason)) { -+ return true; -+ } else if (this.tickAssistThreads != null && this.tickAssistThreads.pollAndSignal(reason)) { -+ return true; -+ } else if (this.asyncThreads != null && this.asyncThreads.pollAndSignal(reason)) { -+ return true; -+ } -+ return false; -+ } -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/wait/WaitingServerThreadSet.java b/src/main/java/org/galemc/gale/executor/thread/wait/WaitingServerThreadSet.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2e631d1c3c1cbbad9d22a187077e148b9f60071f ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/wait/WaitingServerThreadSet.java -@@ -0,0 +1,37 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.wait; -+ -+import org.galemc.gale.executor.thread.pooled.ServerThread; -+ -+/** -+ * A {@link WaitingThreadSet} that simply remembers whether a {@link ServerThread} is waiting for tasks. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+public class WaitingServerThreadSet implements WaitingThreadSet { -+ -+ public volatile boolean waiting; -+ -+ @Override -+ public void add(Thread thread) { -+ waiting = true; -+ } -+ -+ @Override -+ public void remove(Thread thread) { -+ waiting = false; -+ } -+ -+ @Override -+ public boolean pollAndSignal(SignalReason reason) { -+ if (waiting) { -+ //noinspection RedundantIfStatement -+ if (ServerThread.getInstance().signal(reason)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+} -diff --git a/src/main/java/org/galemc/gale/executor/thread/wait/WaitingThreadSet.java b/src/main/java/org/galemc/gale/executor/thread/wait/WaitingThreadSet.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a22f4815cbf6411ac32a070d1769c2389c8140b4 ---- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/thread/wait/WaitingThreadSet.java -@@ -0,0 +1,52 @@ -+// Gale - base thread pools -+ -+package org.galemc.gale.executor.thread.wait; -+ -+import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; -+import org.galemc.gale.executor.annotation.YieldFree; -+import org.galemc.gale.executor.lock.YieldingLock; -+import org.galemc.gale.executor.thread.SignallableThread; -+ -+/** -+ * An interface for a set of waiting threads. -+ * This is used to collect the threads that are all waiting for new work, -+ * e.g. for new tasks to be added to a queue or for a {@link YieldingLock} to be released. -+ * -+ * @author Martijn Muijsers under AGPL-3.0 -+ */ -+@AnyThreadSafe -+@YieldFree -+public interface WaitingThreadSet { -+ + /** -+ * Adds a waiting thread. -+ *
-+ * This method must only be called from the given thread itself. ++ * @return The same value as {@link #getThreadByBaseIndex} if {@link MinecraftServer#isConstructed} is true ++ * or if the given {@code index} is not 0, ++ * or null otherwise (i.e. if {@link MinecraftServer#isConstructed} is false and the given {@code index} is 0). + */ -+ void add(Thread thread); ++ @SuppressWarnings("unused") ++ public static @Nullable BaseThread getThreadByBaseIndexIfConstructed(int index) { ++ return index != 0 || MinecraftServer.isConstructed ? getThreadByBaseIndex(index) : null; ++ } + -+ /** -+ * Removes a waiting thread. -+ *
-+ * This method must only be called from the given thread itself. -+ */ -+ void remove(Thread thread); -+ -+ /** -+ * Attempts to signal one waiting thread. -+ *
-+ * Calling this method twice from different threads concurrently -+ * will never lead to signalling the same waiting thread: -+ * every thread is only signalled by this set at most one time per continuous period of time that it is present. -+ *
-+ * Calling this method may fail to signal a newly added thread that is added after this method is called, -+ * but before it returns, and may also fail to signal a just removed thread -+ * that was removed after this method was called, but before it returned. -+ * -+ * @return Whether a thread was signalled (this return value is always accurate). -+ * -+ * @see SignallableThread#signal -+ */ -+ boolean pollAndSignal(SignalReason reason); ++ public static AssistThread getThreadByAssistIndex(int index) { ++ return assistThreads[index]; ++ } + +} diff --git a/src/main/java/org/spigotmc/SpigotCommand.java b/src/main/java/org/spigotmc/SpigotCommand.java -index 3112a8695639c402e9d18710acbc11cff5611e9c..505181f041fc45a8812bf0b5199950022d3b3001 100644 +index 3112a8695639c402e9d18710acbc11cff5611e9c..7b38565b8699bd083c2114981feb2202321b8486 100644 --- a/src/main/java/org/spigotmc/SpigotCommand.java +++ b/src/main/java/org/spigotmc/SpigotCommand.java @@ -31,7 +31,7 @@ public class SpigotCommand extends Command { @@ -6795,12 +6675,12 @@ index 3112a8695639c402e9d18710acbc11cff5611e9c..505181f041fc45a8812bf0b519995002 MinecraftServer console = MinecraftServer.getServer(); org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); - for (ServerLevel world : console.getAllLevels()) { -+ for (ServerLevel world : console.getAllLevelsArray()) { // Gale - base thread pools - optimize server levels ++ for (ServerLevel world : console.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels world.spigotConfig.init(); } console.server.reloadCount++; diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index 832f1ee4fb11c981bd109510eb908d7c7ef91bd4..8cd1b04dafc16dc96a6ad58fef7930b351c8f147 100644 +index 832f1ee4fb11c981bd109510eb908d7c7ef91bd4..bcb144ec4a836b8b32f60726bcbee218a4f62742 100644 --- a/src/main/java/org/spigotmc/WatchdogThread.java +++ b/src/main/java/org/spigotmc/WatchdogThread.java @@ -1,6 +1,5 @@ @@ -6814,15 +6694,15 @@ index 832f1ee4fb11c981bd109510eb908d7c7ef91bd4..8cd1b04dafc16dc96a6ad58fef7930b3 import java.util.logging.Logger; import net.minecraft.server.MinecraftServer; import org.bukkit.Bukkit; -+import org.galemc.gale.executor.thread.pooled.ServerThread; ++import org.galemc.gale.executor.thread.ServerThread; -public final class WatchdogThread extends io.papermc.paper.util.TickThread // Paper - rewrite chunk system -+public final class WatchdogThread extends ServerThread // Paper - rewrite chunk system // Gale - base thread pools ++public final class WatchdogThread extends ServerThread // Paper - rewrite chunk system // Gale - base thread pool { public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper - private static WatchdogThread instance; -+ public static WatchdogThread instance; // Gale - base thread pools - private -> public ++ public static WatchdogThread instance; // Gale - base thread pool - private -> public private long timeoutTime; private boolean restart; private final long earlyWarningEvery; // Paper - Timeout time for just printing a dump but not restarting @@ -6831,7 +6711,7 @@ index 832f1ee4fb11c981bd109510eb908d7c7ef91bd4..8cd1b04dafc16dc96a6ad58fef7930b3 io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(isLongTimeout); // Paper // Paper - rewrite chunk system this.dumpTickingInfo(); // Paper - log detailed tick information - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); -+ WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.serverThread.getId(), Integer.MAX_VALUE ), log ); // Gale - base thread pools ++ WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.serverThread.getId(), Integer.MAX_VALUE ), log ); // Gale - base thread pool log.log( Level.SEVERE, "------------------------------" ); // // Paper start - Only print full dump on long timeouts diff --git a/patches/server/0148-Non-blocking-PooledObjects.patch b/patches/server/0147-Non-blocking-PooledObjects.patch similarity index 100% rename from patches/server/0148-Non-blocking-PooledObjects.patch rename to patches/server/0147-Non-blocking-PooledObjects.patch