diff --git a/patches/server/0044-SIMD-support.patch b/patches/server/0044-SIMD-support.patch index 4263800..f366b16 100644 --- a/patches/server/0044-SIMD-support.patch +++ b/patches/server/0044-SIMD-support.patch @@ -25,7 +25,7 @@ index 01780a73ee9cc602951bdf568c17673c4c639f79..4b279948e82a6dfd2f471ba698e361dc // Gale end - hide irrelevant compilation warnings diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 9c3ccbbd657d1605b8fabb6e01c11ff31f39a17e..1d1db89362b78ac34d46cdd71b9ab27404b98e48 100644 +index 9c3ccbbd657d1605b8fabb6e01c11ff31f39a17e..20ae254b1cd3d75781d1ea4b9859bf0ef92f173d 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -14,6 +14,8 @@ import java.util.Locale; @@ -45,7 +45,7 @@ index 9c3ccbbd657d1605b8fabb6e01c11ff31f39a17e..1d1db89362b78ac34d46cdd71b9ab274 import org.slf4j.Logger; // CraftBukkit start -@@ -223,6 +226,21 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -223,6 +226,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider // Paper end @@ -59,7 +59,6 @@ index 9c3ccbbd657d1605b8fabb6e01c11ff31f39a17e..1d1db89362b78ac34d46cdd71b9ab274 + LOGGER.warn("SIMD operations are available for your server, but are not configured!"); + LOGGER.warn("To enable additional optimizations, add \"--add-modules=jdk.incubator.vector\" to your startup flags, BEFORE the \"-jar\"."); + LOGGER.warn("If you have already added this flag, then SIMD operations are not supported on your JVM or CPU."); -+ LOGGER.warn("Debug: Java: " + System.getProperty("java.version") + ", test run: " + SIMDDetection.testRun); + LOGGER.warn("If you would like to disable this message, set simd.warn-if-disabled to false in gale-global.yml"); + } + // Gale start - Pufferfish - SIMD support diff --git a/patches/server/0064-Do-not-log-offline-mode-warning.patch b/patches/server/0064-Do-not-log-offline-mode-warning.patch index 8ec4f9f..cf3821c 100644 --- a/patches/server/0064-Do-not-log-offline-mode-warning.patch +++ b/patches/server/0064-Do-not-log-offline-mode-warning.patch @@ -41,10 +41,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 9302502f59243fa254c88a7a8d513a2a65c17979..f5ed3fa20097bdd43a25c76b38353a23743bc9e5 100644 +index 70d375b9803b5b7a33aa02cc1554f6b857aa6613..e4284c16512bbabd25c538eb88bf81d7c5bef9ba 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -293,7 +293,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -292,7 +292,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP); // CraftBukkit end diff --git a/patches/server/0141-Thread-safety-annotations.patch b/patches/server/0141-Thread-safety-annotations.patch index fa043da..995987b 100644 --- a/patches/server/0141-Thread-safety-annotations.patch +++ b/patches/server/0141-Thread-safety-annotations.patch @@ -8,7 +8,7 @@ Gale - https://galemc.org diff --git a/src/main/java/org/galemc/gale/executor/annotation/Access.java b/src/main/java/org/galemc/gale/executor/annotation/Access.java new file mode 100644 -index 0000000000000000000000000000000000000000..d07f68ff73a368c8f0da56152021a95474a601ca +index 0000000000000000000000000000000000000000..50541414e1d91ff06d108d9b3fe64dcb4ad09668 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/annotation/Access.java @@ -0,0 +1,39 @@ @@ -41,22 +41,22 @@ index 0000000000000000000000000000000000000000..d07f68ff73a368c8f0da56152021a954 + WRITE, + /** + * Both {@link #READ} and {@link #WRITE}: if the annotation is applied to a field, it holds for both access to -+ * the field's value, as well as modifications made to the field. ++ * the field's value, and for modifications made to the field. + *
+ * 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 new file mode 100644 -index 0000000000000000000000000000000000000000..84a0bac98a382550c826e6adbecec1fe7be974a1 +index 0000000000000000000000000000000000000000..6f1d1960953daf7f6f61643f5165e9a0760a647e --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/annotation/Guarded.java -@@ -0,0 +1,47 @@ +@@ -0,0 +1,55 @@ +// Gale - thread-safety annotations + +package org.galemc.gale.executor.annotation; @@ -83,7 +83,7 @@ index 0000000000000000000000000000000000000000..84a0bac98a382550c826e6adbecec1fe + * @author Martijn Muijsers under AGPL-3.0 + */ +@Documented -+@Repeatable ++@Repeatable(Guarded.Container.class) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +public @interface Guarded { + @@ -103,19 +103,25 @@ index 0000000000000000000000000000000000000000..84a0bac98a382550c826e6adbecec1fe + */ + 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 new file mode 100644 -index 0000000000000000000000000000000000000000..71f26852c96dea34ea07efe07f834f8262509957 +index 0000000000000000000000000000000000000000..a4dc0ebe48fdd352387f06be42ff46fc11ee5822 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/annotation/PotentiallyBlocking.java -@@ -0,0 +1,39 @@ +@@ -0,0 +1,34 @@ +// Gale - thread-safety annotations + +package org.galemc.gale.executor.annotation; + -+import org.galemc.gale.executor.thread.BaseThread; -+ +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; @@ -129,9 +135,6 @@ index 0000000000000000000000000000000000000000..71f26852c96dea34ea07efe07f834f82 + *
+ * In a method annotated with {@link PotentiallyBlocking}, fields and methods annotated with + * {@link PotentiallyBlocking}, {@link PotentiallyYielding} or {@link YieldFree} may all be used. -+ *
-+ * Methods that are potentially blocking, including those annotated with {@link PotentiallyBlocking}, must never -+ * be called on a {@link BaseThread}. + * + * @author Martijn Muijsers under AGPL-3.0 + */ @@ -151,7 +154,7 @@ index 0000000000000000000000000000000000000000..71f26852c96dea34ea07efe07f834f82 +} diff --git a/src/main/java/org/galemc/gale/executor/annotation/PotentiallyYielding.java b/src/main/java/org/galemc/gale/executor/annotation/PotentiallyYielding.java new file mode 100644 -index 0000000000000000000000000000000000000000..e87ee2612348fc559b21256cc7cadfc684f01f8e +index 0000000000000000000000000000000000000000..44b70d68ba6823ab72ea9af4b7774051785c0a2b --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/annotation/PotentiallyYielding.java @@ -0,0 +1,35 @@ @@ -165,7 +168,7 @@ index 0000000000000000000000000000000000000000..e87ee2612348fc559b21256cc7cadfc6 + +/** + * An annotation primarily for methods, identifying methods that do not block, but may yield to other tasks -+ * under certain circumstances, such as when attempting to acquire a {@link YieldingLock}. ++ * under certain circumstances. + *
+ * When applied to a class, this annotation indicates it holds for all methods, both instance and static, + * belonging to the class, or any superclass thereof, or any inner or statically nested class of the class or diff --git a/patches/server/0143-CheckableLock-utility.patch b/patches/server/0143-CheckableLock-utility.patch new file mode 100644 index 0000000..7cc18ec --- /dev/null +++ b/patches/server/0143-CheckableLock-utility.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martijn Muijsers +Date: Sun, 5 Feb 2023 19:11:39 +0100 +Subject: [PATCH] CheckableLock 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/CheckableLock.java b/src/main/java/org/galemc/gale/concurrent/CheckableLock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..749d8d8e2038554a21c2d879349d5189d86a8481 +--- /dev/null ++++ b/src/main/java/org/galemc/gale/concurrent/CheckableLock.java +@@ -0,0 +1,28 @@ ++// Gale - CheckableLock utility ++ ++package org.galemc.gale.concurrent; ++ ++import org.galemc.gale.executor.annotation.YieldFree; ++ ++import java.util.concurrent.locks.Lock; ++ ++/** ++ * 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(); ++ ++ /** ++ * @return Whether this lock is currently held by the {@link Thread#currentThread}. ++ */ ++ @YieldFree ++ boolean isHeldByCurrentThread(); ++ ++} diff --git a/patches/server/0143-Mutex-utility.patch b/patches/server/0144-Mutex-utility.patch similarity index 90% rename from patches/server/0143-Mutex-utility.patch rename to patches/server/0144-Mutex-utility.patch index a585b45..1c4c0d9 100644 --- a/patches/server/0143-Mutex-utility.patch +++ b/patches/server/0144-Mutex-utility.patch @@ -8,16 +8,15 @@ Gale - https://galemc.org diff --git a/src/main/java/org/galemc/gale/concurrent/Mutex.java b/src/main/java/org/galemc/gale/concurrent/Mutex.java new file mode 100644 -index 0000000000000000000000000000000000000000..f7bedd5cbe9b48ac94f8cc228a17c8a54db7d7e9 +index 0000000000000000000000000000000000000000..649d2cfc1d73699302b4e5e64e9110e7681ae09c --- /dev/null +++ b/src/main/java/org/galemc/gale/concurrent/Mutex.java -@@ -0,0 +1,123 @@ +@@ -0,0 +1,127 @@ +// Gale - mutex utility + +package org.galemc.gale.concurrent; + +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; -+import org.galemc.gale.executor.lock.CheckableLock; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.TimeUnit; @@ -34,7 +33,7 @@ index 0000000000000000000000000000000000000000..f7bedd5cbe9b48ac94f8cc228a17c8a5 + *
+ * This interface extends {@link AutoCloseable}, where {@link #close()} calls {@link #release()}. + * -+ * @author Martijn Muijsers ++ * @author Martijn Muijsers under AGPL-3.0 + */ +@AnyThreadSafe +public interface Mutex extends CheckableLock, AutoCloseable { @@ -134,10 +133,15 @@ index 0000000000000000000000000000000000000000..f7bedd5cbe9b48ac94f8cc228a17c8a5 + return create(); + } + ++ @Override ++ default boolean isHeldByCurrentThread() { ++ throw new UnsupportedOperationException("isHeldByCurrentThread() is not supported for Mutex, and is not implemented by the implementation " + this.getClass().getName() + " either"); ++ } ++ +} diff --git a/src/main/java/org/galemc/gale/concurrent/SemaphoreMutex.java b/src/main/java/org/galemc/gale/concurrent/SemaphoreMutex.java new file mode 100644 -index 0000000000000000000000000000000000000000..c310264afea6c81ec575bdf6aa5495ccb34d7ae4 +index 0000000000000000000000000000000000000000..d31293a2a2151bc9fbdc6eb2175045b429fb4461 --- /dev/null +++ b/src/main/java/org/galemc/gale/concurrent/SemaphoreMutex.java @@ -0,0 +1,39 @@ @@ -158,7 +162,7 @@ index 0000000000000000000000000000000000000000..c310264afea6c81ec575bdf6aa5495cc + * and throws {@link UnsupportedOperationException} for all {@link Lock} methods that do not have a default + * implementation in {@link Mutex}. + * -+ * @author Martijn Muijsers ++ * @author Martijn Muijsers under AGPL-3.0 + */ +@AnyThreadSafe +@YieldFree diff --git a/patches/server/0144-Thread-aware-lock-utility.patch b/patches/server/0145-Thread-aware-lock-utility.patch similarity index 95% rename from patches/server/0144-Thread-aware-lock-utility.patch rename to patches/server/0145-Thread-aware-lock-utility.patch index eab237d..a8653bf 100644 --- a/patches/server/0144-Thread-aware-lock-utility.patch +++ b/patches/server/0145-Thread-aware-lock-utility.patch @@ -8,16 +8,15 @@ Gale - https://galemc.org diff --git a/src/main/java/org/galemc/gale/concurrent/ThreadAwareNonReentrantLock.java b/src/main/java/org/galemc/gale/concurrent/ThreadAwareNonReentrantLock.java new file mode 100644 -index 0000000000000000000000000000000000000000..1dd9d78f70fe586a9868e046e01ab512af036eeb +index 0000000000000000000000000000000000000000..8c14d90d1a907adf994070cbe5d62f1fbfd8d9c8 --- /dev/null +++ b/src/main/java/org/galemc/gale/concurrent/ThreadAwareNonReentrantLock.java -@@ -0,0 +1,109 @@ +@@ -0,0 +1,113 @@ +// Gale - thread-aware lock utility + +package org.galemc.gale.concurrent; + +import net.minecraft.server.MinecraftServer; -+import org.galemc.gale.executor.lock.CheckableLock; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + @@ -120,4 +119,9 @@ index 0000000000000000000000000000000000000000..1dd9d78f70fe586a9868e046e01ab512 + return this.innerLock.isLocked(); + } + ++ @Override ++ public boolean isHeldByCurrentThread() { ++ return this.innerLock.isHeldByCurrentThread(); ++ } ++ +} diff --git a/patches/server/0145-Unterminable-executor-utility.patch b/patches/server/0146-Unterminable-executor-utility.patch similarity index 100% rename from patches/server/0145-Unterminable-executor-utility.patch rename to patches/server/0146-Unterminable-executor-utility.patch diff --git a/patches/server/0146-FIFO-concurrent-queue-utility.patch b/patches/server/0147-FIFO-concurrent-queue-utility.patch similarity index 100% rename from patches/server/0146-FIFO-concurrent-queue-utility.patch rename to patches/server/0147-FIFO-concurrent-queue-utility.patch diff --git a/patches/server/0147-Base-thread-pool.patch b/patches/server/0148-Base-thread-pool.patch similarity index 94% rename from patches/server/0147-Base-thread-pool.patch rename to patches/server/0148-Base-thread-pool.patch index bea58cb..48eef00 100644 --- a/patches/server/0147-Base-thread-pool.patch +++ b/patches/server/0148-Base-thread-pool.patch @@ -1352,7 +1352,7 @@ index f0a7a8df3caa2ea765bb0a87cfede71d0995d276..16f3475b059d2b6b85d2b342e84ab32d // CraftBukkit start TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, time); diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index eed9f125df46b616b7234a2d669971bc51bc231b..3056d66779d236071d52d731a1c4e56aad0746c5 100644 +index 5292616c846d495b1d7be040cecf47fb33412191..b49b2fe152b70c8020ba2edc48d46cc4fe8d525e 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -49,6 +49,7 @@ import net.minecraft.world.level.block.entity.SkullBlockEntity; @@ -1372,6 +1372,20 @@ index eed9f125df46b616b7234a2d669971bc51bc231b..3056d66779d236071d52d731a1c4e56a super(options, worldLoader, thread, convertable_conversionsession, resourcepackrepository, worldstem, Proxy.NO_PROXY, datafixer, services, worldloadlistenerfactory); // CraftBukkit end this.settings = dedicatedserversettings; +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index df4db98618c6c9261b4ec8e2987c4ed26af4bd4b..83a57b9bc59063ed8299f98bc33e14b57f2ea0de 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -48,6 +48,9 @@ import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelData; + import net.minecraft.world.level.storage.LevelStorageSource; + import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper ++import org.galemc.gale.executor.lock.YieldingLock; ++import org.galemc.gale.executor.queue.BaseTaskQueues; ++import org.galemc.gale.executor.thread.AbstractYieldingThread; + + public class ServerChunkCache extends ChunkSource { + diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 37e0b6212fec71ec9662e6be3b1e8bea487eb4a6..e7747b19685fd943d7fbefbfef656f8bb7c359f1 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -1850,32 +1864,6 @@ index 78f53ee557276de85f0431ebcb146445b1f4fb92..6176867eea06c53882dcaacfbde0334b }); return ret; -diff --git a/src/main/java/org/galemc/gale/concurrent/Mutex.java b/src/main/java/org/galemc/gale/concurrent/Mutex.java -index f7bedd5cbe9b48ac94f8cc228a17c8a54db7d7e9..129ac4f70f2e7f176517af1e46ea36c5144473a0 100644 ---- a/src/main/java/org/galemc/gale/concurrent/Mutex.java -+++ b/src/main/java/org/galemc/gale/concurrent/Mutex.java -@@ -20,7 +20,7 @@ import java.util.concurrent.locks.Lock; - *
- * This interface extends {@link AutoCloseable}, where {@link #close()} calls {@link #release()}. - * -- * @author Martijn Muijsers -+ * @author Martijn Muijsers under AGPL-3.0 - */ - @AnyThreadSafe - public interface Mutex extends CheckableLock, AutoCloseable { -diff --git a/src/main/java/org/galemc/gale/concurrent/SemaphoreMutex.java b/src/main/java/org/galemc/gale/concurrent/SemaphoreMutex.java -index c310264afea6c81ec575bdf6aa5495ccb34d7ae4..d31293a2a2151bc9fbdc6eb2175045b429fb4461 100644 ---- a/src/main/java/org/galemc/gale/concurrent/SemaphoreMutex.java -+++ b/src/main/java/org/galemc/gale/concurrent/SemaphoreMutex.java -@@ -15,7 +15,7 @@ import java.util.concurrent.locks.Lock; - * and throws {@link UnsupportedOperationException} for all {@link Lock} methods that do not have a default - * implementation in {@link Mutex}. - * -- * @author Martijn Muijsers -+ * @author Martijn Muijsers under AGPL-3.0 - */ - @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 9571aae593999d11b3908856b0295a7d6b588007..ed2841d3a6c6d90ad02266f38c0821bca4f549f1 100644 --- a/src/main/java/org/galemc/gale/configuration/GaleConfigurations.java @@ -2420,79 +2408,34 @@ index 0000000000000000000000000000000000000000..1d5bb1ba545200f954c886a2afb9d8ee + public static final int length = VALUES.length; + +} -diff --git a/src/main/java/org/galemc/gale/executor/annotation/Access.java b/src/main/java/org/galemc/gale/executor/annotation/Access.java -index d07f68ff73a368c8f0da56152021a95474a601ca..50541414e1d91ff06d108d9b3fe64dcb4ad09668 100644 ---- a/src/main/java/org/galemc/gale/executor/annotation/Access.java -+++ b/src/main/java/org/galemc/gale/executor/annotation/Access.java -@@ -27,13 +27,13 @@ public enum Access { - WRITE, - /** - * Both {@link #READ} and {@link #WRITE}: if the annotation is applied to a field, it holds for both access to -- * the field's value, as well as modifications made to the field. -+ * the field's value, and for modifications made to the field. - *
- * 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 +index a4dc0ebe48fdd352387f06be42ff46fc11ee5822..d324c303245bcbedaaaab573803d73caff941901 100644 --- a/src/main/java/org/galemc/gale/executor/annotation/PotentiallyBlocking.java +++ b/src/main/java/org/galemc/gale/executor/annotation/PotentiallyBlocking.java -@@ -2,7 +2,7 @@ +@@ -2,6 +2,8 @@ package org.galemc.gale.executor.annotation; --import org.galemc.gale.executor.thread.BaseThread; +import org.galemc.gale.executor.thread.AbstractYieldingThread; - ++ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; -@@ -19,7 +19,7 @@ import java.lang.annotation.Target; - * {@link PotentiallyBlocking}, {@link PotentiallyYielding} or {@link YieldFree} may all be used. + import java.lang.annotation.Target; +@@ -15,6 +17,9 @@ import java.lang.annotation.Target; *
- * Methods that are potentially blocking, including those annotated with {@link PotentiallyBlocking}, must never -- * be called on a {@link BaseThread}. + * In a method annotated with {@link PotentiallyBlocking}, fields and methods annotated with + * {@link PotentiallyBlocking}, {@link PotentiallyYielding} or {@link YieldFree} may all be used. ++ *
++ * Methods that are potentially blocking, including those annotated with {@link PotentiallyBlocking}, must never + * be called on an {@link AbstractYieldingThread}. * * @author Martijn Muijsers under AGPL-3.0 */ diff --git a/src/main/java/org/galemc/gale/executor/annotation/PotentiallyYielding.java b/src/main/java/org/galemc/gale/executor/annotation/PotentiallyYielding.java -index e87ee2612348fc559b21256cc7cadfc684f01f8e..7ff4e4ab43d316e319efb33b2dd365d679a58118 100644 +index 44b70d68ba6823ab72ea9af4b7774051785c0a2b..7ff4e4ab43d316e319efb33b2dd365d679a58118 100644 --- a/src/main/java/org/galemc/gale/executor/annotation/PotentiallyYielding.java +++ b/src/main/java/org/galemc/gale/executor/annotation/PotentiallyYielding.java -@@ -2,6 +2,9 @@ +@@ -2,13 +2,16 @@ package org.galemc.gale.executor.annotation; @@ -2502,6 +2445,14 @@ index e87ee2612348fc559b21256cc7cadfc684f01f8e..7ff4e4ab43d316e319efb33b2dd365d6 import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Target; + + /** + * An annotation primarily for methods, identifying methods that do not block, but may yield to other tasks +- * under certain circumstances. ++ * under certain circumstances, such as when attempting to acquire a {@link YieldingLock}. + *
+ * When applied to a class, this annotation indicates it holds for all methods, both instance and static, + * belonging to the class, or any superclass thereof, or any inner or statically nested class of the class or @@ -16,6 +19,9 @@ import java.lang.annotation.Target; *
* In a method annotated with {@link PotentiallyYielding}, the only methods that can be called are those @@ -2685,32 +2636,6 @@ index 0000000000000000000000000000000000000000..d27ee27a65635d0136c5c9e33925b640 + Access value() default Access.READ_WRITE; + +} -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..995746e7f89481f885ea6e3965fc9a2f3f9e9498 ---- /dev/null -+++ 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.annotation.YieldFree; -+ -+import java.util.concurrent.locks.Lock; -+ -+/** -+ * 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 @@ -2756,16 +2681,62 @@ index 0000000000000000000000000000000000000000..a248a9ea644a8bb4175da2e1903483ab + } + +} -diff --git a/src/main/java/org/galemc/gale/executor/lock/YieldingLock.java b/src/main/java/org/galemc/gale/executor/lock/YieldingLock.java +diff --git a/src/main/java/org/galemc/gale/executor/lock/SingleWaitingBaseThreadYieldingLock.java b/src/main/java/org/galemc/gale/executor/lock/SingleWaitingBaseThreadYieldingLock.java new file mode 100644 -index 0000000000000000000000000000000000000000..3ec790a7b19790731f70f59cc2bdb1919f26dd2d +index 0000000000000000000000000000000000000000..686e16da8372085196d8f92adb881f82dd5c2947 --- /dev/null -+++ b/src/main/java/org/galemc/gale/executor/lock/YieldingLock.java -@@ -0,0 +1,163 @@ ++++ b/src/main/java/org/galemc/gale/executor/lock/SingleWaitingBaseThreadYieldingLock.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 one {@link BaseThread} may be waiting at any time. ++ * ++ * @author Martijn Muijsers under AGPL-3.0 ++ */ ++@SuppressWarnings("unused") ++public class SingleWaitingBaseThreadYieldingLock extends YieldingLock { ++ ++ private volatile boolean hasWaitingThread = false; ++ ++ public SingleWaitingBaseThreadYieldingLock(Lock innerLock) { ++ super(innerLock); ++ } ++ ++ @Override ++ public void incrementWaitingThreads() { ++ hasWaitingThread = true; ++ } ++ ++ @Override ++ public void decrementWaitingThreads() { ++ hasWaitingThread = false; ++ } ++ ++ @Override ++ protected boolean hasWaitingThreads() { ++ return this.hasWaitingThread; ++ } ++ ++} +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..799608befb739d051153b5321598ea87b50fd6bd +--- /dev/null ++++ b/src/main/java/org/galemc/gale/executor/lock/YieldingLock.java +@@ -0,0 +1,202 @@ ++// Gale - base thread pool ++ ++package org.galemc.gale.executor.lock; ++ ++import org.galemc.gale.concurrent.CheckableLock; +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; +import org.galemc.gale.executor.annotation.PotentiallyYielding; +import org.galemc.gale.executor.annotation.YieldFree; @@ -2808,6 +2779,13 @@ index 0000000000000000000000000000000000000000..3ec790a7b19790731f70f59cc2bdb191 + */ + private final @Nullable ReentrantLock innerReentrantLock; + ++ /** ++ * Whether the {@link #innerLock} can be truly 'held' as lock. ++ * Can be set to false via {@link #withCanNotBeHeld()} for locks that are not real, but ++ * merely use the {@link Lock} interface allow base threads to wait for them. ++ */ ++ private boolean canBeHeld; ++ + public YieldingLock(Lock innerLock) { + this.innerLock = innerLock; + if (innerLock instanceof CheckableLock checkableLock) { @@ -2829,7 +2807,17 @@ index 0000000000000000000000000000000000000000..3ec790a7b19790731f70f59cc2bdb191 + @YieldFree + @Override + public boolean tryLock() { -+ return innerLock.tryLock(); ++ if (innerLock.tryLock()) { ++ // Increment the YieldingLock count of the current thread ++ if (this.canBeHeld) { ++ @Nullable AbstractYieldingThread yieldingThread = AbstractYieldingThread.currentYieldingThread(); ++ if (yieldingThread != null) { ++ yieldingThread.incrementHeldYieldingLockCount(); ++ } ++ } ++ return true; ++ } ++ return false; + } + + /** @@ -2855,8 +2843,10 @@ index 0000000000000000000000000000000000000000..3ec790a7b19790731f70f59cc2bdb191 + yieldingThread.yieldUntil(null, this); + } + // Increment the YieldingLock count of the current thread -+ if (yieldingThread != null) { -+ yieldingThread.incrementHeldYieldingLockCount(); ++ if (this.canBeHeld) { ++ if (yieldingThread != null) { ++ yieldingThread.incrementHeldYieldingLockCount(); ++ } + } + } + @@ -2867,9 +2857,11 @@ index 0000000000000000000000000000000000000000..3ec790a7b19790731f70f59cc2bdb191 + public void unlock() { + this.innerLock.unlock(); + // Decrement the YieldingLock count of the current thread -+ @Nullable AbstractYieldingThread yieldingThread = AbstractYieldingThread.currentYieldingThread(); -+ if (yieldingThread != null) { -+ yieldingThread.decrementHeldYieldingLockCount(); ++ if (this.canBeHeld) { ++ @Nullable AbstractYieldingThread yieldingThread = AbstractYieldingThread.currentYieldingThread(); ++ if (yieldingThread != null) { ++ yieldingThread.decrementHeldYieldingLockCount(); ++ } + } + // 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, @@ -2906,6 +2898,11 @@ index 0000000000000000000000000000000000000000..3ec790a7b19790731f70f59cc2bdb191 + return this.innerCheckableLock != null ? this.innerCheckableLock.isLocked() : this.innerReentrantLock.isLocked(); + } + ++ @Override ++ public boolean isHeldByCurrentThread() { ++ return this.innerCheckableLock != null ? this.innerCheckableLock.isHeldByCurrentThread() : this.innerReentrantLock.isHeldByCurrentThread(); ++ } ++ + /** + * Increments the number of threads waiting for this lock to be released. + */ @@ -2924,6 +2921,18 @@ index 0000000000000000000000000000000000000000..3ec790a7b19790731f70f59cc2bdb191 + @YieldFree + protected abstract boolean hasWaitingThreads(); + ++ /** ++ * Sets {@link #canBeHeld} to false. ++ * This should be called immediately after construction of this {@link YieldingLock}. ++ * ++ * @return This instance. ++ */ ++ @YieldFree ++ public @NotNull YieldingLock withCanNotBeHeld() { ++ this.canBeHeld = false; ++ return this; ++ } ++ +} 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 @@ -3026,10 +3035,10 @@ index 0000000000000000000000000000000000000000..552e82a33c59261b06911b479400a7b1 +} 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..bbe92d92f58d484084114da73003c345d00aac7e +index 0000000000000000000000000000000000000000..2eb121798b2feb2a5ce5ebb60316660dbff87de3 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java -@@ -0,0 +1,127 @@ +@@ -0,0 +1,130 @@ +// Gale - base thread pool + +package org.galemc.gale.executor.queue; @@ -3086,6 +3095,9 @@ index 0000000000000000000000000000000000000000..bbe92d92f58d484084114da73003c345 + + @Override + public boolean hasTasks() { ++ if (MinecraftServer.SERVER == null) { ++ return false; ++ } + for (ServerLevel level : MinecraftServer.SERVER.getAllLevels()) { + if (this.hasLevelTasks(level)) { + return true; @@ -3192,10 +3204,10 @@ index 0000000000000000000000000000000000000000..690979cb9b7ec3dedbd7d0c45d0c183a +} 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..cc5373e78657d04a43cb844c4fcd5f0f9cacf187 +index 0000000000000000000000000000000000000000..e4a0b3085cb22f25246010c43919129283a3b872 --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java -@@ -0,0 +1,115 @@ +@@ -0,0 +1,125 @@ +// Gale - base thread pool + +package org.galemc.gale.executor.queue; @@ -3204,6 +3216,7 @@ index 0000000000000000000000000000000000000000..cc5373e78657d04a43cb844c4fcd5f0f +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; ++import org.galemc.gale.executor.TaskSpan; +import org.galemc.gale.executor.thread.AssistThread; +import org.galemc.gale.executor.thread.ServerThread; + @@ -3243,6 +3256,15 @@ index 0000000000000000000000000000000000000000..cc5373e78657d04a43cb844c4fcd5f0f + * 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. ++ *
++ * Note that a {@link ServerThread} can only yield to {@link TaskSpan#TINY} tasks ++ * (since there are no higher tiers, and threads can only yield to lower tiers when ++ * the task yielded to is{@link TaskSpan#TINY}. ++ * Yielding to other tasks in this same tier is somewhat risky, since this means that the tasks that were ++ * yielded to must assume that although they are running on the server thread, they may be running at ++ * some unknown point in execution of the main thread. Therefore, scheduling any {@link TaskSpan#TINY} tasks to ++ * a queue in this tier must be done with the utmost care that the task cannot disrupt, or be disrupted by, ++ * the surrounding code that yields to it. + */ + SERVER(new AbstractTaskQueue[]{ + BaseTaskQueues.deferredToServerThread, @@ -4141,22 +4163,30 @@ index 0000000000000000000000000000000000000000..fb4f9c047fc71a9a01aa47871254c6a9 +} 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..d1c305735488ee02e3d86c777ada068da955495b +index 0000000000000000000000000000000000000000..24a30760982244fb0d3fa1933751a8286c27014c --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/thread/AbstractYieldingThread.java -@@ -0,0 +1,68 @@ +@@ -0,0 +1,147 @@ +// Gale - base thread pool + +package org.galemc.gale.executor.thread; + ++import net.minecraft.server.MinecraftServer; ++import org.galemc.gale.concurrent.CheckableLock; ++import org.galemc.gale.executor.annotation.PotentiallyYielding; +import org.galemc.gale.executor.annotation.thread.AnyThreadSafe; +import org.galemc.gale.executor.annotation.YieldFree; +import org.galemc.gale.executor.annotation.thread.ThisThreadOnly; ++import org.galemc.gale.executor.lock.SingleWaitingBaseThreadYieldingLock; +import org.galemc.gale.executor.lock.YieldingLock; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.locks.Condition; +import java.util.function.BooleanSupplier; ++import java.util.function.Consumer; + +/** + * An interface for threads that can yield to other tasks, for example upon encountering a {@link YieldingLock}, @@ -4191,8 +4221,79 @@ index 0000000000000000000000000000000000000000..d1c305735488ee02e3d86c777ada068d + void decrementHeldYieldingLockCount(); + + @ThisThreadOnly ++ @PotentiallyYielding("this method is meant to yield") + void yieldUntil(@Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock); + ++ /** ++ * Calls {@link #yieldUntil(BooleanSupplier, YieldingLock)}, but creates a {@link YieldingLock} ++ * based on the given {@code future}, that causes this thread to be notified when the future is completed. ++ * ++ * @param autoCompletingLockConsumer An optional consumer that will be applied to the created {@link YieldingLock}, ++ * so that {@link YieldingLock#unlock} can be called on it ++ * when the {@code stopCondition} becomes true. ++ */ ++ @ThisThreadOnly ++ @PotentiallyYielding("this method is meant to yield") ++ default void yieldUntilFuture(@Nullable BooleanSupplier stopCondition, @NotNull CompletableFuture future, @Nullable Consumer autoCompletingLockConsumer) { ++ ++ /* ++ Here, we abuse the Lock interface to create a YieldingLock ++ that can be speculatively locked only while tasks for this blockable event loop are available, ++ or when the future has completed. ++ */ ++ YieldingLock autoCompletingLock = new SingleWaitingBaseThreadYieldingLock(new CheckableLock() { ++ ++ @Override ++ public boolean isLocked() { ++ return !stopCondition.getAsBoolean() && !future.isDone(); ++ } ++ ++ @Override ++ public boolean isHeldByCurrentThread() { ++ throw new UnsupportedOperationException("isHeldByCurrentThread() is not supported for a yieldUntilFuture lock"); ++ } ++ ++ @Override ++ public void lock() { ++ throw new UnsupportedOperationException("lock() is not supported for a yieldUntilFuture lock"); ++ } ++ ++ @Override ++ public void lockInterruptibly() { ++ throw new UnsupportedOperationException("lockInterruptibly() is not supported for a yieldUntilFuture lock"); ++ } ++ ++ @Override ++ public boolean tryLock() { ++ return !this.isLocked(); ++ } ++ ++ @Override ++ public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException { ++ throw new UnsupportedOperationException("tryLock(long, TimeUnit) is not supported for a yieldUntilFuture lock"); ++ } ++ ++ @Override ++ public void unlock() {} ++ ++ @NotNull ++ @Override ++ public Condition newCondition() { ++ throw new UnsupportedOperationException("newCondition() is not supported for a yieldUntilFuture lock"); ++ } ++ ++ }).withCanNotBeHeld(); ++ if (autoCompletingLockConsumer != null) { ++ autoCompletingLockConsumer.accept(autoCompletingLock); ++ } ++ // Be properly notified when the future completes ++ future.thenRun(autoCompletingLock::unlock); ++ ++ // Yield while necessary ++ this.yieldUntil(null, autoCompletingLock); ++ ++ } ++ + @ThisThreadOnly + void runTasksUntil(@Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock); + @@ -4299,10 +4400,10 @@ index 0000000000000000000000000000000000000000..a5605765f6be0b75e5df5613e8b393b6 +} 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..633c1aeddca31f5fe95cc8c9ad54e6a2cf1b4ac1 +index 0000000000000000000000000000000000000000..1c7275dd53e29594d25c63d3df54311a300a08fb --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/thread/BaseThread.java -@@ -0,0 +1,721 @@ +@@ -0,0 +1,719 @@ +// Gale - base thread pool + +package org.galemc.gale.executor.thread; @@ -4516,8 +4617,9 @@ index 0000000000000000000000000000000000000000..633c1aeddca31f5fe95cc8c9ad54e6a2 + } + + /** -+ * This method is based on {@link #signal}, and must be so, or {@link BaseThreadActivation} will get stuck -+ * while choosing a thread to activate. ++ * This method is based on {@link #signal}. ++ * {@link #signal} must always return true if this method returns true; ++ * otherwise {@link BaseThreadActivation} will get stuck while choosing a thread to activate. + * + * @see #signal + */ @@ -4533,10 +4635,6 @@ index 0000000000000000000000000000000000000000..633c1aeddca31f5fe95cc8c9ad54e6a2 + if (!this.mayBeStillWaitingButHasBeenSignalled) { + return true; + } -+ } else if (this.isPollingTaskOrCheckingStopCondition) { -+ if (!this.skipNextWait) { -+ return true; -+ } + } + return false; + } @@ -4550,8 +4648,6 @@ index 0000000000000000000000000000000000000000..633c1aeddca31f5fe95cc8c9ad54e6a2 + *
+ * 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) { + int oldYieldDepth = this.yieldDepth; + int newYieldDepth = oldYieldDepth + 1; @@ -4574,6 +4670,8 @@ index 0000000000000000000000000000000000000000..633c1aeddca31f5fe95cc8c9ad54e6a2 + * 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. ++ *
++ * Exactly one of {@code stopCondition} or {@code yieldingLock} must be non-null. + * + * @see #yieldUntil + */ @@ -4699,37 +4797,37 @@ index 0000000000000000000000000000000000000000..633c1aeddca31f5fe95cc8c9ad54e6a2 + 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; -+ } ++ this.lastPolledTaskTier = tier; ++ return task; ++ } ++ /* ++ 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 (tierHasNoTasksForSpan) { -+ // Set thereMayBeTasks to false, but only if it did not change in the meantime -+ BaseThreadActivation.thereMayBeTasks[tier.ordinal][spanI].compareAndSet(oldTasks, 0); ++ 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; @@ -4972,8 +5070,9 @@ index 0000000000000000000000000000000000000000..633c1aeddca31f5fe95cc8c9ad54e6a2 + @AnyThreadSafe + @YieldFree + public final boolean signal(@Nullable SignalReason reason) { -+ //noinspection StatementWithEmptyBody -+ 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 ++ 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 ++ Thread.onSpinWait(); ++ } + try { + if (this.isWaiting) { + if (this.isNotActuallyWaitingYet) { @@ -5497,16 +5596,18 @@ index 0000000000000000000000000000000000000000..77fe10e51b00115da520cfc211bf84ba +} 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..ac1f646ed846ed6067f77e7526e2bd0a43bf6677 +index 0000000000000000000000000000000000000000..a22b63a15fcc737494454c0d91c35eef5bb21d9e --- /dev/null +++ b/src/main/java/org/galemc/gale/executor/thread/pool/BaseThreadActivation.java -@@ -0,0 +1,518 @@ +@@ -0,0 +1,552 @@ +// Gale - base thread pool + +package org.galemc.gale.executor.thread.pool; + +import net.minecraft.server.MinecraftServer; +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.lock.YieldingLock; +import org.galemc.gale.executor.queue.BaseTaskQueueTier; +import org.galemc.gale.executor.thread.BaseThread; @@ -5527,6 +5628,8 @@ index 0000000000000000000000000000000000000000..ac1f646ed846ed6067f77e7526e2bd0a + * + * @author Martijn Muijsers under AGPL-3.0 + */ ++@AnyThreadSafe ++@YieldFree +public final class BaseThreadActivation { + + private BaseThreadActivation() {} @@ -5563,10 +5666,10 @@ index 0000000000000000000000000000000000000000..ac1f646ed846ed6067f77e7526e2bd0a + * A re-usable array for use inside {@link #update()}. + */ + @SuppressWarnings("unchecked") -+ private static final List[] threadsWaitingForLockForTier = new List[BaseTaskQueueTier.length]; ++ private static final List[] threadsWaitingForUnlockedLockForTier = new List[BaseTaskQueueTier.length]; + static { + for (int tierI = 0; tierI < BaseTaskQueueTier.length; tierI++) { -+ threadsWaitingForLockForTier[tierI] = new ArrayList<>(); ++ threadsWaitingForUnlockedLockForTier[tierI] = new ArrayList<>(); + } + } + @@ -5705,7 +5808,7 @@ index 0000000000000000000000000000000000000000..ac1f646ed846ed6067f77e7526e2bd0a + * The following values that are never changed outside of {@link #update()}: + *
    + *
  • {@link #numberOfThreadsActiveForTier}
  • -+ *
  • {@link #threadsWaitingForLockForTier}
  • ++ *
  • {@link #threadsWaitingForUnlockedLockForTier}
  • + *
  • {@link #numberOfThreadsActiveForLowerThanTier}
  • + *
  • {@link #numberOfThreadsActiveForHigherThanTier}
  • + *
  • {@link #numberOfThreadsIntendedToBeActiveForTier}
  • @@ -5805,32 +5908,48 @@ index 0000000000000000000000000000000000000000..ac1f646ed846ed6067f77e7526e2bd0a + + /* + Compute for each tier, for how many threads -+ the highest tier of any task on their stack is that tier. ++ the highest tier of any task on their stack is that tier, ++ and that are not waiting. + 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). ++ (threads with no tasks on their stack cannot be waiting for a YieldingLock) ++ that can be unlocked. + 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(); ++ threadsWaitingForUnlockedLockForTier[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++; ++ // This value will be unused if tier is null, so we can safely use some dummy value like 1 ++ int tierOrdinal = thread.baseThreadIndex > 0 ? (tier == null ? -1 : tier.ordinal) : BaseTaskQueueTier.SERVER.ordinal; ++ if (thread.isWaitingAndNeedsSignal()) { ++ var lockWaitingFor = thread.lockWaitingFor; ++ if (lockWaitingFor != null && !lockWaitingFor.isLocked()) { ++ threadsWaitingForUnlockedLockForTier[tierOrdinal].add(thread); + } + } else { -+ numberOfThreadsActiveForTier[tier.ordinal]++; -+ if (thread.lockWaitingFor != null) { -+ threadsWaitingForLockForTier[tier.ordinal].add(thread); ++ if (tier == null && thread.baseThreadIndex > 0) { ++ /* ++ ^ Note that assist threads are never waiting for a YieldingLock while they are doing nothing, ++ so we can safely use 'else' below, but the server thread may be waiting for a YieldingLock ++ while it has no highestTierOfTaskOnStack, because it reached a YieldingLock during its ++ normal outer execution. ++ */ ++ /* ++ The thread is doing nothing: ++ if it is also not waiting, it is available to start anything. ++ */ ++ activeAssistThreadsWithoutTask++; ++ } else { ++ numberOfThreadsActiveForTier[tierOrdinal]++; + } + } ++ } else { ++ numberOfThreadsActiveForTier[BaseTaskQueueTier.SERVER.ordinal]++; + } + } + @@ -5858,11 +5977,16 @@ index 0000000000000000000000000000000000000000..ac1f646ed846ed6067f77e7526e2bd0a + 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); ++ for (int tierI = 1; tierI < BaseTaskQueueTier.length; tierI++) { ++ numberOfThreadsIntendedToBeActiveForTier[tierI] = BaseThreadPool.targetParallelism - activeAssistThreadsWithoutTask - numberOfThreadsActiveForHigherThanTier[tierI] - Math.min(numberOfThreadsActiveForLowerThanTier[tierI], BaseThreadPool.maxUndisturbedLowerTierThreadCount); + } -+ // There can only be one active server thread -+ numberOfThreadsIntendedToBeActiveForTier[0] = Math.min(numberOfThreadsIntendedToBeActiveForTier[0], 1); ++ /* ++ There must always be an attempt to have one active server thread. ++ The above computation would sometimes give 0 due to not wanting to disturb lower tiers, ++ which we don't care about for the server thread because it is always more important. ++ The above computation would also sometimes give a number higher than 1 which makes no sense. ++ */ ++ numberOfThreadsIntendedToBeActiveForTier[0] = 1; + + { + final int finalActiveAssistThreadsWithoutTask = activeAssistThreadsWithoutTask; @@ -5900,7 +6024,7 @@ index 0000000000000000000000000000000000000000..ac1f646ed846ed6067f77e7526e2bd0a + break; + } + } -+ if (thereAreTasks || !threadsWaitingForLockForTier[tierI].isEmpty()) { ++ if (thereAreTasks || !threadsWaitingForUnlockedLockForTier[tierI].isEmpty()) { + + /* + * We attempt to wake up a thread that is sleeping, @@ -5939,7 +6063,16 @@ index 0000000000000000000000000000000000000000..ac1f646ed846ed6067f77e7526e2bd0a + for (int threadI = tryThreadsStart; threadI < tryThreadsEnd; threadI++) { + BaseThread thread = threads[threadI]; + if (thread != null) { -+ if (thread.isWaitingAndNeedsSignal() && thread.canStartYieldingTasks) { ++ /* ++ Note that we only activate threads that can start yielding tasks ++ (it seems wasteful to take the effort to activate threads that can not). ++ or threads that are waiting for a lock that is not currently locked. ++ Note that for the server thread, if it cannot start yielding tasks, ++ there is never an alternative thread that can, so we also allow it, ++ as a special case. ++ */ ++ @Nullable YieldingLock lockWaitingFor = thread.lockWaitingFor; ++ if (thread.isWaitingAndNeedsSignal() && (tierI == 0 || (lockWaitingFor != null && !lockWaitingFor.isLocked()) || 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. @@ -5953,7 +6086,6 @@ index 0000000000000000000000000000000000000000..ac1f646ed846ed6067f77e7526e2bd0a + */ + 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; @@ -5989,7 +6121,8 @@ index 0000000000000000000000000000000000000000..ac1f646ed846ed6067f77e7526e2bd0a + } + // Check if the thread still seems valid and attempt to activate it + BaseThread thread = threads[threadIToUpdate]; -+ if (thread.isWaitingAndNeedsSignal() && thread.canStartYieldingTasks) { ++ @Nullable YieldingLock lockWaitingFor = thread.lockWaitingFor; ++ if (thread.isWaitingAndNeedsSignal() && (tierI == 0 || (lockWaitingFor != null && !lockWaitingFor.isLocked()) || thread.canStartYieldingTasks)) { + // Wake up the thread + if (thread.signal(thereAreTasks ? SignalReason.TASK : SignalReason.YIELDING_LOCK)) { + // Do another update diff --git a/patches/server/0148-Watch-for-blocking-base-threads.patch b/patches/server/0149-Watch-for-blocking-base-threads.patch similarity index 87% rename from patches/server/0148-Watch-for-blocking-base-threads.patch rename to patches/server/0149-Watch-for-blocking-base-threads.patch index f439f0b..18e7b86 100644 --- a/patches/server/0148-Watch-for-blocking-base-threads.patch +++ b/patches/server/0149-Watch-for-blocking-base-threads.patch @@ -32,10 +32,10 @@ index 4b279948e82a6dfd2f471ba698e361dcfe4499de..438e7d8ce4e1df13a6a5cc45075996be doFirst { diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index fab5d1c1531fa89113bef6d17df8437b0aec4582..7ec31bf30db664ade4ee4eaf22dccb4177f9262f 100644 +index fab5d1c1531fa89113bef6d17df8437b0aec4582..95d8715928a0ddccdabcb76090a3b5bdd143c5a5 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -4,18 +4,19 @@ import java.io.File; +@@ -4,18 +4,20 @@ import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -55,11 +55,12 @@ index fab5d1c1531fa89113bef6d17df8437b0aec4582..7ec31bf30db664ade4ee4eaf22dccb41 import net.minecraft.world.level.lighting.LayerLightEventListener; import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper +import org.galemc.gale.executor.thread.BaseThread; ++import org.spigotmc.WatchdogThread; +import reactor.blockhound.BlockHound; public class Main { public static boolean useJline = true; -@@ -48,6 +49,43 @@ public class Main { +@@ -48,6 +50,49 @@ public class Main { // Gale end - include time in startup logs public static void main(String[] args) { @@ -67,11 +68,15 @@ index fab5d1c1531fa89113bef6d17df8437b0aec4582..7ec31bf30db664ade4ee4eaf22dccb41 + if (Boolean.getBoolean("gale.detect.thread.blocks")) { + printlnStartupInfoToSystemOut("Initializing blocking base thread detection..."); + try { -+ var builder = BlockHound.builder() -+ // Mark base threads as intended to be non-blocking -+ builder.nonBlockingThreadPredicate(currentPredicate -> currentPredicate.or(thread -> thread instanceof BaseThread)) ++ var builder = BlockHound.builder(); ++ // Mark base threads as intended to be non-blocking (except the WatchdogThread, which obviously sleeps until needed) ++ builder.nonBlockingThreadPredicate(currentPredicate -> currentPredicate.or(thread -> thread instanceof BaseThread && !(thread instanceof WatchdogThread))); + // Set the callback when a base thread blocks + builder.blockingMethodCallback(blockingMethod -> { ++ if (MinecraftServer.SERVER == null) { ++ // Allow blocking before the server has finished initializing ++ return; ++ } + String message = "A base thread (" + Thread.currentThread() + ") started blocking:"; + if (MinecraftServer.LOGGER != null) { + MinecraftServer.LOGGER.error(message); @@ -88,7 +93,9 @@ index fab5d1c1531fa89113bef6d17df8437b0aec4582..7ec31bf30db664ade4ee4eaf22dccb41 + threadBlockingMethods.remove("onSpinWait"); + threadBlockingMethods.remove("yield"); + // Allow base threads to block in the intended way -+ builder.allowBlockingCallsInside("org.galemc.gale.executor.thread.BaseThread", "waitUntilSignalled") ++ builder.allowBlockingCallsInside("org.galemc.gale.executor.thread.BaseThread", "waitUntilSignalled"); ++ // Allow the ServerThread to block during initialization ++ builder.allowBlockingCallsInside("net.minecraft.server.dedicated.DedicatedServer", "initServer"); + // Install BlockHound + builder.install(); + printlnStartupInfoToSystemOut("Blocking base thread detection is enabled."); diff --git a/patches/server/0149-Non-blocking-PooledObjects.patch b/patches/server/0150-Non-blocking-PooledObjects.patch similarity index 100% rename from patches/server/0149-Non-blocking-PooledObjects.patch rename to patches/server/0150-Non-blocking-PooledObjects.patch diff --git a/patches/server/0150-Yielding-memoized-Supplier.patch b/patches/server/0151-Yielding-memoized-Supplier.patch similarity index 100% rename from patches/server/0150-Yielding-memoized-Supplier.patch rename to patches/server/0151-Yielding-memoized-Supplier.patch diff --git a/patches/server/0151-Run-async-executor-tasks-on-base-thread-pool.patch b/patches/server/0152-Run-async-executor-tasks-on-base-thread-pool.patch similarity index 97% rename from patches/server/0151-Run-async-executor-tasks-on-base-thread-pool.patch rename to patches/server/0152-Run-async-executor-tasks-on-base-thread-pool.patch index c4ed8a5..216b3e5 100644 --- a/patches/server/0151-Run-async-executor-tasks-on-base-thread-pool.patch +++ b/patches/server/0152-Run-async-executor-tasks-on-base-thread-pool.patch @@ -54,10 +54,10 @@ index ac12cabaf15bc3520ff74d09faa48a135c63f23c..0019e5eefc4b638526a75dd3706a5403 LOGGER.info("Closing Server"); try { diff --git a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java -index cc5373e78657d04a43cb844c4fcd5f0f9cacf187..43cb69a7fc55f6b5db05158b5dba3637a664bd19 100644 +index e4a0b3085cb22f25246010c43919129283a3b872..52c413990fd8aaca72e371c3bc8f1f145a172abc 100644 --- a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java +++ b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java -@@ -63,7 +63,9 @@ public enum BaseTaskQueueTier { +@@ -73,7 +73,9 @@ public enum BaseTaskQueueTier { * asynchronously with respect to the {@link ServerThread} and the ticking of the server. * Execution of */ diff --git a/patches/server/0152-Run-background-executor-tasks-on-base-thread-pool.patch b/patches/server/0153-Run-background-executor-tasks-on-base-thread-pool.patch similarity index 100% rename from patches/server/0152-Run-background-executor-tasks-on-base-thread-pool.patch rename to patches/server/0153-Run-background-executor-tasks-on-base-thread-pool.patch diff --git a/patches/server/0153-Run-world-upgrade-tasks-on-base-thread-pool.patch b/patches/server/0154-Run-world-upgrade-tasks-on-base-thread-pool.patch similarity index 100% rename from patches/server/0153-Run-world-upgrade-tasks-on-base-thread-pool.patch rename to patches/server/0154-Run-world-upgrade-tasks-on-base-thread-pool.patch diff --git a/patches/server/0154-Run-tab-completion-tasks-on-base-thread-pool.patch b/patches/server/0155-Run-tab-completion-tasks-on-base-thread-pool.patch similarity index 100% rename from patches/server/0154-Run-tab-completion-tasks-on-base-thread-pool.patch rename to patches/server/0155-Run-tab-completion-tasks-on-base-thread-pool.patch diff --git a/patches/server/0155-Run-text-filter-tasks-on-base-thread-pool.patch b/patches/server/0156-Run-text-filter-tasks-on-base-thread-pool.patch similarity index 100% rename from patches/server/0155-Run-text-filter-tasks-on-base-thread-pool.patch rename to patches/server/0156-Run-text-filter-tasks-on-base-thread-pool.patch diff --git a/patches/server/0156-Run-cleaner-tasks-on-base-thread-pool.patch b/patches/server/0157-Run-cleaner-tasks-on-base-thread-pool.patch similarity index 97% rename from patches/server/0156-Run-cleaner-tasks-on-base-thread-pool.patch rename to patches/server/0157-Run-cleaner-tasks-on-base-thread-pool.patch index fabeea0..9a51203 100644 --- a/patches/server/0156-Run-cleaner-tasks-on-base-thread-pool.patch +++ b/patches/server/0157-Run-cleaner-tasks-on-base-thread-pool.patch @@ -77,10 +77,10 @@ index 80f9e70d5c4330e079feccc9a4b1b5957c79ef45..e4955e8d04735b74007aae0bf3230281 public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE); diff --git a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java -index 43cb69a7fc55f6b5db05158b5dba3637a664bd19..c5e05b115eb3f8b2d55c5aa2ffce8d86822becf2 100644 +index 52c413990fd8aaca72e371c3bc8f1f145a172abc..85f467b3cedef57b6b51f04eb34316d43e192d87 100644 --- a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java +++ b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java -@@ -64,6 +64,8 @@ public enum BaseTaskQueueTier { +@@ -74,6 +74,8 @@ public enum BaseTaskQueueTier { * Execution of */ ASYNC(new AbstractTaskQueue[]{ diff --git a/patches/server/0157-Run-chunk-cache-tasks-on-base-thread-pool.patch b/patches/server/0158-Run-chunk-cache-tasks-on-base-thread-pool.patch similarity index 56% rename from patches/server/0157-Run-chunk-cache-tasks-on-base-thread-pool.patch rename to patches/server/0158-Run-chunk-cache-tasks-on-base-thread-pool.patch index e92a697..b21b25b 100644 --- a/patches/server/0157-Run-chunk-cache-tasks-on-base-thread-pool.patch +++ b/patches/server/0158-Run-chunk-cache-tasks-on-base-thread-pool.patch @@ -7,7 +7,7 @@ License: AGPL-3.0 (https://www.gnu.org/licenses/agpl-3.0.html) Gale - https://galemc.org diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index df4db98618c6c9261b4ec8e2987c4ed26af4bd4b..ba352013692b987518dd200d376fb6cf2c90da19 100644 +index 83a57b9bc59063ed8299f98bc33e14b57f2ea0de..2a0cbe5146eb444a8cb7ab4960904143af4456a7 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -11,7 +11,6 @@ import java.util.Collections; @@ -26,16 +26,34 @@ index df4db98618c6c9261b4ec8e2987c4ed26af4bd4b..ba352013692b987518dd200d376fb6cf import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.util.VisibleForDebug; import net.minecraft.util.thread.BlockableEventLoop; -@@ -48,6 +48,8 @@ import net.minecraft.world.level.storage.DimensionDataStorage; - import net.minecraft.world.level.storage.LevelData; - import net.minecraft.world.level.storage.LevelStorageSource; - import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper -+import org.galemc.gale.executor.queue.BaseTaskQueues; -+import org.galemc.gale.executor.thread.pool.BaseThreadActivation; +@@ -446,7 +446,7 @@ public class ServerChunkCache extends ChunkSource { + // Paper end + com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info + this.level.timings.syncChunkLoad.startTiming(); // Paper +- chunkproviderserver_b.managedBlock(completablefuture::isDone); ++ chunkproviderserver_b.managedYield(completablefuture); // Gale - base thread pool + io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - async chunk debug // Paper - rewrite chunk system + this.level.timings.syncChunkLoad.stopTiming(); // Paper + } // Paper +@@ -489,7 +489,7 @@ public class ServerChunkCache extends ChunkSource { + ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor; - public class ServerChunkCache extends ChunkSource { + Objects.requireNonNull(completablefuture); +- chunkproviderserver_b.managedBlock(completablefuture::isDone); ++ chunkproviderserver_b.managedYield(completablefuture); // Gale - base thread pool + } else { + completablefuture = CompletableFuture.supplyAsync(() -> { + return this.getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create); +@@ -973,6 +973,8 @@ public class ServerChunkCache extends ChunkSource { -@@ -999,6 +1001,14 @@ public class ServerChunkCache extends ChunkSource { + public final class MainThreadExecutor extends BlockableEventLoop { + ++ private @Nullable YieldingLock yieldingLockToNotifyOnNewTasks = null; // Gale - base thread pool ++ + MainThreadExecutor(Level world) { + super("Chunk source main thread executor for " + world.dimension().location()); + } +@@ -1002,6 +1004,39 @@ public class ServerChunkCache extends ChunkSource { super.doRunTask(task); } @@ -45,11 +63,62 @@ index df4db98618c6c9261b4ec8e2987c4ed26af4bd4b..ba352013692b987518dd200d376fb6cf + super.tell(runnable); + MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks = true; + BaseTaskQueues.allLevelsScheduledChunkCache.newTaskWasAdded(); ++ if (this.yieldingLockToNotifyOnNewTasks != null) { ++ this.yieldingLockToNotifyOnNewTasks.unlock(); ++ } + } ++ ++ public void managedBlock(BooleanSupplier stopCondition) { ++ throw new UnsupportedOperationException("Cannot call " + this.getClass().getName() + ".managedBlock(BooleanSupplier), call managedYield(CompletableFuture) instead"); ++ } ++ ++ public void managedYield(CompletableFuture future) { ++ if (!future.isDone()) { ++ ++this.blockingCount; ++ try { ++ var currentThread = AbstractYieldingThread.currentYieldingThread(); ++ while (!future.isDone()) { ++ if (!this.pollTask()) { ++ currentThread.yieldUntilFuture(this::hasPendingTasks, future, autoCompletingLock -> this.yieldingLockToNotifyOnNewTasks = autoCompletingLock); ++ } ++ } ++ this.yieldingLockToNotifyOnNewTasks = null; ++ } finally { ++ --this.blockingCount; ++ } ++ } ++ } ++ // Gale end - base thread pool + @Override // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task public boolean pollTask() { +diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +index 392e7b4a89669f16b32043b65b69e6593d17f10e..6328c0254b585b3bc169dd2b5d0f25bfd67566d2 100644 +--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +@@ -21,7 +21,7 @@ public abstract class BlockableEventLoop implements Profiler + private final String name; + private static final Logger LOGGER = LogUtils.getLogger(); + private final Queue pendingRunnables = Queues.newConcurrentLinkedQueue(); +- private int blockingCount; ++ protected int blockingCount; // Gale - base thread pool + + protected BlockableEventLoop(String name) { + this.name = name; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 949feba1264bcafb8dc2dcecd0a566fea80a2ba0..9eae3862abb5f1d7755a8e777fd4bf9a6f8e321d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -470,7 +470,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + list, + true + ); +- serverChunkCache.mainThreadProcessor.managedBlock(future::isDone); ++ serverChunkCache.mainThreadProcessor.managedYield(future); // Gale - base thread pool + if (chunkStatus == ChunkStatus.NOISE) { + future.join().left().ifPresent(chunk -> net.minecraft.world.level.levelgen.Heightmap.primeHeightmaps(chunk, ChunkStatus.POST_FEATURES)); + } 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..60a3b6e935fcea6cf27c31e2b967bf3758283274 @@ -109,7 +178,7 @@ index 0000000000000000000000000000000000000000..60a3b6e935fcea6cf27c31e2b967bf37 + +} diff --git a/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java -index bbe92d92f58d484084114da73003c345d00aac7e..2a42fa59fb09196959be66d58b67862902a5e17f 100644 +index 2eb121798b2feb2a5ce5ebb60316660dbff87de3..c9ded5f30e5465c8e15719ac785797cf476474f1 100644 --- a/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java +++ b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java @@ -12,7 +12,8 @@ import org.galemc.gale.executor.thread.pool.BaseThreadActivation; @@ -123,10 +192,10 @@ index bbe92d92f58d484084114da73003c345d00aac7e..2a42fa59fb09196959be66d58b678629 * All tasks provided by this queue must be yield-free. * diff --git a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java -index c5e05b115eb3f8b2d55c5aa2ffce8d86822becf2..27fee00edfa768679d7f998bc997f2d69c6d7ac5 100644 +index 85f467b3cedef57b6b51f04eb34316d43e192d87..152d3c9805365ff157e484e644b982febdcb6693 100644 --- a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java +++ b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java -@@ -49,7 +49,8 @@ public enum BaseTaskQueueTier { +@@ -59,7 +59,8 @@ public enum BaseTaskQueueTier { SERVER(new AbstractTaskQueue[]{ BaseTaskQueues.deferredToServerThread, BaseTaskQueues.serverThreadTick, diff --git a/patches/server/0158-Run-TickThread-chunk-tasks-on-base-thread-pool.patch b/patches/server/0159-Run-TickThread-chunk-tasks-on-base-thread-pool.patch similarity index 97% rename from patches/server/0158-Run-TickThread-chunk-tasks-on-base-thread-pool.patch rename to patches/server/0159-Run-TickThread-chunk-tasks-on-base-thread-pool.patch index b47938a..780f331 100644 --- a/patches/server/0158-Run-TickThread-chunk-tasks-on-base-thread-pool.patch +++ b/patches/server/0159-Run-TickThread-chunk-tasks-on-base-thread-pool.patch @@ -83,7 +83,7 @@ index 84cc9397237fa0c17aa1012dfb5683c90eb6d3b8..f5c15d40094c2ddc6220b0595597d121 final ReentrantLock schedulingLock = new ReentrantLock(); public final ChunkHolderManager chunkHolderManager; diff --git a/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java -index 2a42fa59fb09196959be66d58b67862902a5e17f..5a83bd0edb8f74b6603da9be79913f70cab5b50a 100644 +index c9ded5f30e5465c8e15719ac785797cf476474f1..2d5a6d60d87b34d03d2d8bbda6d92fddec86924a 100644 --- a/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java +++ b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledTaskQueue.java @@ -13,7 +13,7 @@ import org.jetbrains.annotations.Nullable; @@ -156,10 +156,10 @@ index 0000000000000000000000000000000000000000..117797c4ac2a81218e9f3e977467b62a + +} diff --git a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java -index 27fee00edfa768679d7f998bc997f2d69c6d7ac5..9a908566ccdda9ca77e0f9236f674f17f79e9c40 100644 +index 152d3c9805365ff157e484e644b982febdcb6693..303bdcf41c7836c35bbe0fe00e0d14b5472df072 100644 --- a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java +++ b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java -@@ -50,7 +50,8 @@ public enum BaseTaskQueueTier { +@@ -60,7 +60,8 @@ public enum BaseTaskQueueTier { BaseTaskQueues.deferredToServerThread, BaseTaskQueues.serverThreadTick, BaseTaskQueues.anyTickScheduledServerThread, diff --git a/patches/server/0159-BaseThread-PrioritisedQueueExecutorThread-agent-util.patch b/patches/server/0160-BaseThread-PrioritisedQueueExecutorThread-agent-util.patch similarity index 100% rename from patches/server/0159-BaseThread-PrioritisedQueueExecutorThread-agent-util.patch rename to patches/server/0160-BaseThread-PrioritisedQueueExecutorThread-agent-util.patch diff --git a/patches/server/0160-Run-chunk-worker-tasks-on-base-thread-pool.patch b/patches/server/0161-Run-chunk-worker-tasks-on-base-thread-pool.patch similarity index 98% rename from patches/server/0160-Run-chunk-worker-tasks-on-base-thread-pool.patch rename to patches/server/0161-Run-chunk-worker-tasks-on-base-thread-pool.patch index 1968096..ee253ee 100644 --- a/patches/server/0160-Run-chunk-worker-tasks-on-base-thread-pool.patch +++ b/patches/server/0161-Run-chunk-worker-tasks-on-base-thread-pool.patch @@ -507,10 +507,10 @@ index abdec5529763b77126494ae0c2be9b48de900bc1..35233587de14cf52da30324df89d9ec7 * This class is a copy of {@link PrioritisedQueueExecutorThread}, with the notable difference * that it does not extend {@link Thread}, but may be instantiated on its own, as an agent representing diff --git a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java -index 9a908566ccdda9ca77e0f9236f674f17f79e9c40..dc006d9940ef8114a3a3e4860fbc1da0f7c2ee60 100644 +index 303bdcf41c7836c35bbe0fe00e0d14b5472df072..e9b9d016309e8fac445b223b4412c479a5aca0e8 100644 --- a/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java +++ b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueueTier.java -@@ -63,7 +63,6 @@ public enum BaseTaskQueueTier { +@@ -73,7 +73,6 @@ public enum BaseTaskQueueTier { /** * 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. @@ -518,7 +518,7 @@ index 9a908566ccdda9ca77e0f9236f674f17f79e9c40..dc006d9940ef8114a3a3e4860fbc1da0 */ ASYNC(new AbstractTaskQueue[]{ // The cleaner queue has high priority because it releases resources back to a pool, thereby saving memory -@@ -74,7 +73,9 @@ public enum BaseTaskQueueTier { +@@ -84,7 +83,9 @@ public enum BaseTaskQueueTier { * A tier for queues that contain tasks with the same considerations as {@link #ASYNC}, * but with a low priority. */ @@ -652,7 +652,7 @@ index 0000000000000000000000000000000000000000..6f78603c87517bb681ae473d0c72b75e + +} diff --git a/src/main/java/org/galemc/gale/executor/thread/BaseThread.java b/src/main/java/org/galemc/gale/executor/thread/BaseThread.java -index 633c1aeddca31f5fe95cc8c9ad54e6a2cf1b4ac1..81e244e94c7422de589e91db44b8e493e1daf5e7 100644 +index 1c7275dd53e29594d25c63d3df54311a300a08fb..00a52c8c21ebfa95cfd3e6e9d9d76c5789ea6d83 100644 --- a/src/main/java/org/galemc/gale/executor/thread/BaseThread.java +++ b/src/main/java/org/galemc/gale/executor/thread/BaseThread.java @@ -2,6 +2,8 @@ @@ -672,7 +672,7 @@ index 633c1aeddca31f5fe95cc8c9ad54e6a2cf1b4ac1..81e244e94c7422de589e91db44b8e493 import org.jetbrains.annotations.Nullable; import java.util.concurrent.TimeUnit; -@@ -698,6 +701,20 @@ public abstract class BaseThread extends Thread implements AbstractYieldingThrea +@@ -696,6 +699,20 @@ public abstract class BaseThread extends Thread implements AbstractYieldingThrea } } diff --git a/patches/server/0161-Split-tick-steps.patch b/patches/server/0162-Split-tick-steps.patch similarity index 99% rename from patches/server/0161-Split-tick-steps.patch rename to patches/server/0162-Split-tick-steps.patch index a7f0594..9ff1cda 100644 --- a/patches/server/0161-Split-tick-steps.patch +++ b/patches/server/0162-Split-tick-steps.patch @@ -200,10 +200,10 @@ index 7ed820d2483bf6741a355b062f062a04866ba938..575f8ba79cf3547b837abb5957fed0aa for (int i = 0; i < this.tickables.size(); ++i) { ((Runnable) this.tickables.get(i)).run(); diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index ba352013692b987518dd200d376fb6cf2c90da19..99fe51c3654bf5f978b6576290504c7b7bf2e7a1 100644 +index 2a0cbe5146eb444a8cb7ab4960904143af4456a7..aa49a3d3827128c7d4c7b424211f80abd7e2ff80 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -629,23 +629,99 @@ public class ServerChunkCache extends ChunkSource { +@@ -630,23 +630,99 @@ public class ServerChunkCache extends ChunkSource { } // CraftBukkit end diff --git a/patches/server/0162-Multithreaded-ticking.patch b/patches/server/0163-Multithreaded-ticking.patch similarity index 100% rename from patches/server/0162-Multithreaded-ticking.patch rename to patches/server/0163-Multithreaded-ticking.patch diff --git a/patches/server/0164-Yielding-ChunkTaskScheduler.patch b/patches/server/0164-Yielding-ChunkTaskScheduler.patch new file mode 100644 index 0000000..efaa910 --- /dev/null +++ b/patches/server/0164-Yielding-ChunkTaskScheduler.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martijn Muijsers +Date: Sun, 5 Feb 2023 19:04:45 +0100 +Subject: [PATCH] Yielding ChunkTaskScheduler + +License: AGPL-3.0 (https://www.gnu.org/licenses/agpl-3.0.html) +Gale - https://galemc.org + +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 79ef41d2bb30beee2355d1de3dc99c9e00d929d5..0b41ffacba43ba784450ec52b0a6e96ac5bd864b 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 +@@ -22,6 +22,8 @@ import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.LevelChunk; + import org.bukkit.Bukkit; ++import org.galemc.gale.executor.lock.MultipleWaitingBaseThreadsYieldingLock; ++import org.galemc.gale.executor.lock.YieldingLock; + import org.galemc.gale.executor.thread.pool.BaseThreadPool; + import org.slf4j.Logger; + import java.io.File; +@@ -116,7 +118,7 @@ public final class ChunkTaskScheduler { + + public final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue(true); // Gale - base thread pool - private -> public, count delayed tasks + +- final ReentrantLock schedulingLock = new ReentrantLock(); ++ final YieldingLock schedulingLock = new MultipleWaitingBaseThreadsYieldingLock(new ReentrantLock()); // Gale - base thread pool - yielding ChunkTaskScheduler + public final ChunkHolderManager chunkHolderManager; + + static {