mirror of
https://github.com/Dreeam-qwq/Gale.git
synced 2025-12-21 15:59:28 +00:00
5456 lines
256 KiB
Diff
5456 lines
256 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Martijn Muijsers <martijnmuijsers@live.nl>
|
|
Date: Fri, 2 Dec 2022 11:43:51 +0100
|
|
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..22089204d13366bb265305ef14d08b0468ff5055 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 @@
|
|
package ca.spottedleaf.concurrentutil.executor.standard;
|
|
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.galemc.gale.executor.queue.AllLevelsScheduledMainThreadChunkTaskQueue;
|
|
+
|
|
import java.util.ArrayDeque;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
|
|
public class PrioritisedThreadedTaskQueue implements PrioritisedExecutor {
|
|
|
|
+ private final boolean influenceMayHaveDelayedTasks; // Gale - base thread pool
|
|
+
|
|
protected final ArrayDeque<PrioritisedTask>[] queues = new ArrayDeque[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; {
|
|
for (int i = 0; i < Priority.TOTAL_SCHEDULABLE_PRIORITIES; ++i) {
|
|
this.queues[i] = new ArrayDeque<>();
|
|
@@ -20,6 +25,16 @@ public class PrioritisedThreadedTaskQueue implements PrioritisedExecutor {
|
|
|
|
protected long taskIdGenerator = 0;
|
|
|
|
+ public PrioritisedThreadedTaskQueue() {
|
|
+ this(false);
|
|
+ }
|
|
+
|
|
+ // Gale start - base thread pool
|
|
+ public PrioritisedThreadedTaskQueue(boolean influenceMayHaveDelayedTasks) {
|
|
+ this.influenceMayHaveDelayedTasks = influenceMayHaveDelayedTasks;
|
|
+ }
|
|
+ // Gale end - base thread pool
|
|
+
|
|
@Override
|
|
public PrioritisedExecutor.PrioritisedTask queueRunnable(final Runnable task, final PrioritisedExecutor.Priority priority) throws IllegalStateException, IllegalArgumentException {
|
|
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
|
@@ -145,6 +160,12 @@ public class PrioritisedThreadedTaskQueue implements PrioritisedExecutor {
|
|
}
|
|
|
|
protected final long getAndAddTotalScheduledTasksVolatile(final long value) {
|
|
+ // Gale start - base thread pool
|
|
+ if (this.influenceMayHaveDelayedTasks) {
|
|
+ MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks = true;
|
|
+ AllLevelsScheduledMainThreadChunkTaskQueue.signalReason.signalAnother();
|
|
+ }
|
|
+ // Gale end - base thread pool
|
|
return this.totalScheduledTasks.getAndAdd(value);
|
|
}
|
|
|
|
@@ -158,6 +179,12 @@ public class PrioritisedThreadedTaskQueue implements PrioritisedExecutor {
|
|
return this.totalCompletedTasks.getAndAdd(value);
|
|
}
|
|
|
|
+ // Gale start - base thread pool
|
|
+ public final boolean hasScheduledUncompletedTasksVolatile() {
|
|
+ return this.totalScheduledTasks.get() > this.totalCompletedTasks.get();
|
|
+ }
|
|
+ // 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 dabd93c35bdbac6a8b668a82d5f3d4173a1baa4a..f22e8930e780b4bf11052504c996b8852d0c5304 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java
|
|
@@ -21,6 +21,7 @@ import net.minecraft.world.level.block.EntityBlock;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.chunk.*;
|
|
import org.bukkit.Bukkit;
|
|
+import org.galemc.gale.executor.queue.ScheduledMainThreadTaskQueues;
|
|
|
|
import java.util.*;
|
|
import java.util.concurrent.Executor;
|
|
@@ -180,7 +181,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
|
|
|
|
if (!Bukkit.isPrimaryThread()) {
|
|
// Plugins?
|
|
- MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo));
|
|
+ ScheduledMainThreadTaskQueues.add(() -> modifyBlocks(chunkPacket, chunkPacketInfo), ScheduledMainThreadTaskQueues.ANTI_XRAY_MODIFY_BLOCKS_TASK_MAX_DELAY); // Gale - base thread pool
|
|
return;
|
|
}
|
|
|
|
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..d89b8ea24844185661ac93dd4cc9f26696e967cb 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 {
|
|
public final PrioritisedThreadPool.PrioritisedPoolExecutor parallelGenExecutor;
|
|
public final PrioritisedThreadPool.PrioritisedPoolExecutor loadExecutor;
|
|
|
|
- private final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue();
|
|
+ public final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue(true); // Gale end - 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..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<G, W> {
|
|
YamlConfiguration global = YamlConfiguration.loadConfiguration(this.globalFolder.resolve(this.globalConfigFileName).toFile());
|
|
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 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 ceef4eed87363298816426dfc19f3207be1af682..ec76ed64fe3082810f4d790c5b73e731d5d524e9 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<GlobalConfiguration, Wor
|
|
try {
|
|
this.initializeGlobalConfiguration(reloader(this.globalConfigClass, GlobalConfiguration.get()));
|
|
this.initializeWorldDefaultsConfiguration();
|
|
- for (ServerLevel level : server.getAllLevels()) {
|
|
+ for (ServerLevel level : server.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
|
|
this.createWorldConfig(createWorldContextMap(level), reloader(this.worldConfigClass, level.paperConfig()));
|
|
}
|
|
} catch (Exception ex) {
|
|
diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
index 1e812c4f5a8d54293084de29e8731e0b8ddcc0ae..0f4aaacafde0fd93443943d5e712f6a22c5dce42 100644
|
|
--- a/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
+++ b/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
@@ -2,12 +2,10 @@ package io.papermc.paper.util;
|
|
|
|
import com.destroystokyo.paper.profile.CraftPlayerProfile;
|
|
import com.destroystokyo.paper.profile.PlayerProfile;
|
|
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
import com.google.gson.JsonArray;
|
|
import com.google.gson.JsonObject;
|
|
import com.google.gson.internal.Streams;
|
|
import com.google.gson.stream.JsonWriter;
|
|
-import com.mojang.datafixers.util.Either;
|
|
import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
|
|
import java.lang.ref.Cleaner;
|
|
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
|
|
@@ -17,16 +15,12 @@ import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.server.level.ChunkHolder;
|
|
-import net.minecraft.server.level.ChunkMap;
|
|
-import net.minecraft.server.level.DistanceManager;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
-import net.minecraft.server.level.Ticket;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.level.ChunkPos;
|
|
import net.minecraft.world.level.ClipContext;
|
|
import net.minecraft.world.level.Level;
|
|
-import net.minecraft.world.level.chunk.ChunkAccess;
|
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
|
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
import com.mojang.authlib.GameProfile;
|
|
@@ -34,7 +28,7 @@ import org.bukkit.Location;
|
|
import org.bukkit.block.BlockFace;
|
|
import org.bukkit.craftbukkit.CraftWorld;
|
|
import org.bukkit.craftbukkit.util.Waitable;
|
|
-import org.spigotmc.AsyncCatcher;
|
|
+import org.galemc.gale.executor.queue.BaseTaskQueues;
|
|
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
@@ -42,11 +36,9 @@ import java.io.*;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.List;
|
|
import java.util.Queue;
|
|
-import java.util.Set;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.ExecutionException;
|
|
-import java.util.concurrent.LinkedBlockingQueue;
|
|
-import java.util.concurrent.ThreadPoolExecutor;
|
|
+import java.util.concurrent.Executor;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.TimeoutException;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
@@ -55,22 +47,8 @@ import java.util.function.Consumer;
|
|
import java.util.function.Supplier;
|
|
|
|
public final class MCUtil {
|
|
- public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor(
|
|
- 0, 2, 60L, TimeUnit.SECONDS,
|
|
- new LinkedBlockingQueue<>(),
|
|
- new ThreadFactoryBuilder()
|
|
- .setNameFormat("Paper Async Task Handler Thread - %1$d")
|
|
- .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(MinecraftServer.LOGGER))
|
|
- .build()
|
|
- );
|
|
- public static final ThreadPoolExecutor cleanerExecutor = new ThreadPoolExecutor(
|
|
- 1, 1, 0L, TimeUnit.SECONDS,
|
|
- new LinkedBlockingQueue<>(),
|
|
- new ThreadFactoryBuilder()
|
|
- .setNameFormat("Paper Object Cleaner")
|
|
- .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(MinecraftServer.LOGGER))
|
|
- .build()
|
|
- );
|
|
+ public static final Executor asyncExecutor = BaseTaskQueues.scheduledAsync.yieldingExecutor; // Gale - base thread pool - remove Paper async executor
|
|
+ public static final Executor cleanerExecutor = BaseTaskQueues.scheduledAsync.yieldingExecutor; // Gale - base thread pool - remove Paper cleaner executor
|
|
|
|
public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/util/TickThread.java b/src/main/java/io/papermc/paper/util/TickThread.java
|
|
index fc57850b80303fcade89ca95794f63910404a407..8f95f98eef90d2618ffc051885bfbd3dc121f561 100644
|
|
--- a/src/main/java/io/papermc/paper/util/TickThread.java
|
|
+++ b/src/main/java/io/papermc/paper/util/TickThread.java
|
|
@@ -4,9 +4,12 @@ import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.world.entity.Entity;
|
|
import org.bukkit.Bukkit;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.MainThreadClaim;
|
|
+
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
-public class TickThread extends Thread {
|
|
+public class TickThread extends BaseThread { // Gale - base thread pool
|
|
|
|
public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("paper.strict-thread-checks");
|
|
|
|
@@ -65,23 +68,19 @@ public class TickThread extends Thread {
|
|
}
|
|
|
|
private TickThread(final Runnable run, final String name, final int id) {
|
|
- super(run, name);
|
|
+ super(run, name, 0); // Gale - base thread pool
|
|
this.id = id;
|
|
}
|
|
|
|
- public static TickThread getCurrentTickThread() {
|
|
- return (TickThread)Thread.currentThread();
|
|
- }
|
|
-
|
|
public static boolean isTickThread() {
|
|
- return Thread.currentThread() instanceof TickThread;
|
|
+ return MainThreadClaim.isCurrentThreadMainThreadAndNotClaimable(); // Gale - base thread pool
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ) {
|
|
- return Thread.currentThread() instanceof TickThread;
|
|
+ return MainThreadClaim.isCurrentThreadMainThreadAndNotClaimable(); // Gale - base thread pool
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Entity entity) {
|
|
- return Thread.currentThread() instanceof TickThread;
|
|
+ return MainThreadClaim.isCurrentThreadMainThreadAndNotClaimable(); // Gale - base thread pool
|
|
}
|
|
}
|
|
diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
|
|
index 95cac7edae8ac64811fc6a2f6b97dd4a0fceb0b0..a376259202b4a16c67db4d3ef071e0b395aca524 100644
|
|
--- a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
|
|
+++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
|
|
@@ -7,25 +7,21 @@ import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.util.worldupdate.WorldUpgrader;
|
|
import net.minecraft.world.level.ChunkPos;
|
|
-import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.chunk.ChunkGenerator;
|
|
import net.minecraft.world.level.chunk.storage.ChunkStorage;
|
|
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
|
|
-import net.minecraft.world.level.dimension.DimensionType;
|
|
import net.minecraft.world.level.dimension.LevelStem;
|
|
-import net.minecraft.world.level.levelgen.WorldGenSettings;
|
|
import net.minecraft.world.level.storage.DimensionDataStorage;
|
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
+import org.galemc.gale.executor.queue.BaseTaskQueues;
|
|
+
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.text.DecimalFormat;
|
|
import java.util.Optional;
|
|
import java.util.concurrent.ExecutorService;
|
|
-import java.util.concurrent.Executors;
|
|
-import java.util.concurrent.ThreadFactory;
|
|
-import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
import java.util.function.Supplier;
|
|
|
|
@@ -46,6 +42,10 @@ public class ThreadedWorldUpgrader {
|
|
this.dimensionType = dimensionType;
|
|
this.worldName = worldName;
|
|
this.worldDir = worldDir;
|
|
+ // Gale start - base thread pool - remove world upgrade executors
|
|
+ this.threadPool = BaseTaskQueues.scheduledAsync.yieldingExecutor;
|
|
+ /*
|
|
+ // Gale end - base thread pool - remove world upgrade executors
|
|
this.threadPool = Executors.newFixedThreadPool(Math.max(1, threads), new ThreadFactory() {
|
|
private final AtomicInteger threadCounter = new AtomicInteger();
|
|
|
|
@@ -61,6 +61,7 @@ public class ThreadedWorldUpgrader {
|
|
return ret;
|
|
}
|
|
});
|
|
+ */ // Gale - base thread pool - remove world upgrade executors
|
|
this.dataFixer = dataFixer;
|
|
this.generatorKey = generatorKey;
|
|
this.removeCaches = removeCaches;
|
|
diff --git a/src/main/java/me/titaniumtown/ArrayConstants.java b/src/main/java/me/titaniumtown/ArrayConstants.java
|
|
index ceec23a85aae625fbbe2db95c8e9c83fb9f9767c..fdb9eefdcbd4abf1936761136077c3d10ef5e594 100644
|
|
--- a/src/main/java/me/titaniumtown/ArrayConstants.java
|
|
+++ b/src/main/java/me/titaniumtown/ArrayConstants.java
|
|
@@ -2,6 +2,8 @@
|
|
|
|
package me.titaniumtown;
|
|
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+
|
|
public final class ArrayConstants {
|
|
|
|
private ArrayConstants() {}
|
|
@@ -14,5 +16,6 @@ public final class ArrayConstants {
|
|
public static final long[] emptyLongArray = new long[0];
|
|
public static final org.bukkit.entity.Entity[] emptyBukkitEntityArray = new org.bukkit.entity.Entity[0];
|
|
public static final net.minecraft.world.entity.Entity[] emptyEntityArray = new net.minecraft.world.entity.Entity[0];
|
|
+ public static final ServerLevel[] emptyServerLevelArray = new ServerLevel[0]; // Gale - base thread pool
|
|
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java
|
|
index 6b7943e8348b0a41ca69fb56ccfd5f1c1484eb07..c2cab5f6be64e7a7adf03bb004709d81cf0eee42 100644
|
|
--- a/src/main/java/net/minecraft/Util.java
|
|
+++ b/src/main/java/net/minecraft/Util.java
|
|
@@ -27,9 +27,6 @@ import java.net.URL;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.spi.FileSystemProvider;
|
|
-import java.security.AccessController;
|
|
-import java.security.PrivilegedActionException;
|
|
-import java.security.PrivilegedExceptionAction;
|
|
import java.time.Duration;
|
|
import java.time.Instant;
|
|
import java.time.ZonedDateTime;
|
|
@@ -47,8 +44,6 @@ import java.util.concurrent.CompletionException;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
-import java.util.concurrent.ForkJoinPool;
|
|
-import java.util.concurrent.ForkJoinWorkerThread;
|
|
import java.util.concurrent.LinkedBlockingQueue;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
@@ -67,11 +62,11 @@ import java.util.stream.Stream;
|
|
import javax.annotation.Nullable;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.Bootstrap;
|
|
-import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.util.TimeSource;
|
|
import net.minecraft.util.datafix.DataFixers;
|
|
import net.minecraft.world.level.block.state.properties.Property;
|
|
+import org.galemc.gale.executor.queue.BaseTaskQueues;
|
|
import org.slf4j.Logger;
|
|
|
|
public class Util {
|
|
@@ -79,8 +74,8 @@ public class Util {
|
|
private static final int DEFAULT_MAX_THREADS = 255;
|
|
private static final String MAX_THREADS_SYSTEM_PROPERTY = "max.bg.threads";
|
|
private static final AtomicInteger WORKER_COUNT = new AtomicInteger(1);
|
|
- private static final ExecutorService BOOTSTRAP_EXECUTOR = makeExecutor("Bootstrap", -2); // Paper - add -2 priority
|
|
- private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main", -1); // Paper - add -1 priority
|
|
+ private static final ExecutorService BACKGROUND_EXECUTOR = BaseTaskQueues.scheduledAsync.yieldingExecutor; // Gale - base thread pool - remove background executor
|
|
+ private static final ExecutorService BOOTSTRAP_EXECUTOR = BACKGROUND_EXECUTOR; // Gale - Patina - remove bootstrap executor
|
|
// Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread
|
|
public static final ExecutorService PROFILE_EXECUTOR = Executors.newFixedThreadPool(2, new java.util.concurrent.ThreadFactory() {
|
|
|
|
@@ -219,7 +214,6 @@ public class Util {
|
|
}
|
|
|
|
public static void shutdownExecutors() {
|
|
- shutdownExecutor(BACKGROUND_EXECUTOR);
|
|
shutdownExecutor(IO_POOL);
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java
|
|
index 40812e6518b8aacfbd2d8cd65a407d00bb19e991..eb1209b760890e6ede8ee149a6debfe83336ee10 100644
|
|
--- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java
|
|
+++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java
|
|
@@ -144,10 +144,7 @@ public class EntitySelector {
|
|
if (this.isWorldLimited()) {
|
|
this.addEntities(list, source.getLevel(), vec3d, predicate);
|
|
} else {
|
|
- Iterator iterator1 = source.getServer().getAllLevels().iterator();
|
|
-
|
|
- while (iterator1.hasNext()) {
|
|
- ServerLevel worldserver1 = (ServerLevel) iterator1.next();
|
|
+ for (ServerLevel worldserver1 : source.getServer().getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
|
|
|
|
this.addEntities(list, worldserver1, vec3d, predicate);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
|
|
index 8bc0cb9ad5bb4e76d962ff54305e2c08e279a17b..405ffeb584d60b3677ae5d8fb79e084635d2dea3 100644
|
|
--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java
|
|
+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
|
|
@@ -2,6 +2,7 @@ package net.minecraft.network.protocol;
|
|
|
|
import com.mojang.logging.LogUtils;
|
|
import net.minecraft.network.PacketListener;
|
|
+import org.galemc.gale.executor.AbstractBlockableEventLoop;
|
|
import org.slf4j.Logger;
|
|
|
|
// CraftBukkit start
|
|
@@ -9,7 +10,6 @@ import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.server.RunningOnDifferentThreadException;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
|
-import net.minecraft.util.thread.BlockableEventLoop;
|
|
|
|
public class PacketUtils {
|
|
|
|
@@ -36,10 +36,10 @@ public class PacketUtils {
|
|
public PacketUtils() {}
|
|
|
|
public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException {
|
|
- PacketUtils.ensureRunningOnSameThread(packet, listener, (BlockableEventLoop) world.getServer());
|
|
+ PacketUtils.ensureRunningOnSameThread(packet, listener, world.getServer()); // Gale - base thread pool
|
|
}
|
|
|
|
- public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T listener, BlockableEventLoop<?> engine) throws RunningOnDifferentThreadException {
|
|
+ public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T listener, AbstractBlockableEventLoop engine) throws RunningOnDifferentThreadException { // Gale - base thread pool
|
|
if (!engine.isSameThread()) {
|
|
engine.executeIfPossible(() -> {
|
|
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 d8f8d2495c1e2e3f194485d16ea587d26cc3a23d..ae423cc7ef2d19d7d5f89ac9cb2962055acda40e 100644
|
|
--- a/src/main/java/net/minecraft/server/Main.java
|
|
+++ b/src/main/java/net/minecraft/server/Main.java
|
|
@@ -55,6 +55,7 @@ import net.minecraft.world.level.levelgen.presets.WorldPresets;
|
|
import net.minecraft.world.level.storage.LevelResource;
|
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
|
import net.minecraft.world.level.storage.LevelSummary;
|
|
+import org.galemc.gale.executor.thread.SecondaryThreadPool;
|
|
import org.slf4j.Logger;
|
|
|
|
// CraftBukkit start
|
|
@@ -224,6 +225,8 @@ public class Main {
|
|
AtomicReference<DynamicOps<Tag>> ops = new AtomicReference<>();
|
|
// CraftBukkit end
|
|
|
|
+ SecondaryThreadPool.startSecondaryThreads(); // Gale - base thread pool
|
|
+
|
|
WorldStem worldstem;
|
|
|
|
try {
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index 0b3b1400e9546060b4ee35236741670aaa226820..8c9738bf077ff37aae269959d071f640e2b1deab 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -10,6 +10,7 @@ import com.mojang.authlib.GameProfileRepository;
|
|
import com.mojang.authlib.minecraft.MinecraftSessionService;
|
|
import com.mojang.datafixers.DataFixer;
|
|
import com.mojang.logging.LogUtils;
|
|
+import io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler;
|
|
import it.unimi.dsi.fastutil.longs.LongIterator;
|
|
import java.awt.image.BufferedImage;
|
|
import java.io.BufferedWriter;
|
|
@@ -40,7 +41,7 @@ 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.AtomicInteger;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import java.util.function.BooleanSupplier;
|
|
import java.util.function.Consumer;
|
|
@@ -115,7 +116,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 net.minecraft.util.thread.ReentrantBlockableEventLoop;
|
|
import net.minecraft.world.Difficulty;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.ai.village.VillageSiege;
|
|
@@ -150,8 +150,17 @@ 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;
|
|
+import org.bukkit.Bukkit;
|
|
+import org.galemc.gale.executor.MinecraftServerBlockableEventLoop;
|
|
import org.galemc.gale.configuration.GaleConfigurations;
|
|
import org.galemc.gale.configuration.GaleGlobalConfiguration;
|
|
+import org.galemc.gale.executor.queue.BaseTaskQueues;
|
|
+import org.galemc.gale.executor.queue.ScheduledMainThreadTaskQueues;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.MainThreadClaim;
|
|
+import org.galemc.gale.executor.thread.SecondaryThreadPool;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
import org.slf4j.Logger;
|
|
|
|
// CraftBukkit start
|
|
@@ -174,9 +183,12 @@ import org.bukkit.event.server.ServerLoadEvent;
|
|
|
|
import co.aikar.timings.MinecraftTimings; // Paper
|
|
|
|
-public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTask> implements CommandSource, AutoCloseable {
|
|
+public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop implements CommandSource, AutoCloseable { // Gale - base thread pool
|
|
|
|
- private static MinecraftServer SERVER; // Paper
|
|
+ // Gale start - base thread pool
|
|
+ public static MinecraftServer SERVER; // Paper // Gale - base thread pool - private -> public
|
|
+ public static boolean canPollAsyncTasksOnOriginalServerThread = true;
|
|
+ // Gale end - base thread pool
|
|
public static final Logger LOGGER = LogUtils.getLogger();
|
|
public static final String VANILLA_BRAND = "vanilla";
|
|
private static final float AVERAGE_TICK_TIME_SMOOTHING = 0.8F;
|
|
@@ -214,6 +226,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
private int port;
|
|
public final RegistryAccess.Frozen registryHolder;
|
|
private Map<ResourceKey<Level>, ServerLevel> levels;
|
|
+ // Gale start - base thread pool - optimize server levels
|
|
+ private @NotNull ServerLevel @NotNull [] levelArray = ArrayConstants.emptyServerLevelArray;
|
|
+ private @Nullable ServerLevel overworld;
|
|
+ // 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
|
|
@@ -243,10 +259,52 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
private long lastOverloadWarning;
|
|
protected final Services services;
|
|
private long lastServerStatus;
|
|
- public final Thread serverThread;
|
|
- private long nextTickTime;
|
|
- private long delayedTasksMaxNextTickTime;
|
|
- private boolean mayHaveDelayedTasks;
|
|
+ public final BaseThread originalServerThread; // Gale - base thread pool
|
|
+ // Gale start - base thread pool - make fields volatile
|
|
+ private volatile long nextTickTime;
|
|
+ private volatile long delayedTasksMaxNextTickTime;
|
|
+ // Gale end - base thread pool - make fields volatile
|
|
+
|
|
+
|
|
+ // Gale start - base thread pool
|
|
+
|
|
+ /**
|
|
+ * Whether to skip the next call to {@link #mayHaveDelayedTasks()} and simply return true.
|
|
+ * This is typically set to true when a new task is added to a queue with tasks that count as potentially
|
|
+ * delayed tasks, or when an element from such a queue is successfully polled (even though it may afterwards be
|
|
+ * empty, it seems better to simply poll again next time, rather than perform the full {@link #mayHaveDelayedTasks()}
|
|
+ * check that loops over all queues).
|
|
+ */
|
|
+ public static volatile boolean nextTimeAssumeWeMayHaveDelayedTasks;
|
|
+
|
|
+ /**
|
|
+ * Whether the value of {@link #lastComputedMayHaveDelayedTasks} should be assumed to be correct.
|
|
+ */
|
|
+ public static volatile boolean mayHaveDelayedTasksIsCurrentlyComputed;
|
|
+
|
|
+ /**
|
|
+ * The cached last computed correct (except for potential race condition mistakes in the computation)
|
|
+ * value of {@link #mayHaveDelayedTasks()}.
|
|
+ */
|
|
+ public static volatile boolean lastComputedMayHaveDelayedTasks;
|
|
+
|
|
+ /**
|
|
+ * Whether the server is currently in spare time after a tick.
|
|
+ * This is set to true by the {@link #originalServerThread} when entering the spare time phase,
|
|
+ * either at the end of a tick, or at the start of one (it it occurred too early), and set to false after
|
|
+ * the corresponding {@link #managedBlock} call.
|
|
+ */
|
|
+ public static volatile boolean isInSpareTime = false;
|
|
+
|
|
+ /**
|
|
+ * A potentially out-of-date value indicating whether {@link #isInSpareTime} is true
|
|
+ * and {@link #haveTime()} is false and {@link #blockingCount} is 0.
|
|
+ * This should be updated just in time before it is potentially needed.
|
|
+ */
|
|
+ public static volatile boolean isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking = false;
|
|
+
|
|
+ // Gale start - base thread pool
|
|
+
|
|
private final PackRepository packRepository;
|
|
private final ServerScoreboard scoreboard;
|
|
@Nullable
|
|
@@ -276,7 +334,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
|
|
public int autosavePeriod;
|
|
public Commands vanillaCommandDispatcher;
|
|
- public boolean forceTicks; // Paper
|
|
+ public volatile boolean forceTicks; // Paper // Gale - base thread pool - make fields volatile
|
|
// CraftBukkit end
|
|
// Spigot start
|
|
public static final int TPS = 20;
|
|
@@ -292,9 +350,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
public volatile boolean abnormalExit = false; // Paper
|
|
public boolean isIteratingOverLevels = false; // Paper
|
|
|
|
- public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
|
|
+ public static <S extends MinecraftServer> S spin(Function<BaseThread, S> serverFactory) { // Gale - base thread pool
|
|
AtomicReference<S> atomicreference = new AtomicReference();
|
|
- Thread thread = new io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system
|
|
+ BaseThread thread = new io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system // Gale - base thread pool
|
|
((MinecraftServer) atomicreference.get()).runServer();
|
|
}, "Server thread");
|
|
|
|
@@ -313,9 +371,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
return s0;
|
|
}
|
|
|
|
- public MinecraftServer(OptionSet options, DataPackConfig datapackconfiguration, DynamicOps<Tag> registryreadops, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, Proxy proxy, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
|
|
- super("Server");
|
|
+ public MinecraftServer(OptionSet options, DataPackConfig datapackconfiguration, DynamicOps<Tag> registryreadops, BaseThread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, Proxy proxy, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) { // Gale - base thread pool
|
|
+ super(); // Gale - base thread pool
|
|
SERVER = this; // Paper - better singleton
|
|
+ canPollAsyncTasksOnOriginalServerThread = false;
|
|
this.metricsRecorder = InactiveMetricsRecorder.INSTANCE;
|
|
this.profiler = this.metricsRecorder.getProfiler();
|
|
this.onMetricsRecordingStopped = (methodprofilerresults) -> {
|
|
@@ -353,7 +412,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
this.fixerUpper = datafixer;
|
|
this.functionManager = new ServerFunctionManager(this, this.resources.managers.getFunctionLibrary());
|
|
this.structureTemplateManager = new StructureTemplateManager(worldstem.resourceManager(), convertable_conversionsession, datafixer);
|
|
- this.serverThread = thread;
|
|
+ // Gale start - base thread pool
|
|
+ this.originalServerThread = thread;
|
|
+ SecondaryThreadPool.setOriginalServerThreadInBaseThread();
|
|
+ MainThreadClaim.initialize();
|
|
+ // Gale end - base thread pool
|
|
this.executor = Util.backgroundExecutor();
|
|
}
|
|
// CraftBukkit start
|
|
@@ -607,7 +670,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
}
|
|
}
|
|
this.forceDifficulty();
|
|
- for (ServerLevel worldserver : this.getAllLevels()) {
|
|
+ for (ServerLevel worldserver : this.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
|
|
this.prepareLevels(worldserver.getChunkSource().chunkMap.progressListener, worldserver);
|
|
//worldserver.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API // Paper - rewrite chunk system, not required to "tick" anything
|
|
this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(worldserver.getWorld()));
|
|
@@ -834,8 +897,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
// Paper end - rewrite chunk system - add close param
|
|
boolean flag3 = false;
|
|
|
|
- for (Iterator iterator = this.getAllLevels().iterator(); iterator.hasNext(); flag3 = true) {
|
|
- ServerLevel worldserver = (ServerLevel) iterator.next();
|
|
+ // Gale start - base thread pool - optimize server levels
|
|
+ ServerLevel[] worldservers = this.getAllLevelsArray();
|
|
+ for (int worldserverI = 0; worldserverI < worldservers.length; flag3 = true) {
|
|
+ ServerLevel worldserver = worldservers[worldserverI];
|
|
+ worldserverI++;
|
|
+ // Gale end - base thread pool - optimize server levels
|
|
|
|
if (!suppressLogs) {
|
|
MinecraftServer.LOGGER.info("Saving chunks for level '{}'/{}", worldserver, worldserver.dimension().location());
|
|
@@ -859,14 +926,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
*/
|
|
// CraftBukkit end
|
|
if (flush) {
|
|
- Iterator iterator1 = this.getAllLevels().iterator();
|
|
-
|
|
- while (iterator1.hasNext()) {
|
|
- ServerLevel worldserver2 = (ServerLevel) iterator1.next();
|
|
-
|
|
- //MinecraftServer.LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", worldserver2.getChunkSource().chunkMap.getStorageName()); // Paper - move up
|
|
- }
|
|
-
|
|
MinecraftServer.LOGGER.info("ThreadedAnvilChunkStorage: All dimensions are saved");
|
|
}
|
|
|
|
@@ -893,7 +952,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
}
|
|
|
|
// CraftBukkit start
|
|
- private boolean hasStopped = false;
|
|
+ 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();
|
|
@@ -922,8 +981,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
*/
|
|
MinecraftServer.LOGGER.info("Stopping main thread (Ignore any thread death message you see! - DO NOT REPORT THREAD DEATH TO PAPER - If you think this is a Gale bug, please report it at https://github.com/GaleMC/Gale/issues )");
|
|
// Gale end - branding changes
|
|
- while (this.getRunningThread().isAlive()) {
|
|
- this.getRunningThread().stop();
|
|
+ // Gale start - base thread pool
|
|
+ while (this.originalServerThread.isAlive()) {
|
|
+ this.originalServerThread.stop();
|
|
+ // Gale end - base thread pool
|
|
try {
|
|
Thread.sleep(1);
|
|
} catch (InterruptedException e) {}
|
|
@@ -957,12 +1018,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
}
|
|
|
|
MinecraftServer.LOGGER.info("Saving worlds");
|
|
- Iterator iterator = this.getAllLevels().iterator();
|
|
-
|
|
- ServerLevel worldserver;
|
|
-
|
|
- while (iterator.hasNext()) {
|
|
- worldserver = (ServerLevel) iterator.next();
|
|
+ for (ServerLevel worldserver : this.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
|
|
if (worldserver != null) {
|
|
worldserver.noSave = false;
|
|
}
|
|
@@ -979,9 +1035,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
MinecraftServer.LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), ioexception1);
|
|
}
|
|
// Spigot start
|
|
- io.papermc.paper.util.MCUtil.asyncExecutor.shutdown(); // Paper
|
|
- try { io.papermc.paper.util.MCUtil.asyncExecutor.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper
|
|
- } catch (java.lang.InterruptedException ignored) {} // Paper
|
|
if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
|
|
MinecraftServer.LOGGER.info("Saving usercache.json");
|
|
this.getProfileCache().save(false); // Paper
|
|
@@ -991,6 +1044,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
LOGGER.info("Flushing Chunk IO");
|
|
io.papermc.paper.chunk.system.io.RegionFileIOThread.close(true); // Paper // Paper - rewrite chunk system
|
|
LOGGER.info("Closing Thread Pool");
|
|
+ canPollAsyncTasksOnOriginalServerThread = true;
|
|
+ // Gale start - base thread pool - remove Paper async executor, remove background executor
|
|
+ long startTime = Util.getMillis();
|
|
+ LOGGER.info("Waiting 30 seconds for asynchronous tasks to finish...");
|
|
+ originalServerThread.runTasksUntil(() -> !BaseTaskQueues.scheduledAsync.hasTasks(), null, System.nanoTime() + 30_000_000_000L); // Paper
|
|
+ LOGGER.info("Shutting down IO 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 {
|
|
@@ -1026,7 +1087,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
this.running = false;
|
|
if (flag) {
|
|
try {
|
|
- this.serverThread.join();
|
|
+ this.originalServerThread.join(); // Gale - base thread pool
|
|
} catch (InterruptedException interruptedexception) {
|
|
MinecraftServer.LOGGER.error("Error while shutting down", interruptedexception);
|
|
}
|
|
@@ -1181,7 +1242,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
this.tickServer(this::haveTime);
|
|
lastTickProperTime = (System.nanoTime() - tickProperStart) / 1000000L; // Gale - YAPFA - last tick time
|
|
this.profiler.popPush("nextTickWait");
|
|
- this.mayHaveDelayedTasks = true;
|
|
this.delayedTasksMaxNextTickTime = Math.max(Util.getMillis() + 50L, this.nextTickTime);
|
|
this.waitUntilNextTick();
|
|
this.profiler.pop();
|
|
@@ -1265,7 +1325,47 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
return crashreport;
|
|
}
|
|
|
|
- private boolean haveTime() {
|
|
+ // Gale start - base thread pool
|
|
+ private static final SignalReason noMoreDelayedTasksSignalReason = SignalReason.createNonRetrying();
|
|
+
|
|
+ /**
|
|
+ * The return value of this method is slightly heuristic in when it is computed: it may be invalidated by
|
|
+ * other threads during its execution or immediately upon returning.
|
|
+ *
|
|
+ * @return Whether there are potentially main-thread-only tasks scheduled in some queue.
|
|
+ */
|
|
+ private static boolean mayHaveDelayedTasks() {
|
|
+ // First check the flag to skip this check
|
|
+ if (nextTimeAssumeWeMayHaveDelayedTasks) {
|
|
+ nextTimeAssumeWeMayHaveDelayedTasks = false;
|
|
+ mayHaveDelayedTasksIsCurrentlyComputed = false;
|
|
+ return true;
|
|
+ }
|
|
+ // If we still have a valid computation result, use it
|
|
+ if (mayHaveDelayedTasksIsCurrentlyComputed) {
|
|
+ return lastComputedMayHaveDelayedTasks;
|
|
+ }
|
|
+ // Compute the result and save it
|
|
+ lastComputedMayHaveDelayedTasks = false;
|
|
+ if (ScheduledMainThreadTaskQueues.hasTasks(true)) {
|
|
+ lastComputedMayHaveDelayedTasks = true;
|
|
+ } else {
|
|
+ for (ServerLevel level : SERVER.getAllLevelsArray()) {
|
|
+ if (level.chunkSource.mainThreadProcessor.hasPendingTasks() || level.chunkTaskScheduler.mainThreadExecutor.hasScheduledUncompletedTasksVolatile()) {
|
|
+ lastComputedMayHaveDelayedTasks = true;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ mayHaveDelayedTasksIsCurrentlyComputed = true;
|
|
+ if (!lastComputedMayHaveDelayedTasks) {
|
|
+ SERVER.originalServerThread.signal(noMoreDelayedTasksSignalReason);
|
|
+ }
|
|
+ return lastComputedMayHaveDelayedTasks;
|
|
+ }
|
|
+ // Gale end - base thread pool
|
|
+
|
|
+ public boolean haveTime() { // Gale - base thread pool - private -> public
|
|
// Paper start
|
|
if (this.forceTicks) {
|
|
return true;
|
|
@@ -1273,13 +1373,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
// Paper end
|
|
// CraftBukkit start
|
|
if (isOversleep) return canOversleep();// Paper - because of our changes, this logic is broken
|
|
- return this.forceTicks || this.runningTask() || Util.getMillis() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTime : this.nextTickTime);
|
|
+ return this.forceTicks || this.runningTask() || Util.getMillis() < (mayHaveDelayedTasks() ? this.delayedTasksMaxNextTickTime : this.nextTickTime); // Gale - base thread pool
|
|
}
|
|
|
|
// Paper start
|
|
- boolean isOversleep = false;
|
|
+ volatile boolean isOversleep = false; // Gale - base thread pool - make fields volatile
|
|
private boolean canOversleep() {
|
|
- return this.mayHaveDelayedTasks && Util.getMillis() < this.delayedTasksMaxNextTickTime;
|
|
+ return Util.getMillis() < this.delayedTasksMaxNextTickTime && mayHaveDelayedTasks(); // Gale - base thread pool
|
|
}
|
|
|
|
private boolean canSleepForTickNoOversleep() {
|
|
@@ -1288,7 +1388,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
// Paper end
|
|
|
|
private void executeModerately() {
|
|
- this.runAllTasks();
|
|
+ this.runAllMainThreadTasksForAllTicks(); // Gale - base thread pool
|
|
java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L);
|
|
}
|
|
// CraftBukkit end
|
|
@@ -1296,62 +1396,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
protected void waitUntilNextTick() {
|
|
//this.executeAll(); // Paper - move this into the tick method for timings
|
|
long tickOversleepStart = System.nanoTime(); // Gale - YAPFA - last tick time
|
|
+ isInSpareTime = true; // Gale - base thread pool
|
|
this.managedBlock(() -> {
|
|
return !this.canSleepForTickNoOversleep(); // Paper - move oversleep into full server tick
|
|
- });
|
|
+ // Gale start - base thread pool
|
|
+ }, 1_000_000L * this.nextTickTime - 200_000L);
|
|
+ isInSpareTime = false;
|
|
+ // Gale end - base thread pool
|
|
lastTickOversleepTime = (System.nanoTime() - tickOversleepStart) / 1000000L; // Gale - YAPFA - last tick time
|
|
}
|
|
|
|
- @Override
|
|
- public TickTask wrapRunnable(Runnable runnable) {
|
|
- // Paper start - anything that does try to post to main during watchdog crash, run on watchdog
|
|
- if (this.hasStopped && Thread.currentThread().equals(shutdownThread)) {
|
|
- runnable.run();
|
|
- runnable = () -> {};
|
|
- }
|
|
- // Paper end
|
|
- return new TickTask(this.tickCount, runnable);
|
|
- }
|
|
-
|
|
- protected boolean shouldRun(TickTask ticktask) {
|
|
- return ticktask.getTick() + 3 < this.tickCount || this.haveTime();
|
|
- }
|
|
-
|
|
- @Override
|
|
- public boolean pollTask() {
|
|
- boolean flag = this.pollTaskInternal();
|
|
-
|
|
- this.mayHaveDelayedTasks = flag;
|
|
- return flag;
|
|
- }
|
|
-
|
|
- private boolean pollTaskInternal() {
|
|
- if (super.pollTask()) {
|
|
- this.executeMidTickTasks(); // Paper - execute chunk tasks mid tick
|
|
- return true;
|
|
- } else {
|
|
- boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
|
|
- if (this.haveTime()) {
|
|
- Iterator iterator = this.getAllLevels().iterator();
|
|
-
|
|
- while (iterator.hasNext()) {
|
|
- ServerLevel worldserver = (ServerLevel) iterator.next();
|
|
-
|
|
- if (worldserver.getChunkSource().pollTask()) {
|
|
- ret = true; // Paper - force execution of all worlds, do not just bias the first
|
|
- }
|
|
- }
|
|
- }
|
|
-
|
|
- return ret; // Paper - force execution of all worlds, do not just bias the first
|
|
- }
|
|
- }
|
|
-
|
|
- public void doRunTask(TickTask ticktask) { // CraftBukkit - decompile error
|
|
- this.getProfiler().incrementCounter("runTask");
|
|
- super.doRunTask(ticktask);
|
|
- }
|
|
-
|
|
private void updateStatusIcon(ServerStatus metadata) {
|
|
Optional<File> optional = Optional.of(this.getFile("server-icon.png")).filter(File::isFile);
|
|
|
|
@@ -1399,14 +1453,19 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
|
|
// Paper start - move oversleep into full server tick
|
|
isOversleep = true;MinecraftTimings.serverOversleep.startTiming();
|
|
+ isInSpareTime = true; // Gale - base thread pool
|
|
this.managedBlock(() -> {
|
|
return !this.canOversleep();
|
|
- });
|
|
+ // Gale start - base thread pool
|
|
+ }, 1_000_000L * this.nextTickTime - 200_000L);
|
|
+ isInSpareTime = false;
|
|
+ // 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;
|
|
+ ScheduledMainThreadTaskQueues.shiftTasksForNextTick(); // Gale - base thread pool
|
|
this.tickChildren(shouldKeepTicking);
|
|
if (i - this.lastServerStatus >= 5000000000L) {
|
|
this.lastServerStatus = i;
|
|
@@ -1442,7 +1501,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
if (playerSaveInterval > 0) {
|
|
this.playerList.saveAll(playerSaveInterval);
|
|
}
|
|
- for (ServerLevel level : this.getAllLevels()) {
|
|
+ for (ServerLevel level : this.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
|
|
if (level.paperConfig().chunks.autoSaveInterval.value() > 0) {
|
|
level.saveIncrementally(fullSave);
|
|
}
|
|
@@ -1455,7 +1514,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
io.papermc.paper.util.CachedLists.reset(); // Paper
|
|
// Paper start - move executeAll() into full server tick timing
|
|
try (co.aikar.timings.Timing ignored = MinecraftTimings.processTasksTimer.startTiming()) {
|
|
- this.runAllTasks();
|
|
+ this.runAllTasksWithinTimeOrForCurrentTick();
|
|
}
|
|
// Paper end
|
|
// Paper start
|
|
@@ -1503,7 +1562,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
MinecraftTimings.timeUpdateTimer.startTiming(); // Spigot // Paper
|
|
// Send time updates to everyone, it will get the right time from the world the player is in.
|
|
// Paper start - optimize time updates
|
|
- for (final ServerLevel world : this.getAllLevels()) {
|
|
+ for (final ServerLevel world : this.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
|
|
final boolean doDaylight = world.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT);
|
|
final long dayTime = world.getDayTime();
|
|
long worldTime = world.getGameTime();
|
|
@@ -1523,9 +1582,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
MinecraftTimings.timeUpdateTimer.stopTiming(); // Spigot // Paper
|
|
|
|
this.isIteratingOverLevels = true; // Paper
|
|
- Iterator iterator = this.getAllLevels().iterator(); // Paper - move down
|
|
- while (iterator.hasNext()) {
|
|
- ServerLevel worldserver = (ServerLevel) iterator.next();
|
|
+ for (ServerLevel worldserver : this.getAllLevelsArray()) { // Paper - move down // Gale - base thread pool - optimize server levels
|
|
worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 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
|
|
@@ -1609,7 +1666,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
}
|
|
|
|
public boolean isShutdown() {
|
|
- return !this.serverThread.isAlive();
|
|
+ return !this.originalServerThread.isAlive(); // Gale - base thread pool
|
|
}
|
|
|
|
public File getFile(String path) {
|
|
@@ -1617,7 +1674,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
}
|
|
|
|
public final ServerLevel overworld() {
|
|
- return (ServerLevel) this.levels.get(Level.OVERWORLD);
|
|
+ // Gale start - base thread pool - optimize server levels
|
|
+ if (this.overworld == null) {
|
|
+ this.overworld = (ServerLevel) this.levels.get(Level.OVERWORLD);
|
|
+ }
|
|
+ return this.overworld;
|
|
+ // Gale end - base thread pool - optimize server levels
|
|
}
|
|
|
|
@Nullable
|
|
@@ -1631,6 +1693,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
|
|
newLevels.put(level.dimension(), level);
|
|
this.levels = Collections.unmodifiableMap(newLevels);
|
|
+ // 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 pool - optimize server levels
|
|
}
|
|
|
|
public void removeLevel(ServerLevel level) {
|
|
@@ -1638,6 +1707,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
|
|
newLevels.remove(level.dimension());
|
|
this.levels = Collections.unmodifiableMap(newLevels);
|
|
+ // 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 pool - optimize server levels
|
|
}
|
|
// CraftBukkit end
|
|
|
|
@@ -1645,8 +1722,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
return this.levels.keySet();
|
|
}
|
|
|
|
+ // Gale start - base thread pool - optimize server levels
|
|
+ public ServerLevel[] getAllLevelsArray() {
|
|
+ return this.levelArray;
|
|
+ }
|
|
+ // Gale end - base thread pool - optimize server levels
|
|
+
|
|
public Iterable<ServerLevel> getAllLevels() {
|
|
- return this.levels.values();
|
|
+ return this.levels == null ? Collections.emptyList() : this.levels.values(); // Gale - base thread pool
|
|
}
|
|
|
|
public String getServerVersion() {
|
|
@@ -1775,10 +1858,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
}
|
|
|
|
private void updateMobSpawningFlags() {
|
|
- Iterator iterator = this.getAllLevels().iterator();
|
|
-
|
|
- while (iterator.hasNext()) {
|
|
- ServerLevel worldserver = (ServerLevel) iterator.next();
|
|
+ for (ServerLevel worldserver : this.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
|
|
|
|
worldserver.setSpawnSettings(worldserver.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && ((DedicatedServer) this).settings.getProperties().spawnMonsters, this.isSpawningAnimals()); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean))
|
|
}
|
|
@@ -1981,25 +2061,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
return 29999984;
|
|
}
|
|
|
|
- @Override
|
|
- public boolean scheduleExecutables() {
|
|
- return super.scheduleExecutables() && !this.isStopped();
|
|
- }
|
|
-
|
|
- @Override
|
|
- public void executeIfPossible(Runnable runnable) {
|
|
- if (this.isStopped()) {
|
|
- throw new RejectedExecutionException("Server already shutting down");
|
|
- } else {
|
|
- super.executeIfPossible(runnable);
|
|
- }
|
|
- }
|
|
-
|
|
- @Override
|
|
- public Thread getRunningThread() {
|
|
- return this.serverThread;
|
|
- }
|
|
-
|
|
public int getCompressionThreshold() {
|
|
return 256;
|
|
}
|
|
@@ -2064,7 +2125,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
net.minecraft.world.item.alchemy.PotionBrewing.reload(); // Paper
|
|
new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper
|
|
// Paper start
|
|
- if (Thread.currentThread() != this.serverThread) {
|
|
+ if (Thread.currentThread() != this.originalServerThread) {
|
|
return;
|
|
}
|
|
// this.getPlayerList().saveAll(); // Paper - we don't need to save everything, just advancements
|
|
@@ -2080,7 +2141,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
|
|
if (this.isSameThread()) {
|
|
Objects.requireNonNull(completablefuture);
|
|
- this.managedBlock(completablefuture::isDone);
|
|
+ this.managedBlock(completablefuture::isDone, null);
|
|
}
|
|
|
|
return completablefuture;
|
|
@@ -2289,7 +2350,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
BufferedWriter bufferedwriter = Files.newBufferedWriter(path);
|
|
|
|
try {
|
|
- bufferedwriter.write(String.format(Locale.ROOT, "pending_tasks: %d\n", this.getPendingTasksCount()));
|
|
+ bufferedwriter.write(String.format(Locale.ROOT, "pending_tasks: %d\n", ScheduledMainThreadTaskQueues.getTaskCount())); // Gale - base thread pool
|
|
bufferedwriter.write(String.format(Locale.ROOT, "average_tick_time: %f\n", this.getAverageTickTime()));
|
|
bufferedwriter.write(String.format(Locale.ROOT, "tick_times: %s\n", Arrays.toString(this.tickTimes)));
|
|
bufferedwriter.write(String.format(Locale.ROOT, "queue: %s\n", Util.backgroundExecutor()));
|
|
@@ -2475,7 +2536,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
}
|
|
|
|
// CraftBukkit start
|
|
- @Override
|
|
public boolean isSameThread() {
|
|
return io.papermc.paper.util.TickThread.isTickThread(); // Paper - rewrite chunk system
|
|
}
|
|
@@ -2725,7 +2785,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
// give all worlds a fair chance at by targetting them all.
|
|
// if we execute too many tasks, that's fine - we have logic to correctly handle overuse of allocated time.
|
|
boolean executed = false;
|
|
- for (ServerLevel world : this.getAllLevels()) {
|
|
+ for (ServerLevel world : this.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
|
|
long currTime = System.nanoTime();
|
|
if (currTime - world.lastMidTickExecuteFailure <= TASK_EXECUTION_FAILURE_BACKOFF) {
|
|
continue;
|
|
diff --git a/src/main/java/net/minecraft/server/commands/TimeCommand.java b/src/main/java/net/minecraft/server/commands/TimeCommand.java
|
|
index f0a7a8df3caa2ea765bb0a87cfede71d0995d276..16f3475b059d2b6b85d2b342e84ab32de8e86ac0 100644
|
|
--- a/src/main/java/net/minecraft/server/commands/TimeCommand.java
|
|
+++ b/src/main/java/net/minecraft/server/commands/TimeCommand.java
|
|
@@ -51,10 +51,11 @@ public class TimeCommand {
|
|
}
|
|
|
|
public static int setTime(CommandSourceStack source, int time) {
|
|
- Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
|
|
+ // Gale start - base thread pool - optimize server levels
|
|
+ ServerLevel[] worldservers = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevelsArray() : new ServerLevel[]{source.getLevel()}; // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
|
|
|
|
- while (iterator.hasNext()) {
|
|
- ServerLevel worldserver = (ServerLevel) iterator.next();
|
|
+ for (ServerLevel worldserver : worldservers) {
|
|
+ // Gale end - base thread pool - optimize server levels
|
|
|
|
// CraftBukkit start
|
|
TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, time - worldserver.getDayTime());
|
|
@@ -70,10 +71,11 @@ public class TimeCommand {
|
|
}
|
|
|
|
public static int addTime(CommandSourceStack source, int time) {
|
|
- Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
|
|
+ // Gale start - base thread pool - optimize server levels
|
|
+ ServerLevel[] worldservers = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevelsArray() : new ServerLevel[]{source.getLevel()}; // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
|
|
|
|
- while (iterator.hasNext()) {
|
|
- ServerLevel worldserver = (ServerLevel) iterator.next();
|
|
+ for (ServerLevel worldserver : worldservers) {
|
|
+ // Gale end - base thread pool - optimize server levels
|
|
|
|
// 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 b374f41b20b3ffa2ec2874a06715661f4fec83db..56375503f0aceb1cad3d83d83f72d960dc79006d 100644
|
|
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
|
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
|
@@ -54,6 +54,7 @@ import net.minecraft.world.level.block.entity.SkullBlockEntity;
|
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
|
import org.galemc.gale.command.GaleCommands;
|
|
import org.galemc.gale.configuration.GaleGlobalConfiguration;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
import org.galemc.gale.util.CPUCoresEstimation;
|
|
import org.slf4j.Logger;
|
|
|
|
@@ -87,7 +88,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
|
|
private final TextFilterClient textFilterClient;
|
|
|
|
// CraftBukkit start - Signature changed
|
|
- public DedicatedServer(joptsimple.OptionSet options, DataPackConfig datapackconfiguration, DynamicOps<Tag> registryreadops, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, DedicatedServerSettings dedicatedserversettings, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
|
|
+ public DedicatedServer(joptsimple.OptionSet options, DataPackConfig datapackconfiguration, DynamicOps<Tag> registryreadops, BaseThread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, DedicatedServerSettings dedicatedserversettings, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) { // Gale - base thread pool
|
|
super(options, datapackconfiguration, registryreadops, 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 4654995f9982e77abe4b825b32312c2913671cd4..f833c02b92f995d3f9a09aa6a6594f8ceb800c6e 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;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
-import java.util.Optional;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.function.BooleanSupplier;
|
|
@@ -22,6 +21,7 @@ import net.minecraft.Util;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.SectionPos;
|
|
import net.minecraft.network.protocol.Packet;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.server.level.progress.ChunkProgressListener;
|
|
import net.minecraft.util.VisibleForDebug;
|
|
import net.minecraft.util.profiling.ProfilerFiller;
|
|
@@ -48,6 +48,7 @@ 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.AllLevelsScheduledChunkCacheTaskQueue;
|
|
|
|
public class ServerChunkCache extends ChunkSource {
|
|
|
|
@@ -994,6 +995,14 @@ public class ServerChunkCache extends ChunkSource {
|
|
super.doRunTask(task);
|
|
}
|
|
|
|
+ // Gale start - base thread pool
|
|
+ @Override
|
|
+ public void tell(Runnable runnable) {
|
|
+ super.tell(runnable);
|
|
+ MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks = true;
|
|
+ AllLevelsScheduledChunkCacheTaskQueue.signalReason.signalAnother();
|
|
+ }
|
|
+
|
|
@Override
|
|
// CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
|
|
public boolean pollTask() {
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index 023cd8d948ff7360ac8348b980473ef119b41225..4869e23d7070649e216dcdc64687da61b670355a 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -22,6 +22,7 @@ import java.io.Writer;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.ArrayList;
|
|
+import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.Iterator;
|
|
@@ -155,6 +156,9 @@ import net.minecraft.world.phys.shapes.BooleanOp;
|
|
import net.minecraft.world.phys.shapes.Shapes;
|
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
|
import net.minecraft.world.ticks.LevelTicks;
|
|
+import org.galemc.gale.executor.annotation.Access;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.MainThreadOnly;
|
|
import org.slf4j.Logger;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.Location;
|
|
@@ -188,6 +192,10 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
private static final int MAX_SCHEDULED_TICKS_PER_TICK = 65536;
|
|
final List<ServerPlayer> players;
|
|
public final ServerChunkCache chunkSource;
|
|
+ // Gale start - base thread pool
|
|
+ @AnyThreadSafe(Access.READ) @MainThreadOnly(Access.WRITE)
|
|
+ public volatile int serverLevelArrayIndex;
|
|
+ // Gale end - base thread pool
|
|
private final MinecraftServer server;
|
|
public final PrimaryLevelData serverLevelData; // CraftBukkit - type
|
|
final EntityTickList entityTickList;
|
|
@@ -2609,7 +2617,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
// Spigot start
|
|
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 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 c171c272d5fcf0900514e18eafaa1b5ee019c74d..b9b9b14f5235d0e07feaa1dfedf254fa43880d6e 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.Shapes;
|
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
|
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.ScheduledMainThreadTaskQueues;
|
|
import org.slf4j.Logger;
|
|
|
|
// CraftBukkit start
|
|
@@ -556,7 +558,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
|
|
|
|
Objects.requireNonNull(this.connection);
|
|
// CraftBukkit - Don't wait
|
|
- minecraftserver.scheduleOnMain(networkmanager::handleDisconnection); // Paper
|
|
+ ScheduledMainThreadTaskQueues.add(networkmanager::handleDisconnection, ScheduledMainThreadTaskQueues.HANDLE_DISCONNECT_TASK_MAX_DELAY); // Paper // Gale - base thread pool
|
|
}
|
|
|
|
private <T, R> CompletableFuture<R> filterTextPacket(T text, BiFunction<TextFilter, T, CompletableFuture<R>> filterer) {
|
|
@@ -887,21 +889,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 pool - remove tab complete executor
|
|
// Paper end
|
|
@Override
|
|
public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {
|
|
// PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); // Paper - run this async
|
|
// 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
|
|
+ ScheduledMainThreadTaskQueues.add(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), ScheduledMainThreadTaskQueues.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
|
|
+ ScheduledMainThreadTaskQueues.add(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), ScheduledMainThreadTaskQueues.KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY); // Paper - kick event cause // Gale - JettPack - reduce array allocations // Gale - base thread pool
|
|
return;
|
|
}
|
|
// Paper end
|
|
@@ -926,7 +927,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
|
|
if (!event.isHandled()) {
|
|
if (!event.isCancelled()) {
|
|
|
|
- this.server.scheduleOnMain(() -> { // This needs to be on main
|
|
+ ScheduledMainThreadTaskQueues.add(() -> { // This needs to be on main // Gale - base thread pool
|
|
ParseResults<CommandSourceStack> parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack());
|
|
|
|
this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> {
|
|
@@ -937,7 +938,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
|
|
this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestEvent.getSuggestions()));
|
|
// Paper end - Brigadier API
|
|
});
|
|
- });
|
|
+ }, ScheduledMainThreadTaskQueues.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());
|
|
@@ -1246,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
|
|
+ ScheduledMainThreadTaskQueues.add(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), ScheduledMainThreadTaskQueues.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY); // Paper - kick event cause // Gale - base thread pool
|
|
return;
|
|
}
|
|
byteTotal += byteLength;
|
|
@@ -1269,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
|
|
+ ScheduledMainThreadTaskQueues.add(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), ScheduledMainThreadTaskQueues.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY); // Paper - kick event cause // Gale - base thread pool
|
|
return;
|
|
}
|
|
}
|
|
// Paper end
|
|
// 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
|
|
+ ScheduledMainThreadTaskQueues.add(() -> this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), ScheduledMainThreadTaskQueues.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;
|
|
@@ -2077,10 +2078,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
|
|
public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket packet) {
|
|
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel());
|
|
if (this.player.isSpectator()) {
|
|
- Iterator iterator = this.server.getAllLevels().iterator();
|
|
-
|
|
- while (iterator.hasNext()) {
|
|
- ServerLevel worldserver = (ServerLevel) iterator.next();
|
|
+ for (ServerLevel worldserver : this.server.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
|
|
Entity entity = packet.getEntity(worldserver);
|
|
|
|
if (entity != null) {
|
|
@@ -2228,9 +2226,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
|
|
}
|
|
// CraftBukkit end
|
|
if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.message())) {
|
|
- this.server.scheduleOnMain(() -> { // Paper - push to main for event firing
|
|
+ ScheduledMainThreadTaskQueues.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
|
|
+ }, ScheduledMainThreadTaskQueues.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main for event firing // Gale - base thread pool
|
|
} else {
|
|
if (this.tryHandleChat(packet.message(), packet.timeStamp(), packet.lastSeenMessages())) {
|
|
// this.server.submit(() -> { // CraftBukkit - async chat
|
|
@@ -2258,9 +2256,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
|
|
+ ScheduledMainThreadTaskQueues.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
|
|
+ }, ScheduledMainThreadTaskQueues.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main for event firing // Gale - base thread pool
|
|
} else {
|
|
if (this.tryHandleChat(packet.command(), packet.timeStamp(), packet.lastSeenMessages())) {
|
|
this.server.submit(() -> {
|
|
@@ -2357,9 +2355,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
|
|
private boolean tryHandleChat(String message, Instant timestamp, LastSeenMessages.Update acknowledgment) {
|
|
if (!this.updateChatOrder(timestamp)) {
|
|
ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}': {} > {}", this.player.getName().getString(), message, this.lastChatTimeStamp.get().getEpochSecond(), timestamp.getEpochSecond()); // Paper
|
|
- this.server.scheduleOnMain(() -> { // Paper - push to main
|
|
+ ScheduledMainThreadTaskQueues.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
|
|
- }); // Paper - push to main
|
|
+ }, ScheduledMainThreadTaskQueues.KICK_FOR_OUT_OF_ORDER_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main // Gale - base thread pool
|
|
return false;
|
|
} 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));
|
|
@@ -3420,7 +3418,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
|
|
+ ScheduledMainThreadTaskQueues.add(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), ScheduledMainThreadTaskQueues.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 2393b6a5f3f12c2b17b172ee8ca42ead218e2a10..ff2d2366966b00436a350ec73819930248a0fc4f 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;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.ExecutorService;
|
|
-import java.util.concurrent.Executors;
|
|
import java.util.concurrent.ThreadFactory;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import javax.annotation.Nullable;
|
|
@@ -32,6 +31,7 @@ import net.minecraft.Util;
|
|
import net.minecraft.network.chat.FilterMask;
|
|
import net.minecraft.util.GsonHelper;
|
|
import net.minecraft.util.thread.ProcessorMailbox;
|
|
+import org.galemc.gale.executor.queue.BaseTaskQueues;
|
|
import org.slf4j.Logger;
|
|
|
|
public class TextFilterClient implements AutoCloseable {
|
|
@@ -62,7 +62,7 @@ public class TextFilterClient implements AutoCloseable {
|
|
this.joinEncoder = joinEncoder;
|
|
this.leaveEndpoint = leaveEndpoint;
|
|
this.leaveEncoder = leaveEncoder;
|
|
- this.workerPool = Executors.newFixedThreadPool(parallelism, THREAD_FACTORY);
|
|
+ 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 a60db92d8c6afab40e12b3ac28241beac06bcf63..e28f69ca50b513faa7eb656992be14ab8ee50291 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;
|
|
import java.nio.file.Path;
|
|
import java.text.SimpleDateFormat;
|
|
import java.time.Instant;
|
|
-import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Iterator;
|
|
@@ -101,10 +100,10 @@ import net.minecraft.world.scores.PlayerTeam;
|
|
import net.minecraft.world.scores.Scoreboard; // Paper
|
|
import net.minecraft.world.scores.Team;
|
|
import org.galemc.gale.configuration.GaleGlobalConfiguration;
|
|
+import org.galemc.gale.executor.queue.ScheduledMainThreadTaskQueues;
|
|
import org.slf4j.Logger;
|
|
|
|
// CraftBukkit start
|
|
-import java.util.stream.Collectors;
|
|
import net.minecraft.server.dedicated.DedicatedServer;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
@@ -306,7 +305,7 @@ public abstract class PlayerList {
|
|
worldserver1, chunkX, chunkZ, net.minecraft.server.level.ChunkHolder.FullChunkStatus.ENTITY_TICKING, true,
|
|
ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHEST,
|
|
(chunk) -> {
|
|
- MinecraftServer.getServer().scheduleOnMain(() -> {
|
|
+ ScheduledMainThreadTaskQueues.add(() -> { // Gale - base thread pool
|
|
try {
|
|
if (!playerconnection.connection.isConnected()) {
|
|
return;
|
|
@@ -319,7 +318,7 @@ public abstract class PlayerList {
|
|
} finally {
|
|
finalWorldserver.pendingLogin.remove(player);
|
|
}
|
|
- });
|
|
+ }, ScheduledMainThreadTaskQueues.POST_CHUNK_LOAD_JOIN_TASK_MAX_DELAY); // Gale - base thread pool
|
|
}
|
|
);
|
|
}
|
|
@@ -1578,10 +1577,8 @@ public abstract class PlayerList {
|
|
public void setViewDistance(int viewDistance) {
|
|
this.viewDistance = viewDistance;
|
|
//this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - move into setViewDistance
|
|
- Iterator iterator = this.server.getAllLevels().iterator();
|
|
|
|
- while (iterator.hasNext()) {
|
|
- ServerLevel worldserver = (ServerLevel) iterator.next();
|
|
+ for (ServerLevel worldserver : this.server.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
|
|
|
|
if (worldserver != null) {
|
|
worldserver.getChunkSource().setViewDistance(viewDistance);
|
|
@@ -1593,10 +1590,8 @@ public abstract class PlayerList {
|
|
public void setSimulationDistance(int simulationDistance) {
|
|
this.simulationDistance = simulationDistance;
|
|
//this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); // Paper - handled by playerchunkloader
|
|
- Iterator iterator = this.server.getAllLevels().iterator();
|
|
|
|
- while (iterator.hasNext()) {
|
|
- ServerLevel worldserver = (ServerLevel) iterator.next();
|
|
+ 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..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;
|
|
import java.util.List;
|
|
import java.util.Queue;
|
|
import java.util.concurrent.CompletableFuture;
|
|
-import java.util.concurrent.Executor;
|
|
import java.util.concurrent.locks.LockSupport;
|
|
import java.util.function.BooleanSupplier;
|
|
import java.util.function.Supplier;
|
|
+
|
|
import net.minecraft.util.profiling.metrics.MetricCategory;
|
|
import net.minecraft.util.profiling.metrics.MetricSampler;
|
|
import net.minecraft.util.profiling.metrics.MetricsRegistry;
|
|
import net.minecraft.util.profiling.metrics.ProfilerMeasured;
|
|
+import org.galemc.gale.executor.AbstractBlockableEventLoop;
|
|
import org.slf4j.Logger;
|
|
|
|
-public abstract class BlockableEventLoop<R extends Runnable> implements ProfilerMeasured, ProcessorHandle<R>, Executor {
|
|
+public abstract class BlockableEventLoop<R extends Runnable> implements ProfilerMeasured, ProcessorHandle<R>, AbstractBlockableEventLoop { // Gale - base thread pool
|
|
private final String name;
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private final Queue<R> pendingRunnables = Queues.newConcurrentLinkedQueue();
|
|
@@ -31,6 +32,7 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
|
|
|
|
protected abstract boolean shouldRun(R task);
|
|
|
|
+ @Override // Gale - base thread pool
|
|
public boolean isSameThread() {
|
|
return Thread.currentThread() == this.getRunningThread();
|
|
}
|
|
@@ -45,6 +47,12 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
|
|
return this.pendingRunnables.size();
|
|
}
|
|
|
|
+ // Gale start - base thread pool
|
|
+ public boolean hasPendingTasks() {
|
|
+ return !this.pendingRunnables.isEmpty();
|
|
+ }
|
|
+ // Gale end - base thread pool
|
|
+
|
|
@Override
|
|
public String name() {
|
|
return this.name;
|
|
@@ -102,6 +110,7 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
|
|
|
|
}
|
|
|
|
+ @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 ea23e771ab2b77e8001d0eaaf834423353ef70c2..4fc0deca1ba4da302f40cbf8847420d0042b44da 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
|
|
@@ -97,7 +97,7 @@ public abstract class Projectile extends Entity {
|
|
this.cachedOwner = ((ServerLevel) this.level).getEntity(this.ownerUUID);
|
|
// 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 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 69bde99acff7bdae9af7cfe60e2221675a68b858..eb61050067c0f182ef50f4c382add3ea03019701 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
@@ -969,7 +969,7 @@ public final class CraftServer implements Server {
|
|
org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot
|
|
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 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))
|
|
|
|
@@ -1153,7 +1153,7 @@ public final class CraftServer implements Server {
|
|
|
|
@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 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");
|
|
|
|
@@ -2498,7 +2498,7 @@ public final class CraftServer implements Server {
|
|
public Entity getEntity(UUID uuid) {
|
|
Validate.notNull(uuid, "UUID cannot be null");
|
|
|
|
- for (ServerLevel world : this.getServer().getAllLevels()) {
|
|
+ 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 55d83a9a691d11c9408d2c3260c3e77dfb51b97e..89edfcf25e0359905e26181262b9ea5d7b99c56c 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;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.mojang.datafixers.util.Pair;
|
|
-import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
|
import java.io.File;
|
|
@@ -21,7 +20,6 @@ import java.util.Objects;
|
|
import java.util.Random;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
-import java.util.concurrent.ExecutionException;
|
|
import java.util.function.Predicate;
|
|
import java.util.stream.Collectors;
|
|
import net.minecraft.core.BlockPos;
|
|
@@ -43,7 +41,6 @@ import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.server.level.Ticket;
|
|
import net.minecraft.server.level.TicketType;
|
|
import net.minecraft.sounds.SoundSource;
|
|
-import net.minecraft.tags.TagKey;
|
|
import net.minecraft.util.SortedArraySet;
|
|
import net.minecraft.util.Unit;
|
|
import net.minecraft.world.entity.EntityType;
|
|
@@ -115,7 +112,6 @@ import org.bukkit.entity.TippedArrow;
|
|
import org.bukkit.entity.Trident;
|
|
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
|
|
import org.bukkit.event.weather.LightningStrikeEvent;
|
|
-import org.bukkit.event.world.SpawnChangeEvent;
|
|
import org.bukkit.event.world.TimeSkipEvent;
|
|
import org.bukkit.generator.BiomeProvider;
|
|
import org.bukkit.generator.BlockPopulator;
|
|
@@ -135,6 +131,7 @@ import org.bukkit.util.Consumer;
|
|
import org.bukkit.util.RayTraceResult;
|
|
import org.bukkit.util.StructureSearchResult;
|
|
import org.bukkit.util.Vector;
|
|
+import org.galemc.gale.executor.queue.ScheduledMainThreadTaskQueues;
|
|
|
|
public class CraftWorld extends CraftRegionAccessor implements World {
|
|
public static final int CUSTOM_DIMENSION_OFFSET = 10;
|
|
@@ -2353,11 +2350,11 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
java.util.concurrent.CompletableFuture<Chunk> ret = new java.util.concurrent.CompletableFuture<>();
|
|
|
|
io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> {
|
|
- net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
|
|
+ ScheduledMainThreadTaskQueues.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());
|
|
- });
|
|
+ }, ScheduledMainThreadTaskQueues.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 9368ec01e498f913bc5b7b3e77fe87659090d9b5..54b2a93a2b4708a2e06fa399064105f8f95402ca 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
|
|
@@ -189,6 +189,7 @@ import org.bukkit.plugin.Plugin;
|
|
import org.bukkit.util.BoundingBox;
|
|
import org.bukkit.util.NumberConversions;
|
|
import org.bukkit.util.Vector;
|
|
+import org.galemc.gale.executor.queue.ScheduledMainThreadTaskQueues;
|
|
|
|
public abstract class CraftEntity implements org.bukkit.entity.Entity {
|
|
private static PermissibleBase perm;
|
|
@@ -1260,7 +1261,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
|
|
for (net.minecraft.world.level.chunk.ChunkAccess chunk : list) {
|
|
chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId());
|
|
}
|
|
- net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
|
|
+ ScheduledMainThreadTaskQueues.add(() -> { // Gale - base thread pool
|
|
try {
|
|
ret.complete(CraftEntity.this.teleport(locationClone, cause) ? Boolean.TRUE : Boolean.FALSE);
|
|
} catch (Throwable throwable) {
|
|
@@ -1270,7 +1271,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
|
|
net.minecraft.server.MinecraftServer.LOGGER.error("Failed to teleport entity " + CraftEntity.this, throwable);
|
|
ret.completeExceptionally(throwable);
|
|
}
|
|
- });
|
|
+ }, ScheduledMainThreadTaskQueues.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 76e4d7b2f242218df7e853b416a69d62707357e8..beb233dee5b00eee1ff3b3bc9818a2a70a4421d3 100644
|
|
--- a/src/main/java/org/galemc/gale/concurrent/Mutex.java
|
|
+++ b/src/main/java/org/galemc/gale/concurrent/Mutex.java
|
|
@@ -17,7 +17,7 @@ import java.util.concurrent.locks.Lock;
|
|
* respectively {@link #acquireUninterruptibly}, {@link #acquire}, {@link #tryAcquire} and
|
|
* {@link #release}. The {@link Lock#newCondition} method does not have a default implementation.
|
|
*
|
|
- * @author Martijn Muijsers
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
*/
|
|
@AnyThreadSafe
|
|
public interface Mutex extends Lock {
|
|
diff --git a/src/main/java/org/galemc/gale/concurrent/SemaphoreMutex.java b/src/main/java/org/galemc/gale/concurrent/SemaphoreMutex.java
|
|
index f8e3151e6ba1ef0850f50c836962b33f088de375..03bf3a93f683010ea76bbb26ae03c18e4e98d889 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
|
|
public class SemaphoreMutex extends Semaphore implements Mutex {
|
|
diff --git a/src/main/java/org/galemc/gale/configuration/GaleConfigurations.java b/src/main/java/org/galemc/gale/configuration/GaleConfigurations.java
|
|
index c7a4186bb93992733f393ac58fb4d65bbc8db861..c00186a73dc5c705a8a76e6ee532cd63cdf11b4d 100644
|
|
--- a/src/main/java/org/galemc/gale/configuration/GaleConfigurations.java
|
|
+++ b/src/main/java/org/galemc/gale/configuration/GaleConfigurations.java
|
|
@@ -266,7 +266,7 @@ public class GaleConfigurations extends Configurations<GaleGlobalConfiguration,
|
|
try {
|
|
this.initializeGlobalConfiguration(reloader(this.globalConfigClass, GaleGlobalConfiguration.get()));
|
|
this.initializeWorldDefaultsConfiguration();
|
|
- for (ServerLevel level : server.getAllLevels()) {
|
|
+ for (ServerLevel level : server.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
|
|
this.createWorldConfig(PaperConfigurations.createWorldContextMap(level), reloader(this.worldConfigClass, level.galeConfig()));
|
|
}
|
|
} catch (Exception ex) {
|
|
diff --git a/src/main/java/org/galemc/gale/configuration/GaleGlobalConfiguration.java b/src/main/java/org/galemc/gale/configuration/GaleGlobalConfiguration.java
|
|
index 6f6b96fe499f1b1888a141b60bd3773ba45e75f6..03dbc0a0d5b65c0f484e11b5bc321b75a5ce3e19 100644
|
|
--- a/src/main/java/org/galemc/gale/configuration/GaleGlobalConfiguration.java
|
|
+++ b/src/main/java/org/galemc/gale/configuration/GaleGlobalConfiguration.java
|
|
@@ -2,11 +2,14 @@
|
|
|
|
package org.galemc.gale.configuration;
|
|
|
|
+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
|
|
import io.papermc.paper.configuration.Configuration;
|
|
import io.papermc.paper.configuration.ConfigurationPart;
|
|
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
|
|
+import org.galemc.gale.executor.queue.ScheduledMainThreadTaskQueues;
|
|
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
|
|
|
+import java.util.Arrays;
|
|
import java.util.Locale;
|
|
import java.util.function.Consumer;
|
|
|
|
@@ -74,6 +77,223 @@ public class GaleGlobalConfiguration extends ConfigurationPart {
|
|
}
|
|
// Gale end - Pufferfish - SIMD support
|
|
|
|
+ // Gale start - base thread pool
|
|
+ public MainThreadTaskMaxDelay mainThreadTaskMaxDelay;
|
|
+ public class MainThreadTaskMaxDelay extends ConfigurationPart.Post {
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for tasks.
|
|
+ * Given in ticks.
|
|
+ * Any value < 0 uses the vanilla maximum delay for tasks, which is currently 2.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: -1</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ @Setting("default")
|
|
+ public int defaultValue = -1;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for completing a {@link java.util.concurrent.CompletableFuture}
|
|
+ * for a chunk load, after the chunk has already finished loading.
|
|
+ * Given in ticks.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: 0</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int completeChunkFuture = 0;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for completing the steps needed to take when a player is joining and the
|
|
+ * necessary chunk has been loaded.
|
|
+ * Given in ticks.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: 19</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int postChunkLoadJoin = 19;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for chunk packets to be modified for anti-xray.
|
|
+ * Given in ticks.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: 19</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int antiXrayModifyBlocks = 19;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for entities to be teleported when a teleport is started asynchronously.
|
|
+ * Given in ticks.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: -1</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int teleportAsync = -1;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for command completion suggestions to be sent to the player.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: 9</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int sendCommandCompletionSuggestions = 9;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for players to get kicked for command packet spam.
|
|
+ * Given in ticks.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: 0</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int kickForCommandPacketSpam = 0;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for players to get kicked for place-recipe packet spam.
|
|
+ * Given in ticks.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: 0</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int kickForRecipePacketSpam = 0;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for players to get kicked for sending invalid packets trying to
|
|
+ * send book content that is too large, which usually indicates they are attempting to abuse an exploit.
|
|
+ * Given in ticks.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: -1</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int kickForBookTooLargePacket = -1;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for players to get kicked for editing a book too quickly.
|
|
+ * Given in ticks.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: -1</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int kickForEditingBookTooQuickly = -1;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for players to get kicked for sending a chat packet with illegal characters.
|
|
+ * Given in ticks.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: -1</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int kickForIllegalCharactersInChatPacket = -1;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for players to get kicked for sending an out-of-order chat packet.
|
|
+ * Given in ticks.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: -1</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int kickForOutOfOrderChatPacket = -1;
|
|
+
|
|
+ /**
|
|
+ * The default maximum delay for handling player disconnects.
|
|
+ * Any value < 0 uses {@link #defaultValue}.
|
|
+ * <ul>
|
|
+ * <li><i>Default</i>: -1</li>
|
|
+ * <li><i>Vanilla</i>: -1</li>
|
|
+ * </ul>
|
|
+ */
|
|
+ public int handleDisconnect = -1;
|
|
+
|
|
+ @Override
|
|
+ public void postProcess() {
|
|
+ while (!ScheduledMainThreadTaskQueues.writeLock.tryLock());
|
|
+ try {
|
|
+ // Update the values in MinecraftServerBlockableEventLoop for quick access
|
|
+ ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY = this.defaultValue >= 0 ? this.defaultValue : 2;
|
|
+ ScheduledMainThreadTaskQueues.COMPLETE_CHUNK_FUTURE_TASK_MAX_DELAY = this.completeChunkFuture >= 0 ? this.completeChunkFuture : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ ScheduledMainThreadTaskQueues.POST_CHUNK_LOAD_JOIN_TASK_MAX_DELAY = this.postChunkLoadJoin >= 0 ? this.postChunkLoadJoin : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ ScheduledMainThreadTaskQueues.ANTI_XRAY_MODIFY_BLOCKS_TASK_MAX_DELAY = this.antiXrayModifyBlocks >= 0 ? this.antiXrayModifyBlocks : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ ScheduledMainThreadTaskQueues.TELEPORT_ASYNC_TASK_MAX_DELAY = this.teleportAsync >= 0 ? this.teleportAsync : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ ScheduledMainThreadTaskQueues.SEND_COMMAND_COMPLETION_SUGGESTIONS_TASK_MAX_DELAY = this.sendCommandCompletionSuggestions >= 0 ? this.sendCommandCompletionSuggestions : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY = this.kickForCommandPacketSpam >= 0 ? this.kickForCommandPacketSpam : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_RECIPE_PACKET_SPAM_TASK_MAX_DELAY = this.kickForRecipePacketSpam >= 0 ? this.kickForRecipePacketSpam : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY = this.kickForBookTooLargePacket >= 0 ? this.kickForBookTooLargePacket : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_EDITING_BOOK_TOO_QUICKLY_TASK_MAX_DELAY = this.kickForEditingBookTooQuickly >= 0 ? this.kickForEditingBookTooQuickly : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY = this.kickForIllegalCharactersInChatPacket >= 0 ? this.kickForIllegalCharactersInChatPacket : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_OUT_OF_ORDER_CHAT_PACKET_TASK_MAX_DELAY = this.kickForOutOfOrderChatPacket >= 0 ? this.kickForOutOfOrderChatPacket : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ ScheduledMainThreadTaskQueues.HANDLE_DISCONNECT_TASK_MAX_DELAY = this.handleDisconnect >= 0 ? this.handleDisconnect : ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY;
|
|
+ // Change the length of the pendingRunnables array of queues
|
|
+ int maxDelay = 0;
|
|
+ for (int delay : new int[]{
|
|
+ ScheduledMainThreadTaskQueues.DEFAULT_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.COMPLETE_CHUNK_FUTURE_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.POST_CHUNK_LOAD_JOIN_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.ANTI_XRAY_MODIFY_BLOCKS_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.TELEPORT_ASYNC_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.SEND_COMMAND_COMPLETION_SUGGESTIONS_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_RECIPE_PACKET_SPAM_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_EDITING_BOOK_TOO_QUICKLY_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.KICK_FOR_OUT_OF_ORDER_CHAT_PACKET_TASK_MAX_DELAY,
|
|
+ ScheduledMainThreadTaskQueues.HANDLE_DISCONNECT_TASK_MAX_DELAY
|
|
+ }) {
|
|
+ if (delay > maxDelay) {
|
|
+ maxDelay = delay;
|
|
+ }
|
|
+ }
|
|
+ int newPendingRunnablesLength = maxDelay + 1;
|
|
+ int oldPendingRunnablesLength = ScheduledMainThreadTaskQueues.queues.length;
|
|
+ if (oldPendingRunnablesLength != newPendingRunnablesLength) {
|
|
+ if (oldPendingRunnablesLength > newPendingRunnablesLength) {
|
|
+ // Move all tasks in queues that will be removed to the last queue
|
|
+ for (int i = newPendingRunnablesLength + 1; i < ScheduledMainThreadTaskQueues.queues.length; i++) {
|
|
+ ScheduledMainThreadTaskQueues.queues[maxDelay].addAll(ScheduledMainThreadTaskQueues.queues[i]);
|
|
+ }
|
|
+ // Update the first queue with elements index
|
|
+ if (ScheduledMainThreadTaskQueues.firstQueueWithPotentialTasksIndex >= newPendingRunnablesLength) {
|
|
+ ScheduledMainThreadTaskQueues.firstQueueWithPotentialTasksIndex = maxDelay;
|
|
+ }
|
|
+ }
|
|
+ ScheduledMainThreadTaskQueues.queues = Arrays.copyOf(ScheduledMainThreadTaskQueues.queues, newPendingRunnablesLength);
|
|
+ if (newPendingRunnablesLength > oldPendingRunnablesLength) {
|
|
+ // Create new queues
|
|
+ for (int i = oldPendingRunnablesLength; i < newPendingRunnablesLength; i++) {
|
|
+ ScheduledMainThreadTaskQueues.queues[i] = new MultiThreadedQueue<>();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ } finally {
|
|
+ ScheduledMainThreadTaskQueues.writeLock.unlock();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ }
|
|
+ // Gale end - base thread pool
|
|
+
|
|
}
|
|
|
|
public LogToConsole logToConsole;
|
|
diff --git a/src/main/java/org/galemc/gale/executor/AbstractBlockableEventLoop.java b/src/main/java/org/galemc/gale/executor/AbstractBlockableEventLoop.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..c8208f47c53df8ee438409b0a954c9dafca6b8fa
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/AbstractBlockableEventLoop.java
|
|
@@ -0,0 +1,20 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor;
|
|
+
|
|
+import net.minecraft.util.thread.BlockableEventLoop;
|
|
+
|
|
+import java.util.concurrent.Executor;
|
|
+
|
|
+/**
|
|
+ * An interface for the common functionality of {@link BlockableEventLoop} and {@link MinecraftServerBlockableEventLoop}.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+public interface AbstractBlockableEventLoop extends Executor {
|
|
+
|
|
+ boolean isSameThread();
|
|
+
|
|
+ void executeIfPossible(Runnable runnable);
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/galemc/gale/executor/MinecraftServerBlockableEventLoop.java b/src/main/java/org/galemc/gale/executor/MinecraftServerBlockableEventLoop.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..fe168eb59694415b84ee83f943a7e24b1f3d09da
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/MinecraftServerBlockableEventLoop.java
|
|
@@ -0,0 +1,189 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor;
|
|
+
|
|
+import com.mojang.logging.LogUtils;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.util.thread.BlockableEventLoop;
|
|
+import net.minecraft.util.thread.ProcessorHandle;
|
|
+import net.minecraft.util.thread.ReentrantBlockableEventLoop;
|
|
+import org.galemc.gale.executor.annotation.OriginalServerThreadOnly;
|
|
+import org.galemc.gale.executor.queue.AnyTickScheduledMainThreadTaskQueue;
|
|
+import org.galemc.gale.executor.queue.BaseTaskQueues;
|
|
+import org.galemc.gale.executor.queue.ScheduledMainThreadTaskQueues;
|
|
+import org.galemc.gale.executor.thread.MainThreadClaim;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+import org.slf4j.Logger;
|
|
+import org.spigotmc.WatchdogThread;
|
|
+
|
|
+import java.util.concurrent.CompletableFuture;
|
|
+import java.util.concurrent.RejectedExecutionException;
|
|
+import java.util.function.BooleanSupplier;
|
|
+import java.util.function.Supplier;
|
|
+
|
|
+/**
|
|
+ * This is a base class for {@link MinecraftServer}, as a replacement of {@link BlockableEventLoop}
|
|
+ * (and the intermediary class {@link ReentrantBlockableEventLoop}.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+public class MinecraftServerBlockableEventLoop implements ProcessorHandle<Runnable>, AbstractBlockableEventLoop {
|
|
+
|
|
+ private static final String NAME = "Server";
|
|
+ private static final Logger LOGGER = LogUtils.getLogger();
|
|
+
|
|
+ public static volatile int blockingCount;
|
|
+ private static volatile int reentrantCount;
|
|
+
|
|
+ public static boolean scheduleExecutables() {
|
|
+ return (reentrantCount != 0 || !MainThreadClaim.isCurrentThreadMainThreadAndNotClaimable(Thread.currentThread())) && !MinecraftServer.SERVER.isStopped();
|
|
+ }
|
|
+
|
|
+ protected boolean runningTask() {
|
|
+ return reentrantCount != 0;
|
|
+ }
|
|
+
|
|
+ public <V> CompletableFuture<V> submit(Supplier<V> task) {
|
|
+ return scheduleExecutables() ? CompletableFuture.supplyAsync(task, this) : CompletableFuture.completedFuture(task.get());
|
|
+ }
|
|
+
|
|
+ private CompletableFuture<Void> submitAsync(Runnable runnable) {
|
|
+ return CompletableFuture.supplyAsync(() -> {
|
|
+ runnable.run();
|
|
+ return null;
|
|
+ }, this);
|
|
+ }
|
|
+
|
|
+ public CompletableFuture<Void> submit(Runnable task) {
|
|
+ if (scheduleExecutables()) {
|
|
+ return this.submitAsync(task);
|
|
+ } else {
|
|
+ task.run();
|
|
+ return CompletableFuture.completedFuture(null);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void executeBlocking(Runnable runnable) {
|
|
+ if (!MainThreadClaim.isCurrentThreadMainThreadAndNotClaimable(Thread.currentThread())) {
|
|
+ this.submitAsync(runnable).join();
|
|
+ } else {
|
|
+ runnable.run();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @deprecated Use {@link ScheduledMainThreadTaskQueues#add(Runnable, int)} instead:
|
|
+ * do not rely on {@link ScheduledMainThreadTaskQueues#DEFAULT_TASK_MAX_DELAY}.
|
|
+ */
|
|
+ @Deprecated
|
|
+ @Override
|
|
+ public void tell(@NotNull Runnable message) {
|
|
+ ScheduledMainThreadTaskQueues.add(() -> {
|
|
+ if (Thread.currentThread() != WatchdogThread.instance) {
|
|
+ MinecraftServer.SERVER.getProfiler().incrementCounter("runTask");
|
|
+ }
|
|
+ ++reentrantCount;
|
|
+ try {
|
|
+ message.run();
|
|
+ } catch (Exception var3) {
|
|
+ if (var3.getCause() instanceof ThreadDeath) throw var3; // Paper
|
|
+ LOGGER.error(LogUtils.FATAL_MARKER, "Error executing task on {}", NAME, var3);
|
|
+ } finally {
|
|
+ --reentrantCount;
|
|
+ }
|
|
+ MinecraftServer.SERVER.executeMidTickTasks(); // Paper - execute chunk tasks mid tick
|
|
+ });
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void execute(@NotNull Runnable var1) {
|
|
+ if (scheduleExecutables()) {
|
|
+ this.tell(var1);
|
|
+ } else {
|
|
+ var1.run();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isSameThread() {
|
|
+ return MainThreadClaim.isCurrentThreadMainThreadAndNotClaimable(Thread.currentThread());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void executeIfPossible(Runnable runnable) {
|
|
+ if (MinecraftServer.SERVER.isStopped()) {
|
|
+ throw new RejectedExecutionException("Server already shutting down");
|
|
+ } else {
|
|
+ this.execute(runnable);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Runs all tasks, regardless of which tick they must be finished in, or whether there is time.
|
|
+ */
|
|
+ @OriginalServerThreadOnly
|
|
+ protected void runAllMainThreadTasksForAllTicks() {
|
|
+ Runnable task;
|
|
+ while (true) {
|
|
+ // Force polling every tasks regardless of the tick they have to be finished by
|
|
+ MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking = false;
|
|
+ task = ScheduledMainThreadTaskQueues.poll(MinecraftServer.SERVER.originalServerThread, true);
|
|
+ if (task == null) {
|
|
+ break;
|
|
+ }
|
|
+ task.run();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Runs all tasks while there is time.
|
|
+ * Runs at least all tasks that must be finished in the current tick, regardless of whether there is time.
|
|
+ */
|
|
+ @OriginalServerThreadOnly
|
|
+ protected void runAllTasksWithinTimeOrForCurrentTick() {
|
|
+ Runnable task;
|
|
+ while (true) {
|
|
+ /*
|
|
+ Update this value accurately: we are in 'spare time' here, we may have more time or not, and we are
|
|
+ definitely not already blocking.
|
|
+ */
|
|
+ MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking = !MinecraftServer.SERVER.haveTime();
|
|
+ task = BaseTaskQueues.anyTickScheduledMainThread.poll(MinecraftServer.SERVER.originalServerThread);
|
|
+ if (task == null) {
|
|
+ break;
|
|
+ }
|
|
+ task.run();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @deprecated Use {@link #managedBlock(BooleanSupplier, Long)} instead.
|
|
+ */
|
|
+ @OriginalServerThreadOnly
|
|
+ @Deprecated
|
|
+ public void managedBlock(@Nullable BooleanSupplier stopCondition) {
|
|
+ managedBlock(stopCondition, null);
|
|
+ }
|
|
+
|
|
+ @OriginalServerThreadOnly
|
|
+ public void managedBlock(@Nullable BooleanSupplier stopCondition, @Nullable Long stopTime) {
|
|
+ ++blockingCount;
|
|
+ try {
|
|
+ MainThreadClaim.secondaryThreadsCanStartToClaim();
|
|
+ try {
|
|
+ MinecraftServer.SERVER.originalServerThread.runTasksUntil(stopCondition, null, stopTime);
|
|
+ } finally {
|
|
+ MainThreadClaim.claimAsOriginalServerThread();
|
|
+ }
|
|
+ } finally {
|
|
+ --blockingCount;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NotNull String name() {
|
|
+ return NAME;
|
|
+ }
|
|
+
|
|
+}
|
|
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..e7ed376efb811e05b5cfecfa31c9c0041e70cf16 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 @@
|
|
|
|
package org.galemc.gale.executor.annotation;
|
|
|
|
+import org.galemc.gale.executor.lock.YieldingLock;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+
|
|
import java.lang.annotation.Documented;
|
|
import java.lang.annotation.ElementType;
|
|
import java.lang.annotation.Target;
|
|
@@ -16,6 +19,9 @@ import java.lang.annotation.Target;
|
|
* <br>
|
|
* In a method annotated with {@link PotentiallyYielding}, the only methods that can be called are those
|
|
* annotated with {@link PotentiallyYielding} or {@link YieldFree}.
|
|
+ * <br>
|
|
+ * It should be assumed that any method annotated with {@link PotentiallyYielding} is potentially blocking if used
|
|
+ * on a thread that is not a {@link BaseThread}.
|
|
*
|
|
* @author Martijn Muijsers under AGPL-3.0
|
|
*/
|
|
diff --git a/src/main/java/org/galemc/gale/executor/lock/MultipleWaitingThreadsYieldingLock.java b/src/main/java/org/galemc/gale/executor/lock/MultipleWaitingThreadsYieldingLock.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..cc005d17cd4a3b75cb4dcc809df70ded109ad66c
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/lock/MultipleWaitingThreadsYieldingLock.java
|
|
@@ -0,0 +1,39 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.lock;
|
|
+
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.wait.WaitingBaseThreadSet;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+
|
|
+import java.util.concurrent.locks.Lock;
|
|
+
|
|
+/**
|
|
+ * A {@link YieldingLock} for which multiple base threads may be waiting at the same time.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+public class MultipleWaitingThreadsYieldingLock extends YieldingLock {
|
|
+
|
|
+ private final WaitingBaseThreadSet waitingThreads = new WaitingBaseThreadSet();
|
|
+
|
|
+ public MultipleWaitingThreadsYieldingLock(Lock innerLock) {
|
|
+ super(innerLock);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void addWaitingThread(BaseThread thread) {
|
|
+ waitingThreads.add(thread);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void removeWaitingThread(BaseThread thread) {
|
|
+ waitingThreads.remove(thread);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public SignalReason getSignalReason() {
|
|
+ return this.waitingThreads;
|
|
+ }
|
|
+
|
|
+}
|
|
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..9dafb891aeda688ad668cd429c68d063e48429fe
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/lock/YieldingLock.java
|
|
@@ -0,0 +1,121 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.lock;
|
|
+
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.PotentiallyYielding;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+
|
|
+import java.util.concurrent.TimeUnit;
|
|
+import java.util.concurrent.locks.Condition;
|
|
+import java.util.concurrent.locks.Lock;
|
|
+
|
|
+/**
|
|
+ * A wrapper for a lock that can be acquired, but if not able to be acquired right away, can cause the current thread
|
|
+ * to perform other tasks, attempting to acquire the lock again at a later time.
|
|
+ * <br>
|
|
+ * The lock is reentrant if the underlying controlled lock is.
|
|
+ * <br>
|
|
+ * The lock only be speculatively acquired from any {@link BaseThread}. Acquiring it on a thread that is not a
|
|
+ * {@link BaseThread} will perform regular locking on the underlying controlled lock: which typically waits on
|
|
+ * failure, blocking the thread in the process.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+@AnyThreadSafe
|
|
+public abstract class YieldingLock implements Lock {
|
|
+
|
|
+ private final Lock innerLock;
|
|
+
|
|
+ public YieldingLock(Lock innerLock) {
|
|
+ this.innerLock = innerLock;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Attempts to acquire the lock immediately.
|
|
+ *
|
|
+ * @return Whether the lock was acquired.
|
|
+ */
|
|
+ @YieldFree
|
|
+ @Override
|
|
+ public boolean tryLock() {
|
|
+ return innerLock.tryLock();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Acquires the lock.
|
|
+ * <br>
|
|
+ * If the current threads is a {@link BaseThread}, this will yield to other tasks while the lock can not be
|
|
+ * acquired. Otherwise, this will block until the lock is acquired.
|
|
+ */
|
|
+ @PotentiallyYielding
|
|
+ @Override
|
|
+ public void lock() {
|
|
+ // Try to acquire the lock straight away
|
|
+ if (!this.innerLock.tryLock()) {
|
|
+ // If unsuccessful, we find out our current thread
|
|
+ BaseThread baseThread = BaseThread.getCurrent();
|
|
+ // If we are not on a base thread, we wait for the lock instead of yielding
|
|
+ if (baseThread == null) {
|
|
+ this.innerLock.lock();
|
|
+ return;
|
|
+ }
|
|
+ // Otherwise, we yield to other tasks until the lock can be acquired
|
|
+ baseThread.yieldUntil(null, this);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Releases the lock (must be called after having completed the computation block that required the lock).
|
|
+ */
|
|
+ @Override
|
|
+ public void unlock() {
|
|
+ this.innerLock.unlock();
|
|
+ // Signal the first waiting thread, if any.
|
|
+ // 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();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean tryLock(long l, @NotNull TimeUnit timeUnit) throws InterruptedException {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void lockInterruptibly() throws InterruptedException {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @NotNull
|
|
+ @Override
|
|
+ public Condition newCondition() {
|
|
+ // The inner lock may itself not support newCondition and throw UnsupportedOperationException
|
|
+ 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(BaseThread thread);
|
|
+
|
|
+ /**
|
|
+ * 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.
|
|
+ */
|
|
+ @YieldFree
|
|
+ public abstract void removeWaitingThread(BaseThread thread);
|
|
+
|
|
+ @YieldFree
|
|
+ public abstract SignalReason getSignalReason();
|
|
+
|
|
+}
|
|
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..3a79096e5a16798539d45d252672c3650b7a2afd
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/AbstractTaskQueue.java
|
|
@@ -0,0 +1,94 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.queue;
|
|
+
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.BaseThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.galemc.gale.executor.thread.wait.TaskWaitingBaseThreads;
|
|
+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.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+@AnyThreadSafe @YieldFree
|
|
+public interface AbstractTaskQueue {
|
|
+
|
|
+ /**
|
|
+ * @return Whether the tasks in this queue must be executed on the main thread.
|
|
+ */
|
|
+ boolean isMainThreadOnly();
|
|
+
|
|
+ /**
|
|
+ * @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 ScheduledMainThreadTaskQueues} that are scheduled for
|
|
+ * a later tick, while we are already out of spare time this tick.
|
|
+ * <br>
|
|
+ * 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.
|
|
+ */
|
|
+ @BaseThreadOnly
|
|
+ default boolean hasTasksThatCanStartNow(BaseThread currentThread) {
|
|
+ return (!currentThread.isRestricted && this.hasYieldingTasksThatCanStartNow()) || this.hasFreeTasksThatCanStartNow();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @return Whether this queue has any tasks at all.
|
|
+ */
|
|
+ boolean hasTasks();
|
|
+
|
|
+ /**
|
|
+ * Attempts to poll a task.
|
|
+ * <br>
|
|
+ * If the tasks in this queue are main-thread-only or this method may return a main-thread-only task,
|
|
+ * the calling thread must have claimed the main thread and
|
|
+ * {@link org.galemc.gale.executor.thread.MainThreadClaim#canMainThreadBeClaimed} must be false to prevent the
|
|
+ * polling of a main-thread-only task that can then not be immediately started.
|
|
+ *
|
|
+ * @param currentThread The current thread.
|
|
+ * @return The polled task, or null if this queue was empty.
|
|
+ */
|
|
+ @BaseThreadOnly
|
|
+ @Nullable Runnable poll(BaseThread currentThread);
|
|
+
|
|
+ /**
|
|
+ * Schedules a new task to this queue.
|
|
+ *
|
|
+ * @param task The task to schedule.
|
|
+ * @param yielding Whether the task is potentially yielding.
|
|
+ */
|
|
+ void add(Runnable task, boolean yielding);
|
|
+
|
|
+ /**
|
|
+ * @return The {@link SignalReason} that will be given to {@link TaskWaitingBaseThreads#signal} to signal new
|
|
+ * potentially yielding tasks being added to this queue, or null if irrelevant (e.g. because the signal never
|
|
+ * needs to be repeated, or because this queue will never hold potentially yielding tasks).
|
|
+ */
|
|
+ @Nullable SignalReason getYieldingSignalReason();
|
|
+
|
|
+ /**
|
|
+ * @return The {@link SignalReason} that will be given to {@link TaskWaitingBaseThreads#signal} to signal new
|
|
+ * yield-free tasks being added to this queue, or null if irrelevant (e.g. because the signal never
|
|
+ * needs to be repeated, or because this queue will never hold yield-free tasks).
|
|
+ */
|
|
+ @Nullable SignalReason getFreeSignalReason();
|
|
+
|
|
+}
|
|
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..b186e5b7dbd70a2cb3bd2e9559bca791d7ef2fb6
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledChunkCacheTaskQueue.java
|
|
@@ -0,0 +1,103 @@
|
|
+// 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.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.MainThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.galemc.gale.executor.thread.wait.WaitingBaseThreadSet;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+/**
|
|
+ * This class provides access to, but does not store, the tasks scheduled to be executed on the main thread,
|
|
+ * that are scheduled and normally polled by each world's {@link ServerChunkCache#mainThreadProcessor} in their
|
|
+ * respective {@link ServerChunkCache.MainThreadExecutor#managedBlock}. These tasks could normally also be run in the
|
|
+ * server's {@link MinecraftServer#managedBlock} if there were no more global scheduled main thread tasks, and as
|
|
+ * such we provide access to polling these tasks for any {@link BaseThread} to execute them as the main thread.
|
|
+ * <br>
|
|
+ * All tasks provided by this queue must be yield-free.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+@AnyThreadSafe @YieldFree
|
|
+public final class AllLevelsScheduledChunkCacheTaskQueue implements AbstractTaskQueue {
|
|
+
|
|
+ public static final SignalReason signalReason = SignalReason.createForTaskQueue(true, true, false);
|
|
+
|
|
+ AllLevelsScheduledChunkCacheTaskQueue() {}
|
|
+
|
|
+ @Override
|
|
+ public boolean isMainThreadOnly() {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ @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.SERVER == null) {
|
|
+ return false;
|
|
+ }
|
|
+ return this.hasTasks();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasTasks() {
|
|
+ for (ServerLevel worldserver : MinecraftServer.SERVER.getAllLevels()) {
|
|
+ if (worldserver.getChunkSource().mainThreadProcessor.hasPendingTasks()) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ @MainThreadOnly
|
|
+ @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.SERVER == null) {
|
|
+ return null;
|
|
+ }
|
|
+ ServerLevel[] levels = MinecraftServer.SERVER.getAllLevelsArray();
|
|
+ int startIndex = currentThread.allLevelsChunkCacheTaskQueueLevelIterationIndex = Math.min(currentThread.allLevelsChunkCacheTaskQueueLevelIterationIndex, levels.length - 1);
|
|
+ // Paper - force execution of all worlds, do not just bias the first
|
|
+ do {
|
|
+ ServerLevel level = levels[currentThread.allLevelsChunkCacheTaskQueueLevelIterationIndex++];
|
|
+ if (currentThread.allLevelsChunkCacheTaskQueueLevelIterationIndex == levels.length) {
|
|
+ currentThread.allLevelsChunkCacheTaskQueueLevelIterationIndex = 0;
|
|
+ }
|
|
+ if (level.serverLevelArrayIndex != -1) {
|
|
+ var executor = level.getChunkSource().mainThreadProcessor;
|
|
+ if (executor.hasPendingTasks()) {
|
|
+ return executor::pollTask;
|
|
+ }
|
|
+ }
|
|
+ } while (currentThread.allLevelsChunkCacheTaskQueueLevelIterationIndex != startIndex);
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void add(Runnable task, boolean yielding) {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @Nullable SignalReason getYieldingSignalReason() {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public SignalReason getFreeSignalReason() {
|
|
+ return signalReason;
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledMainThreadChunkTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledMainThreadChunkTaskQueue.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..02b61e420478d5b782930111dad96de5bd9f9801
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/AllLevelsScheduledMainThreadChunkTaskQueue.java
|
|
@@ -0,0 +1,104 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.queue;
|
|
+
|
|
+import io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.server.level.ServerChunkCache;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.MainThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+/**
|
|
+ * This class provides access to, but does not store, the tasks scheduled to be executed on the main thread,
|
|
+ * that are scheduled and normally polled by each world's {@link ServerLevel#chunkTaskScheduler} using
|
|
+ * respective {@link ChunkTaskScheduler#executeMainThreadTask}. These tasks could normally also be run in the
|
|
+ * server's {@link MinecraftServer#managedBlock} or a level's {@link ServerChunkCache}'s
|
|
+ * {@link ServerChunkCache.MainThreadExecutor#managedBlock}, and as such we provide access to polling these tasks
|
|
+ * for any {@link BaseThread} to execute them as the main thread.
|
|
+ * <br>
|
|
+ * All tasks provided by this queue must be yield-free.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+@AnyThreadSafe @YieldFree
|
|
+public final class AllLevelsScheduledMainThreadChunkTaskQueue implements AbstractTaskQueue {
|
|
+
|
|
+ public static final SignalReason signalReason = SignalReason.createForTaskQueue(true, true, false);
|
|
+
|
|
+ AllLevelsScheduledMainThreadChunkTaskQueue() {}
|
|
+
|
|
+ @Override
|
|
+ public boolean isMainThreadOnly() {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ @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.SERVER == null) {
|
|
+ return false;
|
|
+ }
|
|
+ return this.hasTasks();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasTasks() {
|
|
+ for (ServerLevel worldserver : MinecraftServer.SERVER.getAllLevels()) {
|
|
+ if (worldserver.chunkTaskScheduler.mainThreadExecutor.hasScheduledUncompletedTasksVolatile()) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ @MainThreadOnly
|
|
+ @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.SERVER == null) {
|
|
+ return null;
|
|
+ }
|
|
+ ServerLevel[] levels = MinecraftServer.SERVER.getAllLevelsArray();
|
|
+ int startIndex = currentThread.allLevelsScheduledMainThreadChunkTaskQueueLevelIterationIndex = Math.min(currentThread.allLevelsScheduledMainThreadChunkTaskQueueLevelIterationIndex, levels.length - 1);
|
|
+ // Paper - force execution of all worlds, do not just bias the first
|
|
+ do {
|
|
+ ServerLevel level = levels[currentThread.allLevelsScheduledMainThreadChunkTaskQueueLevelIterationIndex++];
|
|
+ if (currentThread.allLevelsScheduledMainThreadChunkTaskQueueLevelIterationIndex == levels.length) {
|
|
+ currentThread.allLevelsScheduledMainThreadChunkTaskQueueLevelIterationIndex = 0;
|
|
+ }
|
|
+ if (level.serverLevelArrayIndex != -1) {
|
|
+ var executor = level.chunkTaskScheduler.mainThreadExecutor;
|
|
+ if (executor.hasScheduledUncompletedTasksVolatile()) {
|
|
+ return executor::executeTask;
|
|
+ }
|
|
+ }
|
|
+ } while (currentThread.allLevelsScheduledMainThreadChunkTaskQueueLevelIterationIndex != startIndex);
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void add(Runnable task, boolean yielding) {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @Nullable SignalReason getYieldingSignalReason() {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public SignalReason getFreeSignalReason() {
|
|
+ return signalReason;
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/galemc/gale/executor/queue/AnyTickScheduledMainThreadTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/AnyTickScheduledMainThreadTaskQueue.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..014c94a590c1a0d534f2009caae3a2e3b4ddcdd6
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/AnyTickScheduledMainThreadTaskQueue.java
|
|
@@ -0,0 +1,68 @@
|
|
+// 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.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.MainThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+/**
|
|
+ * 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.
|
|
+ * <br>
|
|
+ * This queue does not support {@link #add}.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+@AnyThreadSafe @YieldFree
|
|
+public final class AnyTickScheduledMainThreadTaskQueue implements AbstractTaskQueue {
|
|
+
|
|
+ AnyTickScheduledMainThreadTaskQueue() {}
|
|
+
|
|
+ @Override
|
|
+ public boolean isMainThreadOnly() {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasYieldingTasksThatCanStartNow() {
|
|
+ return ScheduledMainThreadTaskQueues.hasTasksThatCanStartNow(true);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasTasks() {
|
|
+ return ScheduledMainThreadTaskQueues.hasTasks(true);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasFreeTasksThatCanStartNow() {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ @MainThreadOnly
|
|
+ @Override
|
|
+ public @Nullable Runnable poll(BaseThread currentThread) {
|
|
+ return ScheduledMainThreadTaskQueues.poll(currentThread, true);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void add(Runnable task, boolean yielding) {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public SignalReason getYieldingSignalReason() {
|
|
+ return ScheduledMainThreadTaskQueues.signalReason;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @Nullable SignalReason getFreeSignalReason() {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+}
|
|
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..c2756a05c87cf3fe11cec82f4a2453e44fe89595
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/BaseTaskQueues.java
|
|
@@ -0,0 +1,91 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.queue;
|
|
+
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+
|
|
+/**
|
|
+ * This class statically provides a list of task queues containing tasks for {@link BaseThread}s to poll from.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+public final class BaseTaskQueues {
|
|
+
|
|
+ private BaseTaskQueues() {}
|
|
+
|
|
+ /**
|
|
+ * This queue stores the tasks scheduled to be executed on the main thread, that are procedures that must run
|
|
+ * on the main 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.
|
|
+ * <br>
|
|
+ * These tasks are explicitly those that other tasks are waiting on, and as such always have a higher priority
|
|
+ * 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 TickTaskQueue}, and by extension in
|
|
+ * {@link #anyTickScheduledMainThread} and {@link #scheduledAsync}.
|
|
+ * <br>
|
|
+ * This queue may contain potentially yielding and yield-free tasks.
|
|
+ * <br>
|
|
+ * This queue's {@link AbstractTaskQueue#add} must not be called from the main thread, because the main thread must
|
|
+ * not defer to itself (because tasks in this queue are assumed to have to run independent of other main thread
|
|
+ * tasks, therefore this will cause a deadlock due to the scheduled task never starting). 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 deferredToMainThread = new YieldingAndFreeSimpleTaskQueue(true, true, 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 #thisTickScheduledMainThread} and {@link #scheduledAsync}.
|
|
+ * <br>
|
|
+ * This queue may contain potentially yielding and yield-free tasks.
|
|
+ * <br>
|
|
+ * 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 mainThreadTick = new YieldingAndFreeSimpleTaskQueue(true, true);
|
|
+
|
|
+ /**
|
|
+ * Currently unused.
|
|
+ *
|
|
+ * @see ThisTickScheduledMainThreadTaskQueue
|
|
+ */
|
|
+ public static final ThisTickScheduledMainThreadTaskQueue thisTickScheduledMainThread = new ThisTickScheduledMainThreadTaskQueue();
|
|
+
|
|
+ /**
|
|
+ * @see AnyTickScheduledMainThreadTaskQueue
|
|
+ */
|
|
+ public static final AnyTickScheduledMainThreadTaskQueue anyTickScheduledMainThread = new AnyTickScheduledMainThreadTaskQueue();
|
|
+
|
|
+ /**
|
|
+ * 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 BaseThread}), and have a higher priority
|
|
+ * in being started than pending tasks in {@link #scheduledAsync}.
|
|
+ * <br>
|
|
+ * This queue may contain potentially yielding and yield-free tasks.
|
|
+ * <br>
|
|
+ * Tasks in every queue are performed in the order they are added (first-in, first-out). Note that this means
|
|
+ * 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 baseThreadTick = new YieldingAndFreeSimpleTaskQueue(false, true);
|
|
+
|
|
+ /**
|
|
+ * @see AllLevelsScheduledChunkCacheTaskQueue
|
|
+ */
|
|
+ public static final AllLevelsScheduledChunkCacheTaskQueue allLevelsScheduledChunkCache = new AllLevelsScheduledChunkCacheTaskQueue();
|
|
+
|
|
+ /**
|
|
+ * @see AllLevelsScheduledMainThreadChunkTaskQueue
|
|
+ */
|
|
+ public static final AllLevelsScheduledMainThreadChunkTaskQueue allLevelsScheduledMainThreadChunk = new AllLevelsScheduledMainThreadChunkTaskQueue();
|
|
+
|
|
+ /**
|
|
+ * This queue stores the tasks scheduled to be executed on any thread, which would usually be stored in various
|
|
+ * executors with a specific purpose.
|
|
+ * <br>
|
|
+ * This queue may contain potentially yielding and yield-free tasks.
|
|
+ */
|
|
+ public static final YieldingAndFreeSimpleTaskQueue scheduledAsync = new YieldingAndFreeSimpleTaskQueue(false,false);
|
|
+
|
|
+}
|
|
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..01017d6f392ccb5172b4fa4522576ffb50ef06a3
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/FreeSimpleTaskQueue.java
|
|
@@ -0,0 +1,53 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.queue;
|
|
+
|
|
+import org.galemc.gale.concurrent.UnterminableExecutorService;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.BaseThreadOnly;
|
|
+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 all yield-free.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+@BaseThreadOnly @YieldFree
|
|
+public class FreeSimpleTaskQueue extends SimpleTaskQueue {
|
|
+
|
|
+ FreeSimpleTaskQueue(boolean mainThreadOnly, boolean baseThreadOnly) {
|
|
+ super(mainThreadOnly, baseThreadOnly, false, true);
|
|
+ }
|
|
+
|
|
+ FreeSimpleTaskQueue(boolean mainThreadOnly, boolean baseThreadOnly, boolean lifoQueues) {
|
|
+ super(mainThreadOnly, baseThreadOnly, 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);
|
|
+ }
|
|
+
|
|
+ };
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/galemc/gale/executor/queue/ScheduledMainThreadTaskQueues.java b/src/main/java/org/galemc/gale/executor/queue/ScheduledMainThreadTaskQueues.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..896f84decd11c6afdcd3f956983215d4e22ba0b7
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/ScheduledMainThreadTaskQueues.java
|
|
@@ -0,0 +1,294 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.queue;
|
|
+
|
|
+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.util.thread.BlockableEventLoop;
|
|
+import org.galemc.gale.concurrent.UnterminableExecutorService;
|
|
+import org.galemc.gale.executor.annotation.Access;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.Guarded;
|
|
+import org.galemc.gale.executor.annotation.MainThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+import java.util.Queue;
|
|
+import java.util.concurrent.Executor;
|
|
+import java.util.concurrent.ExecutorService;
|
|
+import java.util.concurrent.locks.Lock;
|
|
+import java.util.concurrent.locks.ReadWriteLock;
|
|
+import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
+
|
|
+/**
|
|
+ * This class stores the tasks scheduled to be executed on the main thread, which would usually be stored in a queue
|
|
+ * in the {@link MinecraftServer} in its role as a {@link BlockableEventLoop}. These tasks are not steps of
|
|
+ * ticking, but other tasks that must be executed at some point while no other main-thread-only task is running.
|
|
+ * <br>
|
|
+ * Each task is stored by the number of ticks left before it has to be executed. Tasks will always definitely be
|
|
+ * executed within the required number of ticks. Tasks may also be executed earlier than needed
|
|
+ * if there is time to do so.
|
|
+ * <br>
|
|
+ * 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.
|
|
+ * <br>
|
|
+ * All contained tasks are currently assumed to be potentially yielding: no special distinction for yield-free
|
|
+ * tasks is made.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+@AnyThreadSafe @YieldFree
|
|
+public final class ScheduledMainThreadTaskQueues {
|
|
+
|
|
+ ScheduledMainThreadTaskQueues() {}
|
|
+
|
|
+ public static int DEFAULT_TASK_MAX_DELAY = 2;
|
|
+ public static int COMPLETE_CHUNK_FUTURE_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+ public static int POST_CHUNK_LOAD_JOIN_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+ public static int ANTI_XRAY_MODIFY_BLOCKS_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+ public static int TELEPORT_ASYNC_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+ public static int SEND_COMMAND_COMPLETION_SUGGESTIONS_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+ public static int KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+ public static int KICK_FOR_RECIPE_PACKET_SPAM_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+ public static int KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+ public static int KICK_FOR_EDITING_BOOK_TOO_QUICKLY_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+ public static int KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+ public static int KICK_FOR_OUT_OF_ORDER_CHAT_PACKET_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+ public static int HANDLE_DISCONNECT_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY;
|
|
+
|
|
+ /**
|
|
+ * 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.
|
|
+ */
|
|
+ @Guarded(value = "#lock", except = "when optimistically reading using versionStamp as a stamp")
|
|
+ public static Queue[] queues = { new MultiThreadedQueue<>() };
|
|
+
|
|
+ /**
|
|
+ * <i>Probably</i> the lowest index of any queue in {@link #queues} that is non-empty.
|
|
+ * This is maintained as well as possible.
|
|
+ */
|
|
+ public static volatile int firstQueueWithPotentialTasksIndex = 0;
|
|
+
|
|
+ @Guarded(value = "#lock", fieldAccess = Access.WRITE)
|
|
+ public static volatile long versionStamp = 0;
|
|
+
|
|
+ private static final ReadWriteLock lock = new ReentrantReadWriteLock();
|
|
+ private static final Lock readLock = lock.readLock();
|
|
+ public static final Lock writeLock = lock.writeLock();
|
|
+
|
|
+ static final SignalReason signalReason = SignalReason.createForTaskQueue(true, true, true);
|
|
+
|
|
+ /**
|
|
+ * @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.
|
|
+ * <br>
|
|
+ * 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 (!queues[firstQueueWithPotentialTasksIndex].isEmpty()) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ int checkUpTo = tryNonCurrentTickQueuesAtAll && (!checkCanStartNow || !MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking) ? queues.length : 1;
|
|
+ for (int i = 0; i < checkUpTo; i++) {
|
|
+ if (!queues[i].isEmpty()) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ } finally {
|
|
+ readLock.unlock();
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Polls a task from this queue and returns it.
|
|
+ * 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.
|
|
+ * <br>
|
|
+ * 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.
|
|
+ *
|
|
+ * @return The task that was polled, or null if no task was found.
|
|
+ */
|
|
+ @MainThreadOnly
|
|
+ public static @Nullable Runnable poll(BaseThread currentThread, boolean tryNonCurrentTickQueuesAtAll) {
|
|
+ // Since we assume the tasks in this queue to be potentially yielding, fail if the thread is restricted
|
|
+ if (currentThread.isRestricted) {
|
|
+ return null;
|
|
+ }
|
|
+ pollFromFirstQueueOrOthers: while (true) {
|
|
+ // Try to get a task from the first queue first
|
|
+ Object task = queues[0].poll();
|
|
+ if (task != null) {
|
|
+ return (Runnable) task;
|
|
+ } else if (!tryNonCurrentTickQueuesAtAll || MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking) {
|
|
+ // We needed a task from the first queue
|
|
+ if (queues[0].isEmpty()) {
|
|
+ // The first queue really is empty, so we fail
|
|
+ return null;
|
|
+ }
|
|
+ // An element was added to the first queue in the meantime, try again
|
|
+ continue;
|
|
+ }
|
|
+ tryGetRunnableUntilSuccessOrNothingChanged: while (true) {
|
|
+ boolean goOverAllQueues = firstQueueWithPotentialTasksIndex == 0;
|
|
+ int oldFirstQueueWithElementsIndex = firstQueueWithPotentialTasksIndex;
|
|
+ long oldVersionStamp = versionStamp;
|
|
+ for (int i = goOverAllQueues ? 0 : firstQueueWithPotentialTasksIndex; i < queues.length; i++) {
|
|
+ if (!queues[i].isEmpty()) {
|
|
+ if (i == 0 || !MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking) {
|
|
+ task = queues[i].poll();
|
|
+ if (task == null) {
|
|
+ // We lost a race condition between the isEmpty() and poll() calls: just try again
|
|
+ continue tryGetRunnableUntilSuccessOrNothingChanged;
|
|
+ }
|
|
+ return (Runnable) task;
|
|
+ }
|
|
+ // Apparently we must now poll from the first queue only, try again
|
|
+ continue pollFromFirstQueueOrOthers;
|
|
+ }
|
|
+ // The queue was empty, the first queue with potential tasks must be the next one
|
|
+ if (i == firstQueueWithPotentialTasksIndex) {
|
|
+ if (i == queues.length - 1) {
|
|
+ firstQueueWithPotentialTasksIndex = 0;
|
|
+ } else {
|
|
+ firstQueueWithPotentialTasksIndex = i + 1;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (goOverAllQueues && firstQueueWithPotentialTasksIndex == 0 && oldVersionStamp == versionStamp) {
|
|
+ /*
|
|
+ We went over all queues and nothing changed in the meantime,
|
|
+ we give up, there appear to be no more tasks.
|
|
+ */
|
|
+ return null;
|
|
+ }
|
|
+ // Something changed, or we did not go over all queues, try again
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static int getTaskCount() {
|
|
+ //noinspection StatementWithEmptyBody
|
|
+ while (!readLock.tryLock());
|
|
+ try {
|
|
+ int count = 0;
|
|
+ for (Queue queue : queues) {
|
|
+ count += queue.size();
|
|
+ }
|
|
+ return count;
|
|
+ } finally {
|
|
+ readLock.unlock();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Schedules a new task to this queue.
|
|
+ *
|
|
+ * @param task The task to schedule.
|
|
+ *
|
|
+ * @deprecated Use {@link #add(Runnable, int)} instead: do not rely on {@link #DEFAULT_TASK_MAX_DELAY}.
|
|
+ */
|
|
+ @Deprecated
|
|
+ public static void add(Runnable task) {
|
|
+ add(task, DEFAULT_TASK_MAX_DELAY);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Schedules a new task to this queue.
|
|
+ *
|
|
+ * @param task The task to schedule.
|
|
+ * @param maxDelay The maximum number of ticks that the task must be finished by.
|
|
+ * A value of 0 means the task must be finished before the end of the current tick.
|
|
+ */
|
|
+ public static void add(Runnable task, int maxDelay) {
|
|
+ // Paper start - anything that does try to post to main during watchdog crash, run on watchdog
|
|
+ if (MinecraftServer.SERVER.hasStopped && Thread.currentThread() == MinecraftServer.SERVER.shutdownThread) {
|
|
+ task.run();
|
|
+ return;
|
|
+ }
|
|
+ // Paper end - anything that does try to post to main during watchdog crash, run on watchdog
|
|
+ //noinspection StatementWithEmptyBody
|
|
+ while (!readLock.tryLock());
|
|
+ try {
|
|
+ versionStamp++;
|
|
+ queues[maxDelay].add(task);
|
|
+ if (maxDelay < firstQueueWithPotentialTasksIndex) {
|
|
+ firstQueueWithPotentialTasksIndex = maxDelay;
|
|
+ }
|
|
+// LockSupport.unpark(MinecraftServer.SERVER.originalServerThread);
|
|
+ versionStamp++;
|
|
+ } finally {
|
|
+ readLock.unlock();
|
|
+ }
|
|
+ MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks = true;
|
|
+ signalReason.signalAnother();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * This moves the queues in {@link #queues}.
|
|
+ */
|
|
+ public static void shiftTasksForNextTick() {
|
|
+ //noinspection StatementWithEmptyBody
|
|
+ while (!writeLock.tryLock());
|
|
+ try {
|
|
+ versionStamp++;
|
|
+ // Move the queues to the preceding position
|
|
+ Queue firstQueue = queues[0];
|
|
+ for (int i = 1; i < queues.length; i++) {
|
|
+ queues[i - 1] = queues[i];
|
|
+ }
|
|
+ // Re-use the same instance that was the old first queue as the new last queue
|
|
+ queues[queues.length - 1] = firstQueue;
|
|
+ // Move any elements that were still present in the previous first queue to the new first queue
|
|
+ queues[0].addAll(firstQueue);
|
|
+ firstQueue.clear();
|
|
+ versionStamp++;
|
|
+ } finally {
|
|
+ writeLock.unlock();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * An executor for adding tasks to this queue,
|
|
+ * where {@link Executor#execute} calls {@link #add}.
|
|
+ *
|
|
+ * @deprecated Use {@link #add(Runnable, int)} instead: do not rely on {@link #DEFAULT_TASK_MAX_DELAY}.
|
|
+ */
|
|
+ @Deprecated
|
|
+ public static final ExecutorService executor = new UnterminableExecutorService() {
|
|
+
|
|
+ @Override
|
|
+ public void execute(@NotNull Runnable runnable) {
|
|
+ add(runnable);
|
|
+ }
|
|
+
|
|
+ };
|
|
+
|
|
+}
|
|
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..bcc14c165011af4dae98769f5dc48a16c676f205
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/SimpleTaskQueue.java
|
|
@@ -0,0 +1,185 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.queue;
|
|
+
|
|
+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.galemc.gale.collection.FIFOConcurrentLinkedQueue;
|
|
+import org.galemc.gale.concurrent.UnterminableExecutorService;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.MainThreadClaim;
|
|
+import org.galemc.gale.executor.thread.SecondaryThread;
|
|
+import org.galemc.gale.executor.thread.SecondaryThreadPool;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+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 potentially yielding and tasks that are yield-free.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+@AnyThreadSafe @YieldFree
|
|
+public class SimpleTaskQueue implements AbstractTaskQueue {
|
|
+
|
|
+ /**
|
|
+ * Whether the tasks in this queue can only be performed on the main thread.
|
|
+ */
|
|
+ public final boolean mainThreadOnly;
|
|
+
|
|
+ /**
|
|
+ * Whether the tasks in this queue can only be performed on a {@link BaseThread},
|
|
+ * as opposed to being able to be performed on any thread.
|
|
+ */
|
|
+ public final boolean baseThreadOnly;
|
|
+
|
|
+ /**
|
|
+ * Whether tasks in this queue can be potentially yielding.
|
|
+ */
|
|
+ public final boolean canHaveYieldingTasks;
|
|
+
|
|
+ /**
|
|
+ * Whether tasks in this queue can be yield-free.
|
|
+ */
|
|
+ public final boolean canHaveFreeTasks;
|
|
+
|
|
+ /**
|
|
+ * The queue of potentially yielding tasks, or null if {@link #canHaveYieldingTasks} is false.
|
|
+ */
|
|
+ private @Nullable Queue<Runnable> yieldingQueue;
|
|
+
|
|
+ /**
|
|
+ * The queue of yield-free tasks, or null if {@link #canHaveFreeTasks} is false.
|
|
+ */
|
|
+ private @Nullable Queue<Runnable> freeQueue;
|
|
+
|
|
+ /**
|
|
+ * The {@link SignalReason} used when signalling threads due to newly added potentially yielding tasks,
|
|
+ * or null if {@link #canHaveYieldingTasks} is false.
|
|
+ */
|
|
+ private final @Nullable SignalReason yieldingSignalReason;
|
|
+
|
|
+ /**
|
|
+ * The {@link SignalReason} used when signalling threads due to newly added yield-free tasks,
|
|
+ * or null if {@link #canHaveFreeTasks} is false.
|
|
+ */
|
|
+ private final @Nullable SignalReason freeSignalReason;
|
|
+
|
|
+ SimpleTaskQueue(boolean mainThreadOnly, boolean baseThreadOnly, boolean canHaveYieldingTasks, boolean canHaveFreeTasks) {
|
|
+ this(mainThreadOnly, baseThreadOnly, canHaveYieldingTasks, canHaveFreeTasks, false);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @param mainThreadOnly Value for {@link #mainThreadOnly}.
|
|
+ * @param baseThreadOnly Value for {@link #baseThreadOnly}.
|
|
+ * @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;
|
|
+ * otherwise, they will be FIFO.
|
|
+ */
|
|
+ SimpleTaskQueue(boolean mainThreadOnly, boolean baseThreadOnly, boolean canHaveYieldingTasks, boolean canHaveFreeTasks, boolean lifoQueues) {
|
|
+ if (mainThreadOnly && !baseThreadOnly) {
|
|
+ throw new IllegalArgumentException();
|
|
+ }
|
|
+ this.mainThreadOnly = mainThreadOnly;
|
|
+ this.baseThreadOnly = baseThreadOnly;
|
|
+ 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;
|
|
+ this.yieldingSignalReason = SignalReason.createForTaskQueue(this.mainThreadOnly, this.baseThreadOnly, true);
|
|
+ this.freeSignalReason = SignalReason.createForTaskQueue(this.mainThreadOnly, this.baseThreadOnly, false);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isMainThreadOnly() {
|
|
+ return this.mainThreadOnly;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasYieldingTasksThatCanStartNow() {
|
|
+ return this.canHaveYieldingTasks && !this.yieldingQueue.isEmpty();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasFreeTasksThatCanStartNow() {
|
|
+ return this.canHaveFreeTasks && !this.freeQueue.isEmpty();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasTasks() {
|
|
+ return (this.canHaveYieldingTasks && !this.yieldingQueue.isEmpty()) || (this.canHaveFreeTasks && !this.freeQueue.isEmpty());
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Attempts to poll a task.
|
|
+ * This always returns null on the {@link MinecraftServer#originalServerThread} if
|
|
+ * {@link MinecraftServer#canPollAsyncTasksOnOriginalServerThread} is false and {@link #baseThreadOnly} is false,
|
|
+ * and always returns null on a {@link SecondaryThread} if
|
|
+ * {@link SecondaryThreadPool#canOnlyPollAsyncTasksOnSecondaryThreads} is true and {@link #baseThreadOnly} is true.
|
|
+ *
|
|
+ * @see AbstractTaskQueue#poll
|
|
+ */
|
|
+ @Override
|
|
+ public @Nullable Runnable poll(BaseThread currentThread) {
|
|
+ boolean isOriginalServerThread = MinecraftServer.SERVER == null || currentThread == MinecraftServer.SERVER.originalServerThread;
|
|
+ if (isOriginalServerThread && !this.baseThreadOnly && !MinecraftServer.canPollAsyncTasksOnOriginalServerThread) {
|
|
+ return null;
|
|
+ } else if (!isOriginalServerThread && this.baseThreadOnly && SecondaryThreadPool.canOnlyPollAsyncTasksOnSecondaryThreads) {
|
|
+ return null;
|
|
+ }
|
|
+ Runnable task;
|
|
+ if (!currentThread.isRestricted && this.canHaveYieldingTasks) {
|
|
+ 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.
|
|
+ */
|
|
+ if (currentThread.lastSignalReason != null && currentThread.lastSignalReason != this.yieldingSignalReason) {
|
|
+ currentThread.lastSignalReason.signalAnother();
|
|
+ }
|
|
+ return task;
|
|
+ }
|
|
+ }
|
|
+ if (this.canHaveFreeTasks) {
|
|
+ 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.
|
|
+ */
|
|
+ if (currentThread.lastSignalReason != null && currentThread.lastSignalReason != this.freeSignalReason) {
|
|
+ currentThread.lastSignalReason.signalAnother();
|
|
+ }
|
|
+ return task;
|
|
+ }
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void add(Runnable task, boolean yielding) {
|
|
+ (yielding ? this.yieldingQueue : this.freeQueue).add(task);
|
|
+ if (!this.mainThreadOnly || MainThreadClaim.canMainThreadBeClaimed) {
|
|
+ (yielding ? this.yieldingSignalReason : this.freeSignalReason).signalAnother();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @Nullable SignalReason getYieldingSignalReason() {
|
|
+ return this.yieldingSignalReason;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @Nullable SignalReason getFreeSignalReason() {
|
|
+ return this.freeSignalReason;
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/galemc/gale/executor/queue/ThisTickScheduledMainThreadTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/ThisTickScheduledMainThreadTaskQueue.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..b94bbeb65d232d8015fd5d76c004145db49bc07a
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/ThisTickScheduledMainThreadTaskQueue.java
|
|
@@ -0,0 +1,82 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.queue;
|
|
+
|
|
+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.util.thread.BlockableEventLoop;
|
|
+import org.galemc.gale.concurrent.UnterminableExecutorService;
|
|
+import org.galemc.gale.executor.annotation.Access;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.Guarded;
|
|
+import org.galemc.gale.executor.annotation.MainThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.galemc.gale.executor.thread.wait.TaskWaitingBaseThreads;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+import java.util.Queue;
|
|
+import java.util.concurrent.Executor;
|
|
+import java.util.concurrent.ExecutorService;
|
|
+import java.util.concurrent.locks.Lock;
|
|
+import java.util.concurrent.locks.LockSupport;
|
|
+import java.util.concurrent.locks.ReadWriteLock;
|
|
+import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
+
|
|
+/**
|
|
+ * 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.
|
|
+ * <br>
|
|
+ * This queue does not support {@link #add}.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+@AnyThreadSafe @YieldFree
|
|
+public final class ThisTickScheduledMainThreadTaskQueue implements AbstractTaskQueue {
|
|
+
|
|
+ ThisTickScheduledMainThreadTaskQueue() {}
|
|
+
|
|
+ @Override
|
|
+ public boolean isMainThreadOnly() {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasYieldingTasksThatCanStartNow() {
|
|
+ return ScheduledMainThreadTaskQueues.hasTasksThatCanStartNow(false);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasTasks() {
|
|
+ return ScheduledMainThreadTaskQueues.hasTasks(false);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasFreeTasksThatCanStartNow() {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ @MainThreadOnly
|
|
+ @Override
|
|
+ public @Nullable Runnable poll(BaseThread currentThread) {
|
|
+ return ScheduledMainThreadTaskQueues.poll(currentThread, false);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void add(Runnable task, boolean yielding) {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public SignalReason getYieldingSignalReason() {
|
|
+ return ScheduledMainThreadTaskQueues.signalReason;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @Nullable SignalReason getFreeSignalReason() {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/galemc/gale/executor/queue/TickTaskQueue.java b/src/main/java/org/galemc/gale/executor/queue/TickTaskQueue.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..ee16e3b71c7951062596fef316d27bc66a3ad655
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/TickTaskQueue.java
|
|
@@ -0,0 +1,281 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.queue;
|
|
+
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.MainThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.MainThreadClaim;
|
|
+import org.galemc.gale.executor.thread.SecondaryThread;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.galemc.gale.executor.thread.wait.WaitingBaseThreadSet;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+import java.util.Queue;
|
|
+import java.util.concurrent.ConcurrentLinkedQueue;
|
|
+
|
|
+/**
|
|
+ * This class stores the tasks scheduled to be executed on any thread that can participate in ticking,
|
|
+ * which is the {@link MinecraftServer#originalServerThread} and any {@link SecondaryThread}.
|
|
+ * <br>
|
|
+ * These tasks are explicitly those that represent steps in ticking the server, and as such always have a
|
|
+ * higher priority in being started than pending tasks in
|
|
+ * {@link ScheduledMainThreadTaskQueues} and {@link ScheduledAsyncTaskQueue}.
|
|
+ * <br>
|
|
+ * This executor stores four queues, for tasks that:
|
|
+ * <ul>
|
|
+ * <li>Must be performed on the main thread, and are potentially yielding</li>
|
|
+ * <li>Must be performed on the main thread, and are yield-free</li>
|
|
+ * <li>Can be performed on any base thread, an are potentially yielding</li>
|
|
+ * <li>Can be performed on any base thread, and are yield-free</li>
|
|
+ * </ul>
|
|
+ * 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 tasks are necessarily performed in the order they are added, because they may be in
|
|
+ * different queues.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+@AnyThreadSafe @YieldFree
|
|
+public final class TickTaskQueue {
|
|
+
|
|
+ TickTaskQueue() {}
|
|
+
|
|
+ /**
|
|
+ * The queue of potentially yielding main-thread-only tasks.
|
|
+ */
|
|
+ private static final Queue<Runnable> mainThreadYieldingQueue = new ConcurrentLinkedQueue<>();
|
|
+
|
|
+ /**
|
|
+ * The queue of yield-free main-thread-only tasks.
|
|
+ */
|
|
+ private static final Queue<Runnable> mainThreadFreeQueue = new ConcurrentLinkedQueue<>();
|
|
+
|
|
+ /**
|
|
+ * The queue of potentially yielding {@link BaseThread} tasks.
|
|
+ */
|
|
+ private static final Queue<Runnable> baseThreadYieldingQueue = new ConcurrentLinkedQueue<>();
|
|
+
|
|
+ /**
|
|
+ * The queue of yield-free {@link BaseThread} tasks.
|
|
+ */
|
|
+ private static final Queue<Runnable> baseThreadFreeQueue = new ConcurrentLinkedQueue<>();
|
|
+
|
|
+ /**
|
|
+ * The base threads waiting for yield-free tasks to be added.
|
|
+ */
|
|
+ private static final WaitingBaseThreadSet freeWaitingThreads = new WaitingBaseThreadSet() {
|
|
+
|
|
+ @Override
|
|
+ public boolean pollAndSignal(SignalReason reason) {
|
|
+ if (super.pollAndSignal(reason)) {
|
|
+ return true;
|
|
+ }
|
|
+ return waitingThreads.pollAndSignal(reason);
|
|
+ }
|
|
+
|
|
+ };
|
|
+
|
|
+ /**
|
|
+ * The base threads waiting for tasks to be added.
|
|
+ */
|
|
+ private static final WaitingBaseThreadSet waitingThreads = new WaitingBaseThreadSet();
|
|
+
|
|
+ private static final SignalReason mainThreadOnlyFreeOrMoreTaskSignalReason = new SignalReason() {
|
|
+
|
|
+ @Override
|
|
+ public boolean signalAnother() {
|
|
+ if (MainThreadClaim.canMainThreadBeClaimed) {
|
|
+ if (freeWaitingThreads.pollAndSignal(this)) {
|
|
+ return true;
|
|
+ }
|
|
+ if (waitingThreads.pollAndSignal(this)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ };
|
|
+
|
|
+ private static final SignalReason generalMainThreadOnlyTaskSignalReason = new SignalReason() {
|
|
+
|
|
+ @Override
|
|
+ public boolean signalAnother() {
|
|
+ if (MainThreadClaim.canMainThreadBeClaimed) {
|
|
+ if (waitingThreads.pollAndSignal(this)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ };
|
|
+
|
|
+ /**
|
|
+ * Attempts to poll a yield-free main-thread-only task.
|
|
+ */
|
|
+ @MainThreadOnly
|
|
+ public static @Nullable Runnable pollMainThreadOnlyFreeTaskAsMainThread() {
|
|
+ return mainThreadFreeQueue.poll();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Attempts to poll a main-thread-only task.
|
|
+ * The different types of tasks are attempted to be polled in the following order:
|
|
+ * <ol>
|
|
+ * <li>Potentially yielding tasks</li>
|
|
+ * <li>Yield-free tasks</li>
|
|
+ * </ol>
|
|
+ */
|
|
+ @MainThreadOnly
|
|
+ public static @Nullable Runnable pollMainThreadOnlyTaskAsMainThread() {
|
|
+ Runnable task = mainThreadYieldingQueue.poll();
|
|
+ if (task != null) {
|
|
+ return task;
|
|
+ }
|
|
+ return mainThreadFreeQueue.poll();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Attempts to poll a yield-free that can be performed on any {@link BaseThread}.
|
|
+ */
|
|
+ public static @Nullable Runnable pollNonMainThreadOnlyFreeTask() {
|
|
+ return baseThreadFreeQueue.poll();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Attempts to poll a task that can be performed on any {@link BaseThread}.
|
|
+ * The different types of tasks are attempted to be polled in the following order:
|
|
+ * <ol>
|
|
+ * <li>Potentially yielding tasks</li>
|
|
+ * <li>Yield-free tasks</li>
|
|
+ * </ol>
|
|
+ */
|
|
+ public static @Nullable Runnable pollNonMainThreadOnlyTask() {
|
|
+ Runnable task = baseThreadYieldingQueue.poll();
|
|
+ if (task != null) {
|
|
+ return task;
|
|
+ }
|
|
+ return baseThreadFreeQueue.poll();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @return Whether there are pending potentially yielding main-thread-only tasks.
|
|
+ */
|
|
+ public static boolean hasMainThreadOnlyYieldingTasks() {
|
|
+ return !mainThreadYieldingQueue.isEmpty();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @return Whether there are pending potentially yielding {@link BaseThread} tasks.
|
|
+ */
|
|
+ public static boolean hasBaseThreadYieldingTasks() {
|
|
+ return !baseThreadYieldingQueue.isEmpty();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @return Whether there are pending yield-free tasks that must be performed on the main thread.
|
|
+ */
|
|
+ public static boolean hasMainThreadOnlyFreeTasks() {
|
|
+ return !mainThreadFreeQueue.isEmpty();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @return Whether there are pending main-thread-only tasks.
|
|
+ */
|
|
+ public static boolean hasMainThreadOnlyTasks() {
|
|
+ return !mainThreadYieldingQueue.isEmpty() || !mainThreadFreeQueue.isEmpty();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @return Whether there are pending yield-free non-main-thread-only tasks.
|
|
+ */
|
|
+ public static boolean hasNonMainThreadOnlyFreeTasks() {
|
|
+ return !baseThreadFreeQueue.isEmpty();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @return Whether there are pending non-main-thread-only.
|
|
+ */
|
|
+ public static boolean hasNonMainThreadOnlyTasks() {
|
|
+ return !baseThreadYieldingQueue.isEmpty() || !baseThreadFreeQueue.isEmpty();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @return Whether there are pending tasks.
|
|
+ */
|
|
+ public static boolean hasTasks() {
|
|
+ return !mainThreadYieldingQueue.isEmpty() || !mainThreadFreeQueue.isEmpty() || !baseThreadYieldingQueue.isEmpty() || !baseThreadFreeQueue.isEmpty();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Schedules a new task to this queue.
|
|
+ *
|
|
+ * @param task The task to schedule.
|
|
+ * @param mainThreadOnly Whether the task must be performed on the main thread.
|
|
+ * @param yielding Whether the task is potentially yielding.
|
|
+ */
|
|
+ public static void add(Runnable task, boolean mainThreadOnly, boolean yielding) {
|
|
+ (mainThreadOnly ? (yielding ? mainThreadYieldingQueue : mainThreadFreeQueue) : (yielding ? baseThreadYieldingQueue : baseThreadFreeQueue)).add(task);
|
|
+ if (!mainThreadOnly || MainThreadClaim.canMainThreadBeClaimed) {
|
|
+ if (!yielding) {
|
|
+ if (freeWaitingThreads.pollAndSignal(mainThreadOnly ? mainThreadOnlyFreeOrMoreTaskSignalReason : freeWaitingThreads)) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ waitingThreads.pollAndSignal(mainThreadOnly ? generalMainThreadOnlyTaskSignalReason : waitingThreads);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void signalWaitingThreadsAfterMainThreadBecameClaimable() {
|
|
+ if (!mainThreadFreeQueue.isEmpty()) {
|
|
+ if (freeWaitingThreads.pollAndSignal()) {
|
|
+ return;
|
|
+ }
|
|
+ waitingThreads.pollAndSignal();
|
|
+ return;
|
|
+ }
|
|
+ if (!mainThreadYieldingQueue.isEmpty()) {
|
|
+ waitingThreads.pollAndSignal();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Adds a thread to the set of threads waiting for certain new tasks to be added to this queue.
|
|
+ *
|
|
+ * @param thread The thread to register as waiting for new tasks.
|
|
+ * @param isRestricted Whether the given thread is restricted.
|
|
+ */
|
|
+ public static void addWaitingThread(BaseThread thread, boolean isRestricted) {
|
|
+ (isRestricted ? freeWaitingThreads : waitingThreads).add(thread);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Removes a thread from the set of threads waiting for certain new tasks to be added to this queue.
|
|
+ *
|
|
+ * @param thread The thread to unregister as waiting for new tasks.
|
|
+ * @param isRestricted Whether the given thread was restricted when being added.
|
|
+ */
|
|
+ public static void removeWaitingThread(BaseThread thread, boolean isRestricted) {
|
|
+ (isRestricted ? freeWaitingThreads : waitingThreads).remove(thread);
|
|
+ }
|
|
+
|
|
+ public static boolean isNonMainThreadOnlyFreeOrMoreTaskSignalReason(SignalReason reason) {
|
|
+ return reason == freeWaitingThreads || reason == waitingThreads;
|
|
+ }
|
|
+
|
|
+ public static boolean isGeneralNonMainThreadOnlyTaskSignalReason(SignalReason reason) {
|
|
+ return reason == waitingThreads;
|
|
+ }
|
|
+
|
|
+ public static boolean isMainThreadOnlyFreeOrMoreTaskSignalReason(SignalReason reason) {
|
|
+ return reason == mainThreadOnlyFreeOrMoreTaskSignalReason || reason == generalMainThreadOnlyTaskSignalReason;
|
|
+ }
|
|
+
|
|
+ public static boolean isGeneralMainThreadOnlyTaskSignalReason(SignalReason reason) {
|
|
+ return reason == generalMainThreadOnlyTaskSignalReason;
|
|
+ }
|
|
+
|
|
+}
|
|
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..7078d50a575f875828ce8284807f2545b0ca3b0d
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/YieldingAndFreeSimpleTaskQueue.java
|
|
@@ -0,0 +1,56 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.queue;
|
|
+
|
|
+import org.galemc.gale.concurrent.UnterminableExecutorService;
|
|
+import org.galemc.gale.executor.annotation.BaseThreadOnly;
|
|
+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
|
|
+ */
|
|
+@BaseThreadOnly @YieldFree
|
|
+public class YieldingAndFreeSimpleTaskQueue extends SimpleTaskQueue {
|
|
+
|
|
+ YieldingAndFreeSimpleTaskQueue(boolean mainThreadOnly, boolean baseThreadOnly) {
|
|
+ super(mainThreadOnly, baseThreadOnly, true, true);
|
|
+ }
|
|
+
|
|
+ YieldingAndFreeSimpleTaskQueue(boolean mainThreadOnly, boolean baseThreadOnly, boolean lifoQueues) {
|
|
+ super(mainThreadOnly, baseThreadOnly, 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..e79f426883c77f81c7068c5d019f1708ef0de5f9
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/queue/YieldingSimpleTaskQueue.java
|
|
@@ -0,0 +1,53 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.queue;
|
|
+
|
|
+import org.galemc.gale.concurrent.UnterminableExecutorService;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.BaseThreadOnly;
|
|
+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 all potentially yielding.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+@BaseThreadOnly @YieldFree
|
|
+public class YieldingSimpleTaskQueue extends SimpleTaskQueue {
|
|
+
|
|
+ YieldingSimpleTaskQueue(boolean mainThreadOnly, boolean baseThreadOnly) {
|
|
+ super(mainThreadOnly, baseThreadOnly, true, false);
|
|
+ }
|
|
+
|
|
+ YieldingSimpleTaskQueue(boolean mainThreadOnly, boolean baseThreadOnly, boolean lifoQueues) {
|
|
+ super(mainThreadOnly, baseThreadOnly, 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/BaseThread.java b/src/main/java/org/galemc/gale/executor/thread/BaseThread.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..8c7023025f6990eacf28186dbf42b275447b13f3
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/thread/BaseThread.java
|
|
@@ -0,0 +1,564 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.thread;
|
|
+
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.galemc.gale.executor.annotation.Access;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.Guarded;
|
|
+import org.galemc.gale.executor.annotation.PotentiallyBlocking;
|
|
+import org.galemc.gale.executor.annotation.PotentiallyYielding;
|
|
+import org.galemc.gale.executor.annotation.ThisBaseThreadOnly;
|
|
+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.queue.AllLevelsScheduledChunkCacheTaskQueue;
|
|
+import org.galemc.gale.executor.queue.AllLevelsScheduledMainThreadChunkTaskQueue;
|
|
+import org.galemc.gale.executor.queue.BaseTaskQueues;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.galemc.gale.executor.thread.wait.TaskWaitingBaseThreads;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+import org.spigotmc.WatchdogThread;
|
|
+import io.papermc.paper.util.TickThread;
|
|
+
|
|
+import java.util.Arrays;
|
|
+import java.util.concurrent.TimeUnit;
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
+import java.util.concurrent.locks.Condition;
|
|
+import java.util.concurrent.locks.Lock;
|
|
+import java.util.concurrent.locks.ReentrantLock;
|
|
+import java.util.function.BooleanSupplier;
|
|
+
|
|
+/**
|
|
+ * This class allows using <code>instanceof</code> to quickly check if the current thread is a base thread, i.e.
|
|
+ * the {@link MinecraftServer#originalServerThread} or a {@link SecondaryThread},
|
|
+ * and contains information present on any base thread.
|
|
+ * <br>
|
|
+ * Since the {@link WatchdogThread#instance} is also a {@link TickThread}, it is also an instance of this class.
|
|
+ * When active, it behaves as much as possible like the {@link MinecraftServer#originalServerThread} because only one of them
|
|
+ * may be usable at a time.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+public abstract class BaseThread extends Thread {
|
|
+
|
|
+ /**
|
|
+ * The maximum yield depth, beyond which a thread can no longer start more potentially yielding tasks.
|
|
+ */
|
|
+ public static final int MAXIMUM_YIELD_DEPTH = Integer.getInteger("gale.max.yield.depth", 100);
|
|
+
|
|
+ /**
|
|
+ * Wait for a limited time only before briefly waking up. This is the minimum time to wait for.
|
|
+ * <br>
|
|
+ * This at least serves to wake up the thread to re-check the {@code stopCondition} in {@link #runTasksUntil}.
|
|
+ */
|
|
+ private static final long MIN_WAIT_CONDITION_TIMEOUT_MICROS = 1L;
|
|
+
|
|
+ /**
|
|
+ * Equivalent to {@link #MIN_WAIT_CONDITION_TIMEOUT_MICROS}, but this is the default time to wait for,
|
|
+ * unless a non-null {@code stopTime} is provided to {@link #await}.
|
|
+ */
|
|
+ private static final long DEFAULT_WAIT_CONDITION_TIMEOUT_MICROS = 100L;
|
|
+
|
|
+ public static final AbstractTaskQueue[] taskQueues = {
|
|
+ BaseTaskQueues.deferredToMainThread,
|
|
+ BaseTaskQueues.mainThreadTick,
|
|
+ BaseTaskQueues.anyTickScheduledMainThread,
|
|
+ BaseTaskQueues.baseThreadTick,
|
|
+ BaseTaskQueues.allLevelsScheduledChunkCache,
|
|
+ BaseTaskQueues.allLevelsScheduledMainThreadChunk,
|
|
+ BaseTaskQueues.scheduledAsync
|
|
+ };
|
|
+
|
|
+ public static final AbstractTaskQueue[] mainThreadOnlyTaskQueues;
|
|
+ public static final AbstractTaskQueue[] nonMainThreadOnlyTaskQueues;
|
|
+ static {
|
|
+ mainThreadOnlyTaskQueues = Arrays.stream(taskQueues).filter(AbstractTaskQueue::isMainThreadOnly).toArray(AbstractTaskQueue[]::new);
|
|
+ nonMainThreadOnlyTaskQueues = Arrays.stream(taskQueues).filter(queue -> !queue.isMainThreadOnly()).toArray(AbstractTaskQueue[]::new);
|
|
+ }
|
|
+
|
|
+ public final int baseThreadIndex;
|
|
+
|
|
+ /**
|
|
+ * The current yield depth of this thread.
|
|
+ */
|
|
+ @ThisBaseThreadOnly
|
|
+ private int yieldDepth = 0;
|
|
+
|
|
+ /**
|
|
+ * Whether this thread is currently restricted.
|
|
+ */
|
|
+ @ThisBaseThreadOnly
|
|
+ public boolean isRestricted = false;
|
|
+
|
|
+ /**
|
|
+ * The queue that the last polled task was successfully polled from.
|
|
+ */
|
|
+ @ThisBaseThreadOnly
|
|
+ private AbstractTaskQueue lastPolledFromTaskQueue = null;
|
|
+
|
|
+ /**
|
|
+ * Whether this thread is currently executing a main-thread-only task.
|
|
+ */
|
|
+ @ThisBaseThreadOnly
|
|
+ public final AtomicInteger mainThreadOnlyTasksBeingExecutedCount = new AtomicInteger();
|
|
+
|
|
+ /**
|
|
+ * The lock to guard this thread's sleeping and waking actions.
|
|
+ */
|
|
+ private final Lock waitLock = new ReentrantLock();
|
|
+
|
|
+ /**
|
|
+ * The condition to wait for a signal, when this thread has to wait for something to do.
|
|
+ */
|
|
+ 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}.
|
|
+ */
|
|
+ @AnyThreadSafe(Access.READ) @ThisBaseThreadOnly(Access.WRITE)
|
|
+ protected volatile boolean isPollingTask = true;
|
|
+
|
|
+ /**
|
|
+ * Whether this thread should not start waiting for something to do the next time no task could be polled,
|
|
+ * but instead try polling a task again.
|
|
+ */
|
|
+ @AnyThreadSafe
|
|
+ private volatile boolean skipNextWait = false;
|
|
+
|
|
+ /**
|
|
+ * Whether this thread is currently waiting for something to do.
|
|
+ */
|
|
+ @AnyThreadSafe(Access.READ) @ThisBaseThreadOnly(Access.WRITE)
|
|
+ @Guarded("#waitLock")
|
|
+ private volatile boolean isWaiting = false;
|
|
+
|
|
+ /**
|
|
+ * The last reason this thread was signalled before the current poll attempt, or null if the current
|
|
+ * poll attempt was not preceded by signalling (but by yielding for example).
|
|
+ */
|
|
+ public volatile @Nullable SignalReason lastSignalReason = null;
|
|
+
|
|
+ protected BaseThread(Runnable target, String name, int baseThreadIndex) {
|
|
+ super(target, name);
|
|
+ this.baseThreadIndex = baseThreadIndex;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Yields to tasks, which means incrementing the yield depth, and polling and executing tasks while possible
|
|
+ * and the stop condition is not met, then decrementing the yield depth again.
|
|
+ * 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 wait for either a task that can be executed by this thread
|
|
+ * to become available, or for the {@code yieldingLock}, if given, to be released.
|
|
+ * <br>
|
|
+ * Exactly one of {@code stopCondition} and {@code yieldingLock} must be non-null.
|
|
+ */
|
|
+ @ThisBaseThreadOnly @PotentiallyYielding("this method is meant to yield")
|
|
+ public void yieldUntil(@Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock) {
|
|
+ this.yieldDepth++;
|
|
+ if (!this.isRestricted) {
|
|
+ if (this.yieldDepth >= MAXIMUM_YIELD_DEPTH || this.mainThreadOnlyTasksBeingExecutedCount.get() > 0) {
|
|
+ this.isRestricted = true;
|
|
+ SecondaryThreadPool.threadBecameRestricted();
|
|
+ }
|
|
+ }
|
|
+ this.runTasksUntil(stopCondition, yieldingLock, null);
|
|
+ this.yieldDepth--;
|
|
+ if (this.isRestricted) {
|
|
+ if (this.yieldDepth < MAXIMUM_YIELD_DEPTH && (this.yieldDepth == 0 || this.mainThreadOnlyTasksBeingExecutedCount.get() == 0)) {
|
|
+ this.isRestricted = false;
|
|
+ SecondaryThreadPool.threadIsNoLongerRestricted();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * 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,
|
|
+ * or {@link System#nanoTime} reaches the given {@code stopTime}.
|
|
+ * <br>
|
|
+ * At least one of {@code stopCondition}, {@code yieldingLock} and {@code stopTime} must be non-null.
|
|
+ * Only one the above parameters may be non-null, except for the pair of {@code stopCondition} and {@code stopTime}:
|
|
+ * in which case running tasks continues until the stop condition becomes true, but {@link #await} is called
|
|
+ * with the given {@code stopTime}.
|
|
+ *
|
|
+ * @see #yieldUntil
|
|
+ */
|
|
+ @ThisBaseThreadOnly @PotentiallyYielding("may yield further if an executed task is potentially yielding")
|
|
+ public void runTasksUntil(@Nullable BooleanSupplier stopCondition, @Nullable YieldingLock yieldingLock, @Nullable Long stopTime) {
|
|
+ /*
|
|
+ Endless loop that attempts to perform a task, and if one is found, tries to perform another again,
|
|
+ but if none is found, starts awaiting such a task to become available, or for the given yielding lock
|
|
+ to be released.
|
|
+ */
|
|
+ /*
|
|
+ Make sure we do not allow the main thread to be claimed in between the moment we finish
|
|
+ executing a main-thread-only task, and we can poll the next one: we must as well stay the main thread if
|
|
+ we immediately start another main-thread-only task.
|
|
+ */
|
|
+ boolean stillHaveToSetMainThreadCanBeClaimed = false;
|
|
+ this.skipNextWait = false;
|
|
+ // Reset the last signal reason before the next poll
|
|
+ this.lastSignalReason = null;
|
|
+ this.isPollingTask = true;
|
|
+ while (stopCondition != null ? !stopCondition.getAsBoolean() : yieldingLock != null ? !yieldingLock.tryLock() : System.nanoTime() < stopTime) {
|
|
+ MinecraftServer.isInSpareTimeAndHaveNoMoreTimeAndNotAlreadyBlocking = MinecraftServer.isInSpareTime && MinecraftServer.blockingCount == 0 && !MinecraftServer.SERVER.haveTime();
|
|
+ if (this.canAcceptTasks()) {
|
|
+ // Get a task that we can execute
|
|
+ Runnable task = this.pollTask();
|
|
+ if (task != null) {
|
|
+ boolean isTaskMainThreadOnly = this.lastPolledFromTaskQueue.isMainThreadOnly();
|
|
+ if (isTaskMainThreadOnly) {
|
|
+ stillHaveToSetMainThreadCanBeClaimed = true;
|
|
+ /*
|
|
+ We successfully polled a main-thread-only task, if there are more left in the queue,
|
|
+ make sure we skip the {@link MinecraftServer#mayHaveDelayedTasks()} check next time.
|
|
+ */
|
|
+ if (!MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks && this.lastPolledFromTaskQueue.hasTasks()) {
|
|
+ MinecraftServer.nextTimeAssumeWeMayHaveDelayedTasks = true;
|
|
+ }
|
|
+ } else {
|
|
+ /*
|
|
+ If we still have to allow other threads to become the main thread,
|
|
+ do so before starting a task that is not main-thread-only.
|
|
+ */
|
|
+ if (stillHaveToSetMainThreadCanBeClaimed) {
|
|
+ stillHaveToSetMainThreadCanBeClaimed = false;
|
|
+ MainThreadClaim.setMainThreadCanBeClaimed();
|
|
+ }
|
|
+ }
|
|
+ this.isPollingTask = false;
|
|
+ if (isTaskMainThreadOnly) {
|
|
+ this.mainThreadOnlyTasksBeingExecutedCount.incrementAndGet();
|
|
+ }
|
|
+ task.run();
|
|
+ if (isTaskMainThreadOnly) {
|
|
+ MinecraftServer.SERVER.executeMidTickTasks(); // Paper - execute chunk tasks mid tick
|
|
+ }
|
|
+ if (isTaskMainThreadOnly) {
|
|
+ this.mainThreadOnlyTasksBeingExecutedCount.decrementAndGet();
|
|
+ }
|
|
+ this.isPollingTask = true;
|
|
+ // Reset the last signal reason before the next poll
|
|
+ this.lastSignalReason = null;
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ // If we still have to allow other threads to become the main thread, do so before waiting
|
|
+ if (stillHaveToSetMainThreadCanBeClaimed) {
|
|
+ stillHaveToSetMainThreadCanBeClaimed = false;
|
|
+ if (this.mainThreadOnlyTasksBeingExecutedCount.get() == 0) {
|
|
+ MainThreadClaim.setMainThreadCanBeClaimed();
|
|
+ }
|
|
+ }
|
|
+ /*
|
|
+ If no task that can be executed was found, wait for one to become available,
|
|
+ or for the given yielding lock to be released. This is the only time we should ever block inside
|
|
+ a potentially yielding procedure.
|
|
+ */
|
|
+ this.await(yieldingLock, stopTime);
|
|
+ }
|
|
+ if (this.lastSignalReason != null && yieldingLock != null && this.lastSignalReason != yieldingLock.getSignalReason()) {
|
|
+ // The thread was signalled for another reason than the lock - but we acquired the lock instead
|
|
+ this.lastSignalReason.signalAnother();
|
|
+ }
|
|
+ this.isPollingTask = false;
|
|
+ /*
|
|
+ If we still have to allow other threads to become the main thread, do so before exiting running tasks.
|
|
+ */
|
|
+ if (stillHaveToSetMainThreadCanBeClaimed) {
|
|
+ if (this.mainThreadOnlyTasksBeingExecutedCount.get() == 0) {
|
|
+ MainThreadClaim.setMainThreadCanBeClaimed();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @return Whether this thread can currently accept more tasks. This is true if either the task is not restricted
|
|
+ * (i.e. it has not exceeded the maximum yield depth), or if the task is restricted but there are no pending
|
|
+ * potentially yielding tasks that a replacement thread could start if this thread were to idle.
|
|
+ */
|
|
+ @ThisBaseThreadOnly @YieldFree
|
|
+ protected boolean canAcceptTasks() {
|
|
+ /*
|
|
+ This thread cannot accept new tasks if it is a surplus thread in the thread pool,
|
|
+ and not enough threads are restricted to warrant use of this surplus thread.
|
|
+ */
|
|
+ if (this.baseThreadIndex >= SecondaryThreadPool.intendedSecondaryParallelism + 1 + SecondaryThreadPool.restrictedBaseThreadCount.get()) {
|
|
+ return false;
|
|
+ }
|
|
+ /*
|
|
+ Otherwise, this thread can always accept new tasks if it is not restricted, i.e. if it has not exceeded the
|
|
+ maximum yield depth and if it is not currently yielding from a main-thread-only task.
|
|
+ */
|
|
+ if (!this.isRestricted) {
|
|
+ return true;
|
|
+ }
|
|
+ /*
|
|
+ Lastly, check if there are any yielding tasks that could be performed by other non-restricted:
|
|
+ if there are, since we are restricted, we should let other threads start executing those tasks instead.
|
|
+ */
|
|
+ for (var queue : nonMainThreadOnlyTaskQueues) {
|
|
+ if (queue.hasYieldingTasksThatCanStartNow()) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ if (MainThreadClaim.canMainThreadBeClaimed) {
|
|
+ for (var queue : mainThreadOnlyTaskQueues) {
|
|
+ if (queue.hasYieldingTasksThatCanStartNow()) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+// public boolean doesAnyMainThreadOnlyQueueHaveAppropriateTasks() {
|
|
+// for (var queue : mainThreadOnlyTaskQueues) {
|
|
+// if (queue.hasTasksThatCanStartNow(this)) {
|
|
+// return true;
|
|
+// }
|
|
+// }
|
|
+// return false;
|
|
+// }
|
|
+
|
|
+ /**
|
|
+ * Polls a task from any queue this thread can currently poll from, and returns it.
|
|
+ * Polling main-thread-only tasks is attempted before polling tasks that can be performed on any thread,
|
|
+ * and secondarily, polling potentially yielding tasks is attempted before yield-free tasks.
|
|
+ *
|
|
+ * @return The task that was polled, or null if no task was found.
|
|
+ */
|
|
+ @ThisBaseThreadOnly @YieldFree
|
|
+ protected @Nullable Runnable pollTask() {
|
|
+ boolean attemptedToClaimMeanThread = false;
|
|
+ boolean hasJustClaimedMainThread = false;
|
|
+ for (var queue : taskQueues) {
|
|
+ // Claim the main thread if needed for this queue
|
|
+ boolean isQueueMainThreadOnly = queue.isMainThreadOnly();
|
|
+ if (isQueueMainThreadOnly) {
|
|
+ if (!attemptedToClaimMeanThread) {
|
|
+ // Don't attempt to claim the main thread if this queue is empty anyway
|
|
+ if (!queue.hasTasksThatCanStartNow(this)) {
|
|
+ continue;
|
|
+ }
|
|
+ if (MainThreadClaim.attemptToClaim(this, true)) {
|
|
+ hasJustClaimedMainThread = true;
|
|
+ }
|
|
+ attemptedToClaimMeanThread = true;
|
|
+ }
|
|
+ // Skip this queue if we have failed to claim the main thread
|
|
+ if (!hasJustClaimedMainThread) {
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ Runnable task = queue.poll(this);
|
|
+ if (task != null) {
|
|
+ // We successfully polled a task
|
|
+ if (hasJustClaimedMainThread && !isQueueMainThreadOnly) {
|
|
+ /*
|
|
+ We claimed the main thread but polled from a queue that is not main-thread-only, so we must
|
|
+ mark the main thread as claimable again.
|
|
+ */
|
|
+ MainThreadClaim.setMainThreadCanBeClaimed();
|
|
+ }
|
|
+ this.lastPolledFromTaskQueue = queue;
|
|
+ return task;
|
|
+ }
|
|
+ }
|
|
+ if (hasJustClaimedMainThread) {
|
|
+ /*
|
|
+ We lost a race condition between the hasTasksThatCanStartNow checks and the poll call to a main-thread-only
|
|
+ task queue: let's just mark that the main thread can be claimed by another thread, and return that we
|
|
+ failed to poll a task.
|
|
+ */
|
|
+ MainThreadClaim.setMainThreadCanBeClaimed();
|
|
+ }
|
|
+ // We failed to poll any task
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Performs a dry run of {@link #pollTask}, returning whether a task would have been polled.
|
|
+ *
|
|
+ * @see #pollTask
|
|
+ */
|
|
+ @ThisBaseThreadOnly @YieldFree
|
|
+ protected boolean couldPollTask() {
|
|
+ boolean checkedIfCouldClaimMeanThread = false;
|
|
+ boolean couldClaimMainThread = false;
|
|
+ for (var queue : taskQueues) {
|
|
+ // Check if this thread could claim the main thread if needed for this queue
|
|
+ boolean alreadyVerifiedHasTasksThatCanStartNow = false;
|
|
+ if (queue.isMainThreadOnly()) {
|
|
+ if (!checkedIfCouldClaimMeanThread) {
|
|
+ // Don't attempt to check to claim the main thread if this queue is empty anyway
|
|
+ if (!queue.hasTasksThatCanStartNow(this)) {
|
|
+ continue;
|
|
+ }
|
|
+ alreadyVerifiedHasTasksThatCanStartNow = true;
|
|
+ if (MainThreadClaim.couldClaim(this)) {
|
|
+ couldClaimMainThread = true;
|
|
+ }
|
|
+ checkedIfCouldClaimMeanThread = true;
|
|
+ }
|
|
+ // Skip this queue if we could not claim the main thread
|
|
+ if (!couldClaimMainThread) {
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ if (alreadyVerifiedHasTasksThatCanStartNow || queue.hasTasksThatCanStartNow(this)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ // We failed to peek any task
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Signals this thread to wake up, or if it was not sleeping but attempting to poll a task:
|
|
+ * to not go to sleep the next time no task could be polled, and instead try polling a task again.
|
|
+ *
|
|
+ * @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 signal(@Nullable SignalReason reason) {
|
|
+ //noinspection StatementWithEmptyBody
|
|
+ while (!this.waitLock.tryLock());
|
|
+ try {
|
|
+ if (this.isWaiting) {
|
|
+ this.lastSignalReason = reason;
|
|
+ this.waitCondition.signal();
|
|
+ return true;
|
|
+ } else if (this.isPollingTask) {
|
|
+ this.lastSignalReason = reason;
|
|
+ this.skipNextWait = true;
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+ } finally {
|
|
+ this.waitLock.unlock();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Starts waiting on something to do.
|
|
+ *
|
|
+ * @param yieldingLock A {@link YieldingLock} to register with, or null if this thread is not waiting for
|
|
+ * a yielding lock.
|
|
+ * @param stopTime The maximum moment to wait until, or null if no specific time is known, in which case this
|
|
+ * thread will await being signalled without a timeout.
|
|
+ */
|
|
+ @ThisBaseThreadOnly @PotentiallyBlocking
|
|
+ private void await(@Nullable YieldingLock yieldingLock, @Nullable Long stopTime) {
|
|
+ // Register this thread with parties that may signal it
|
|
+ boolean registeredAsWaiting = false;
|
|
+ if (!this.skipNextWait) {
|
|
+ // No point in registering if we're not going to wait anyway
|
|
+ this.registerAsWaiting(yieldingLock);
|
|
+ registeredAsWaiting = true;
|
|
+ }
|
|
+ // If we cannot acquire the lock, this thread is being signalled, so there is no reason to start waiting.
|
|
+ if (this.waitLock.tryLock()) {
|
|
+ try {
|
|
+ if (!this.skipNextWait) {
|
|
+ if (!registeredAsWaiting) {
|
|
+ this.registerAsWaiting(yieldingLock);
|
|
+ registeredAsWaiting = true;
|
|
+ }
|
|
+ // Do a quick last check to not sleep if something changed since last checking
|
|
+ if (!this.couldPollTask()) {
|
|
+ // Wait
|
|
+ this.isWaiting = true;
|
|
+ try {
|
|
+ if (stopTime == null) {
|
|
+ this.waitCondition.await();
|
|
+ } else {
|
|
+ this.waitCondition.await(Math.max(MIN_WAIT_CONDITION_TIMEOUT_MICROS, Math.max((stopTime - System.nanoTime()) / 1000L, DEFAULT_WAIT_CONDITION_TIMEOUT_MICROS)), TimeUnit.MICROSECONDS);
|
|
+ }
|
|
+ } catch (InterruptedException e) {
|
|
+ throw new IllegalStateException(e);
|
|
+ }
|
|
+ this.isWaiting = false;
|
|
+ }
|
|
+ }
|
|
+ this.skipNextWait = false;
|
|
+ } finally {
|
|
+ this.waitLock.unlock();
|
|
+ }
|
|
+ }
|
|
+ // Unregister this thread from the parties it was registered with before
|
|
+ if (registeredAsWaiting) {
|
|
+ this.unregisterAsWaiting(yieldingLock);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * 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.
|
|
+ */
|
|
+ @ThisBaseThreadOnly @YieldFree
|
|
+ private void registerAsWaiting(@Nullable YieldingLock yieldingLock) {
|
|
+ // Register with the queues that we may poll tasks from
|
|
+ TaskWaitingBaseThreads.add(this);
|
|
+ // Register with the lock
|
|
+ if (yieldingLock != null) {
|
|
+ yieldingLock.addWaitingThread(this);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Unregisters this thread from the places it was registered with in {@link #registerAsWaiting}.
|
|
+ */
|
|
+ @ThisBaseThreadOnly @YieldFree
|
|
+ private void unregisterAsWaiting(@Nullable YieldingLock yieldingLock) {
|
|
+ // Unregister from the task queues
|
|
+ TaskWaitingBaseThreads.remove(this);
|
|
+ // Unregister from the lock
|
|
+ if (yieldingLock != null) {
|
|
+ yieldingLock.removeWaitingThread(this);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * A thread-local iteration index for iterating over the levels in
|
|
+ * {@link AllLevelsScheduledChunkCacheTaskQueue#poll}.
|
|
+ */
|
|
+ public int allLevelsChunkCacheTaskQueueLevelIterationIndex;
|
|
+
|
|
+ /**
|
|
+ * A thread-local iteration index for iterating over the levels in
|
|
+ * {@link AllLevelsScheduledMainThreadChunkTaskQueue#poll}.
|
|
+ */
|
|
+ public int allLevelsScheduledMainThreadChunkTaskQueueLevelIterationIndex;
|
|
+
|
|
+ /**
|
|
+ * @return The current thread if it is a {@link BaseThread}, or null otherwise.
|
|
+ */
|
|
+ @AnyThreadSafe @YieldFree
|
|
+ public static @Nullable BaseThread getCurrent() {
|
|
+ if (Thread.currentThread() instanceof BaseThread baseThread) {
|
|
+ return baseThread;
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @return Whether the current thread is a {@link BaseThread}.
|
|
+ */
|
|
+ @AnyThreadSafe @YieldFree
|
|
+ public static boolean isBaseThread() {
|
|
+ return Thread.currentThread() instanceof BaseThread;
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/galemc/gale/executor/thread/MainThreadClaim.java b/src/main/java/org/galemc/gale/executor/thread/MainThreadClaim.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..d424c53c3d627d1e5265081ff99dbca6b91f15f4
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/thread/MainThreadClaim.java
|
|
@@ -0,0 +1,250 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.thread;
|
|
+
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.galemc.gale.concurrent.Mutex;
|
|
+import org.galemc.gale.executor.MinecraftServerBlockableEventLoop;
|
|
+import org.galemc.gale.executor.annotation.Access;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.BaseThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.Guarded;
|
|
+import org.galemc.gale.executor.annotation.MainThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.OriginalServerThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.PotentiallyYielding;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.galemc.gale.executor.thread.wait.TaskWaitingBaseThreads;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+import java.util.function.BooleanSupplier;
|
|
+
|
|
+/**
|
|
+ * This class serves to remove the notion of 'main thread' from any specific thread, and instead make it
|
|
+ * a specific designation that any thread can claim.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+public final class MainThreadClaim {
|
|
+
|
|
+ private MainThreadClaim() {}
|
|
+
|
|
+ /**
|
|
+ * Whether the main thread can currently be claimed. This is set to false after a thread becomes the main thread
|
|
+ * and is still attempting to poll a main thread task, or when a main-thread-only task is active (including
|
|
+ * the code surrounding any task execution loop calls made by the {@link MinecraftServer#originalServerThread}).
|
|
+ */
|
|
+ @AnyThreadSafe(Access.READ) @MainThreadOnly(Access.WRITE)
|
|
+ @Guarded(value = "#lock", fieldAccess = Access.WRITE, except = "when only speculatively read")
|
|
+ public static volatile boolean canMainThreadBeClaimed = false;
|
|
+
|
|
+ /**
|
|
+ * Whether the {@link MinecraftServer#originalServerThread} is wanting to become the main thread so that it
|
|
+ * can exit a part of code during which secondary threads can become the main thread.
|
|
+ * To be precise, the main thread can be claimed by secondary threads during ticking the server (because
|
|
+ * the {@link MinecraftServer#originalServerThread} then starts doing the same task-execution loop as secondary
|
|
+ * threads, with the exception that it will break this loop when ticking finishes - and to break the loop it
|
|
+ * must be the main thread) and during a {@link MinecraftServerBlockableEventLoop#managedBlock(BooleanSupplier)}
|
|
+ * during spare time that follows ticking (because during that time, other threads should be able to become the main
|
|
+ * thread to make sure main-thread-only tasks get executed speedily when they become available while the
|
|
+ * {@link MinecraftServer#originalServerThread} is working on non-main-thread-only tasks).
|
|
+ * <br>
|
|
+ * This value is true until the {@link MinecraftServer#originalServerThread} starts allowing secondary threads
|
|
+ * to claim the main thread again.
|
|
+ */
|
|
+ @AnyThreadSafe(Access.READ) @OriginalServerThreadOnly(Access.WRITE)
|
|
+ @Guarded(value = "#lock", fieldAccess = Access.WRITE)
|
|
+ private static volatile boolean isOriginalServerThreadWantingToClaim = true;
|
|
+
|
|
+ /**
|
|
+ * The current main thread. Will be first initialized to {@link MinecraftServer#originalServerThread}.
|
|
+ */
|
|
+ @AnyThreadSafe(Access.READ)
|
|
+ @Guarded(value = "#lock", fieldAccess = Access.WRITE, except = "in initialize(), where acquiring the lock is unnecessary")
|
|
+ private static volatile Thread mainThread;
|
|
+
|
|
+ /**
|
|
+ * A lock for {@link #canMainThreadBeClaimed} and {@link #mainThread}, which are jointly
|
|
+ * checked or updated.
|
|
+ */
|
|
+ private static final Mutex lock = Mutex.create();
|
|
+
|
|
+ /**
|
|
+ * Sets the current main thread to {@link MinecraftServer#originalServerThread}.
|
|
+ */
|
|
+ @OriginalServerThreadOnly
|
|
+ public static void initialize() {
|
|
+ mainThread = MinecraftServer.SERVER.originalServerThread;;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @deprecated Use {@link #attemptToClaim(Thread, boolean)} instead.
|
|
+ */
|
|
+ @BaseThreadOnly @YieldFree
|
|
+ @Deprecated
|
|
+ public static boolean attemptToClaim(boolean setCanMainThreadBeClaimedToFalse) {
|
|
+ return attemptToClaim(Thread.currentThread(), setCanMainThreadBeClaimedToFalse);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Makes the current thread the main thread, if possible.
|
|
+ * This is possible if and only if {@link #canMainThreadBeClaimed} is true or the current thread
|
|
+ * is already the main thread.
|
|
+ * If successful, and {@code setCanMainThreadBeClaimedToFalse} is true,
|
|
+ * sets {@link #canMainThreadBeClaimed} to false.
|
|
+ *
|
|
+ * @return Whether the current thread is now the main thread.
|
|
+ */
|
|
+ @BaseThreadOnly @YieldFree
|
|
+ public static boolean attemptToClaim(Thread currentThread, boolean setCanMainThreadBeClaimedToFalse) {
|
|
+ // Fast pre-check to avoid acquiring the lock
|
|
+ if (!canMainThreadBeClaimed || (isOriginalServerThreadWantingToClaim && currentThread != MinecraftServer.SERVER.originalServerThread)) {
|
|
+ /*
|
|
+ If true, we are already the main thread and no-one can have claimed it by this point.
|
|
+ If false cannot become the main thread right now, maybe it just became possible during the last
|
|
+ line of code, but it's not worth acquiring the lock for.
|
|
+ */
|
|
+ if (mainThread == currentThread) {
|
|
+ if (!setCanMainThreadBeClaimedToFalse || !canMainThreadBeClaimed) {
|
|
+ // We can only return if we do not still need to set canMainThreadBeClaimed
|
|
+ return true;
|
|
+ }
|
|
+ } else {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ // Acquire the lock and make an attempt
|
|
+ //noinspection StatementWithEmptyBody
|
|
+ while (!lock.tryAcquire());
|
|
+ try {
|
|
+ if (!canMainThreadBeClaimed || (isOriginalServerThreadWantingToClaim && currentThread != MinecraftServer.SERVER.originalServerThread)) {
|
|
+ if (mainThread == currentThread) {
|
|
+ if (setCanMainThreadBeClaimedToFalse) {
|
|
+ canMainThreadBeClaimed = false;
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+ mainThread = currentThread;
|
|
+ if (setCanMainThreadBeClaimedToFalse) {
|
|
+ canMainThreadBeClaimed = false;
|
|
+ }
|
|
+ return true;
|
|
+ } finally {
|
|
+ lock.release();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @deprecated Use {@link #couldClaim(Thread)} instead.
|
|
+ */
|
|
+ @BaseThreadOnly @YieldFree
|
|
+ @Deprecated
|
|
+ public static boolean couldClaim() {
|
|
+ return couldClaim(Thread.currentThread());
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Performs a dry run of {@link #attemptToClaim(Thread, boolean)}, returning the same result but not actually
|
|
+ * claiming the main thread.
|
|
+ *
|
|
+ * @see #attemptToClaim(Thread, boolean)
|
|
+ */
|
|
+ @BaseThreadOnly @YieldFree
|
|
+ public static boolean couldClaim(Thread currentThread) {
|
|
+ // Fast pre-check to avoid acquiring the lock
|
|
+ if (!canMainThreadBeClaimed || (isOriginalServerThreadWantingToClaim && currentThread != MinecraftServer.SERVER.originalServerThread)) {
|
|
+ /*
|
|
+ If true, we are already the main thread and no-one can have claimed it by this point.
|
|
+ If false cannot become the main thread right now, maybe it just became possible during the last
|
|
+ line of code, but it's not worth acquiring the lock for.
|
|
+ */
|
|
+ return mainThread == currentThread;
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Sets {@link #canMainThreadBeClaimed} to true: must only be called from the current main thread.
|
|
+ */
|
|
+ @MainThreadOnly @YieldFree
|
|
+ public static void setMainThreadCanBeClaimed() {
|
|
+ //noinspection StatementWithEmptyBody
|
|
+ while (!lock.tryAcquire());
|
|
+ try {
|
|
+ canMainThreadBeClaimed = true;
|
|
+ } finally {
|
|
+ lock.release();
|
|
+ }
|
|
+ signalWaitingThreads();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @deprecated Use {@link #isCurrentThreadMainThreadAndNotClaimable(Thread)} instead.
|
|
+ */
|
|
+ @AnyThreadSafe @YieldFree
|
|
+ @Deprecated
|
|
+ public static boolean isCurrentThreadMainThreadAndNotClaimable() {
|
|
+ return isCurrentThreadMainThreadAndNotClaimable(Thread.currentThread());
|
|
+ }
|
|
+
|
|
+ @AnyThreadSafe @YieldFree
|
|
+ public static boolean isCurrentThreadMainThreadAndNotClaimable(Thread currentThread) {
|
|
+ return !canMainThreadBeClaimed && currentThread == mainThread;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Called by the {@link MinecraftServer#originalServerThread} to indicate that it requires being the main thread,
|
|
+ * and secondary threads can no longer claim the main thread from now on.
|
|
+ * <br>
|
|
+ * This method waits until the main thread can be claimed for the current thread, yielding to pending tasks
|
|
+ * until then.
|
|
+ */
|
|
+ @OriginalServerThreadOnly @PotentiallyYielding
|
|
+ public static void claimAsOriginalServerThread() {
|
|
+ //noinspection StatementWithEmptyBody
|
|
+ while (!lock.tryAcquire());
|
|
+ try {
|
|
+ isOriginalServerThreadWantingToClaim = true;
|
|
+ } finally {
|
|
+ lock.release();
|
|
+ }
|
|
+ MinecraftServer.SERVER.originalServerThread.yieldUntil(() -> attemptToClaim(MinecraftServer.SERVER.originalServerThread, true), null);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Called by the {@link MinecraftServer#originalServerThread} to indicate that secondary threads can
|
|
+ * claim the main thread from now on.
|
|
+ */
|
|
+ @OriginalServerThreadOnly @MainThreadOnly @YieldFree
|
|
+ public static void secondaryThreadsCanStartToClaim() {
|
|
+ //noinspection StatementWithEmptyBody
|
|
+ while (!lock.tryAcquire());
|
|
+ try {
|
|
+ isOriginalServerThreadWantingToClaim = false;
|
|
+ canMainThreadBeClaimed = true;
|
|
+ } finally {
|
|
+ lock.release();
|
|
+ }
|
|
+ signalWaitingThreads();
|
|
+ }
|
|
+
|
|
+ private static void signalWaitingThreads() {
|
|
+ for (var queue : BaseThread.mainThreadOnlyTaskQueues) {
|
|
+ if (queue.hasYieldingTasksThatCanStartNow()) {
|
|
+ @Nullable SignalReason yieldingSignalReason = queue.getYieldingSignalReason();
|
|
+ if (yieldingSignalReason != null && yieldingSignalReason.signalAnother()) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ if (queue.hasFreeTasksThatCanStartNow()) {
|
|
+ @Nullable SignalReason freeSignalReason = queue.getFreeSignalReason();
|
|
+ if (freeSignalReason != null && freeSignalReason.signalAnother()) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/galemc/gale/executor/thread/MainThreadDeferral.java b/src/main/java/org/galemc/gale/executor/thread/MainThreadDeferral.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..017a5ab8c2468cc59142be7c28a78622e6646dde
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/thread/MainThreadDeferral.java
|
|
@@ -0,0 +1,132 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.thread;
|
|
+
|
|
+import org.galemc.gale.concurrent.UnterminableExecutorService;
|
|
+import org.galemc.gale.executor.queue.BaseTaskQueues;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+import java.util.concurrent.CompletableFuture;
|
|
+import java.util.concurrent.Executor;
|
|
+import java.util.concurrent.ExecutorService;
|
|
+import java.util.function.Supplier;
|
|
+
|
|
+/**
|
|
+ * This class provides functionality to allow any thread, including a {@link BaseThread}, to defer blocks of code to the
|
|
+ * main thread, and wait for their 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 to be run on
|
|
+ * the main thread and wait for that to happen.
|
|
+ * <br>
|
|
+ * This has a number of advantages. Code that checks whether it is being run on the main thread can be run this
|
|
+ * way. Since these parts of code are always performed on the main thread 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 threads. Also, we can yield from a {@link BaseThread} until the code has been executed.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+public final class MainThreadDeferral {
|
|
+
|
|
+ private MainThreadDeferral() {}
|
|
+
|
|
+ /**
|
|
+ * @see #defer(Supplier, boolean)
|
|
+ */
|
|
+ public static void defer(Runnable task, boolean yielding) {
|
|
+ defer(task, null, yielding);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Runs the given {@code task} on the main thread, and yields until it has finished.
|
|
+ * If this thread is already the main thread, the task will be executed right away.
|
|
+ * <br>
|
|
+ * Like any potentially yielding method, while technically possible to call from any thread, this method should
|
|
+ * generally only be called from a {@link BaseThread}, because on any other thread, the thread will block until
|
|
+ * the given task has been completed by the main thread.
|
|
+ *
|
|
+ * @param task The task to run on the main thread.
|
|
+ * @param yielding Whether the given {@code task} is potentially yielding.
|
|
+ */
|
|
+ public static <T> T defer(Supplier<T> task, boolean yielding) {
|
|
+ return defer(null, task, yielding);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Common implementation for {@link #defer(Runnable, boolean)} and {@link #defer(Supplier, boolean)}.
|
|
+ * Exactly one of {@code runnable} or {@code supplier} must be non-null.
|
|
+ */
|
|
+ private static <T> T defer(@Nullable Runnable runnable, @Nullable Supplier<T> supplier, boolean yielding) {
|
|
+ // Become the main thread if possible
|
|
+ BaseThread baseThread = BaseThread.getCurrent();
|
|
+ if (baseThread != null && MainThreadClaim.attemptToClaim(baseThread, true)) {
|
|
+ // Run the task right away
|
|
+ baseThread.mainThreadOnlyTasksBeingExecutedCount.incrementAndGet();
|
|
+ T output;
|
|
+ if (supplier == null) {
|
|
+ runnable.run();
|
|
+ output = null;
|
|
+ } else {
|
|
+ output = supplier.get();
|
|
+ }
|
|
+ if (baseThread.mainThreadOnlyTasksBeingExecutedCount.decrementAndGet() == 0) {
|
|
+ // Make the main thread claimable again
|
|
+ MainThreadClaim.setMainThreadCanBeClaimed();
|
|
+ }
|
|
+ return output;
|
|
+ }
|
|
+ // Otherwise, schedule the task and wait for it to complete
|
|
+ CompletableFuture<T> future = new CompletableFuture<>();
|
|
+ if (baseThread != null) {
|
|
+ // Yield until the task completes
|
|
+ BaseTaskQueues.deferredToMainThread.add(() -> {
|
|
+ if (supplier == null) {
|
|
+ runnable.run();
|
|
+ future.complete(null);
|
|
+ } else {
|
|
+ future.complete(supplier.get());
|
|
+ }
|
|
+ baseThread.signal(null);
|
|
+ }, yielding);
|
|
+ baseThread.yieldUntil(future::isDone, null);
|
|
+ return future.getNow(null);
|
|
+ } else {
|
|
+ // Block until the task completes
|
|
+ BaseTaskQueues.deferredToMainThread.add(() -> {
|
|
+ if (supplier == null) {
|
|
+ runnable.run();
|
|
+ future.complete(null);
|
|
+ } else {
|
|
+ future.complete(supplier.get());
|
|
+ }
|
|
+ }, yielding);
|
|
+ return future.join();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * An executor for deferring potentially 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);
|
|
+ }
|
|
+
|
|
+ };
|
|
+
|
|
+ /**
|
|
+ * An executor for deferring yield-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);
|
|
+ }
|
|
+
|
|
+ };
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/galemc/gale/executor/thread/SecondaryThread.java b/src/main/java/org/galemc/gale/executor/thread/SecondaryThread.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..14ce3eb68983b7d53062e16bfa0da352562364a6
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/thread/SecondaryThread.java
|
|
@@ -0,0 +1,42 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.thread;
|
|
+
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.galemc.gale.executor.annotation.BaseThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.ThisBaseThreadOnly;
|
|
+
|
|
+/**
|
|
+ * A secondary thread to perform tasks in parallel to the {@link MinecraftServer#originalServerThread}.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+public class SecondaryThread extends BaseThread {
|
|
+
|
|
+ public final int secondaryThreadIndex;
|
|
+
|
|
+ public SecondaryThread(int secondaryThreadIndex) {
|
|
+ super(SecondaryThread::getInstanceAndRunForever, "Secondary Thread " + secondaryThreadIndex, secondaryThreadIndex + 1);
|
|
+ this.secondaryThreadIndex = secondaryThreadIndex;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * This is the main thread loop for a {@link SecondaryThread}.
|
|
+ * It will 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.
|
|
+ */
|
|
+ @ThisBaseThreadOnly
|
|
+ private void runForever() {
|
|
+ this.runTasksUntil(() -> false, null, null);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * A method that simply acquires the {@link SecondaryThread} that is the current thread, and calls
|
|
+ * {@link #runForever()} on it.
|
|
+ */
|
|
+ @BaseThreadOnly
|
|
+ private static void getInstanceAndRunForever() {
|
|
+ ((SecondaryThread) Thread.currentThread()).runForever();
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/galemc/gale/executor/thread/SecondaryThreadPool.java b/src/main/java/org/galemc/gale/executor/thread/SecondaryThreadPool.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..312c57bd7d4ea627e39b797c782be3f893564b22
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/thread/SecondaryThreadPool.java
|
|
@@ -0,0 +1,225 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.thread;
|
|
+
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.galemc.gale.concurrent.Mutex;
|
|
+import org.galemc.gale.executor.annotation.Access;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.BaseThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.Guarded;
|
|
+import org.galemc.gale.executor.annotation.OriginalServerThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.wait.SignalReason;
|
|
+import org.galemc.gale.util.CPUCoresEstimation;
|
|
+
|
|
+import java.util.Arrays;
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
+
|
|
+/**
|
|
+ * A pool of threads that, in addition to the main thread, all non-blocking tasks can be performed on.
|
|
+ * This pool manages the amount of threads necessary, and activates and de-activates surplus threads to avoid
|
|
+ * context switches. Threads created for this pool are instances of {@link SecondaryThread}.
|
|
+ * <br>
|
|
+ * This pool intends to keep {@link #intendedSecondaryParallelism} + 1 threads active at any time, which is initially
|
|
+ * the {@link MinecraftServer#originalServerThread} and {@link #intendedSecondaryParallelism} secondary threads.
|
|
+ * It allows more secondary threads to become active if an existing active thread is unable to accept more tasks
|
|
+ * because it has become restricted. A {@link BaseThread} is restricted when it can not accept new tasks. This happens
|
|
+ * when it can only accept yield-free tasks, but there are still pending potentially yielding tasks, which means we
|
|
+ * prefer other threads to become active and start those potentially yielding tasks. A thread can only accept yield-free
|
|
+ * tasks when it has reached its maximum yield depth or is attempting to yield from a main-thread-only task.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+public final class SecondaryThreadPool {
|
|
+
|
|
+ private SecondaryThreadPool() {}
|
|
+
|
|
+ /**
|
|
+ * Whether {@link SecondaryThread}s can poll only tasks that can run on any thread.
|
|
+ * <br>
|
|
+ * This is true when {@link #intendedSecondaryParallelism} is first determined to have to be 0. In that case,
|
|
+ * it is set to 1 instead, so that we at least have one secondary thread to perform asynchronous tasks that
|
|
+ * may take so long they prevent a tick from finishing.
|
|
+ */
|
|
+ public static final boolean canOnlyPollAsyncTasksOnSecondaryThreads;
|
|
+
|
|
+ /**
|
|
+ * The minimum number of threads that will be actively in use by this pool.
|
|
+ * <br>
|
|
+ * By default, we always do not use one core, so that there is always a core available for the main thread, or
|
|
+ * to run other important threads such as Netty, I/O or garbage collection on.
|
|
+ * This also means that if the system has 1 core only, we do not use any secondary threads by default.
|
|
+ * <br>
|
|
+ * This value is never negative.
|
|
+ * <br>
|
|
+ * The value is currently automatically determined according to the following table:
|
|
+ * <table>
|
|
+ * <tr><th>system cores</th><th>cores spared</th></tr>
|
|
+ * <tr><td>≤ 3</td><td>1</td></tr>
|
|
+ * <tr><td>[4, 14]</td><td>2</td></tr>
|
|
+ * <tr><td>[15, 23]</td><td>3</td></tr>
|
|
+ * <tr><td>[24, 37]</td><td>4</td></tr>
|
|
+ * <tr><td>[38, 54]</td><td>5</td></tr>
|
|
+ * <tr><td>[55, 74]</td><td>6</td></tr>
|
|
+ * <tr><td>[75, 99]</td><td>7</td></tr>
|
|
+ * <tr><td>[100, 127]</td><td>8</td></tr>
|
|
+ * <tr><td>[128, 158]</td><td>9</td></tr>
|
|
+ * <tr><td>[159, 193]</td><td>10</td></tr>
|
|
+ * <tr><td>[194, 232]</td><td>11</td></tr>
|
|
+ * <tr><td>[233, 274]</td><td>12</td></tr>
|
|
+ * <tr><td>≥ 275</td><td>13</td></tr>
|
|
+ * </table>
|
|
+ * Then <code>minimum number of secondary threads = system cores - cores spared</code>.
|
|
+ */
|
|
+ public static final int intendedSecondaryParallelism;
|
|
+ static {
|
|
+ int parallelismByEnvironmentVariable = Integer.getInteger("gale.threads.secondary", -1);
|
|
+ int intendedSecondaryParallelismBeforeSetAtLeastOne;
|
|
+ if (parallelismByEnvironmentVariable >= 0) {
|
|
+ intendedSecondaryParallelismBeforeSetAtLeastOne = parallelismByEnvironmentVariable;
|
|
+ } else {
|
|
+ int systemCores = CPUCoresEstimation.get();
|
|
+ int coresSpared;
|
|
+ if (systemCores <= 3) {
|
|
+ coresSpared = 1;
|
|
+ } else if (systemCores <= 14) {
|
|
+ coresSpared = 2;
|
|
+ } else if (systemCores <= 23) {
|
|
+ coresSpared = 3;
|
|
+ } else if (systemCores <= 37) {
|
|
+ coresSpared = 4;
|
|
+ } else if (systemCores <= 54) {
|
|
+ coresSpared = 5;
|
|
+ } else if (systemCores <= 74) {
|
|
+ coresSpared = 6;
|
|
+ } else if (systemCores <= 99) {
|
|
+ coresSpared = 7;
|
|
+ } else if (systemCores <= 127) {
|
|
+ coresSpared = 8;
|
|
+ } else if (systemCores <= 158) {
|
|
+ coresSpared = 9;
|
|
+ } else if (systemCores <= 193) {
|
|
+ coresSpared = 10;
|
|
+ } else if (systemCores <= 232) {
|
|
+ coresSpared = 11;
|
|
+ } else if (systemCores <= 274) {
|
|
+ coresSpared = 12;
|
|
+ } else {
|
|
+ coresSpared = 13;
|
|
+ }
|
|
+ intendedSecondaryParallelismBeforeSetAtLeastOne = systemCores - coresSpared;
|
|
+ }
|
|
+ if (intendedSecondaryParallelismBeforeSetAtLeastOne >= 1) {
|
|
+ intendedSecondaryParallelism = intendedSecondaryParallelismBeforeSetAtLeastOne;
|
|
+ canOnlyPollAsyncTasksOnSecondaryThreads = false;
|
|
+ } else {
|
|
+ intendedSecondaryParallelism = 1;
|
|
+ canOnlyPollAsyncTasksOnSecondaryThreads = true;
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * The base threads, which is an array of the {@link MinecraftServer#originalServerThread} at index 0, and
|
|
+ * {@link #secondaryThreads} afterwards. In other words, it is an array of every {@link BaseThread} among
|
|
+ * {@link MinecraftServer#originalServerThread} and {@link #secondaryThreads}, indexed by their
|
|
+ * {@link BaseThread#baseThreadIndex}.
|
|
+ */
|
|
+ @AnyThreadSafe(Access.READ) @BaseThreadOnly(Access.WRITE)
|
|
+ @Guarded(value = "#threadsLock", fieldAccess = Access.WRITE, except = "during static initialization, where acquiring the lock is unnecessary")
|
|
+ private static BaseThread[] baseThreads = new BaseThread[intendedSecondaryParallelism + 1];
|
|
+
|
|
+ @OriginalServerThreadOnly @YieldFree
|
|
+ public static void setOriginalServerThreadInBaseThread() {
|
|
+ baseThreads[0] = MinecraftServer.SERVER.originalServerThread;
|
|
+ }
|
|
+
|
|
+ @AnyThreadSafe @YieldFree
|
|
+ public static BaseThread getBaseThreadByIndex(int baseThreadIndex) {
|
|
+ return baseThreads[baseThreadIndex];
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * The secondary threads. It is an array of every {@link SecondaryThread}, indexed by their
|
|
+ * {@link SecondaryThread#secondaryThreadIndex}.
|
|
+ */
|
|
+ @AnyThreadSafe(Access.READ) @BaseThreadOnly(Access.WRITE)
|
|
+ @Guarded(value = "#threadsLock", fieldAccess = Access.WRITE, except = "during static initialization, where acquiring the lock is unnecessary")
|
|
+ private static SecondaryThread[] secondaryThreads = new SecondaryThread[intendedSecondaryParallelism];
|
|
+
|
|
+ static {
|
|
+ for (int i = 0; i < secondaryThreads.length; i++) {
|
|
+ baseThreads[i + 1] = secondaryThreads[i] = new SecondaryThread(i);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @AnyThreadSafe @YieldFree
|
|
+ public static SecondaryThread getSecondaryThreadByIndex(int secondaryThreadIndex) {
|
|
+ return secondaryThreads[secondaryThreadIndex];
|
|
+ }
|
|
+
|
|
+ private static final Mutex threadsLock = Mutex.create();
|
|
+
|
|
+ /**
|
|
+ * The number of base threads that are currently restricted.
|
|
+ */
|
|
+ @AnyThreadSafe(Access.READ) @BaseThreadOnly(Access.WRITE)
|
|
+ public static final AtomicInteger restrictedBaseThreadCount = new AtomicInteger();
|
|
+
|
|
+ private static final SignalReason surplusThreadActivationSignalReason = SignalReason.createNonRetrying();
|
|
+
|
|
+ /**
|
|
+ * Starts all secondary threads in this pool.
|
|
+ */
|
|
+ @OriginalServerThreadOnly @YieldFree
|
|
+ public static void startSecondaryThreads() {
|
|
+ //noinspection StatementWithEmptyBody
|
|
+ while (!threadsLock.tryAcquire());
|
|
+ try {
|
|
+ for (SecondaryThread secondaryThread : secondaryThreads) {
|
|
+ secondaryThread.start();
|
|
+ }
|
|
+ } finally {
|
|
+ threadsLock.release();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Called by a {@link BaseThread} when it becomes restricted.
|
|
+ */
|
|
+ @BaseThreadOnly @YieldFree
|
|
+ public static void threadBecameRestricted() {
|
|
+ int newSecondaryThreadCount = restrictedBaseThreadCount.incrementAndGet() + intendedSecondaryParallelism;
|
|
+ if (newSecondaryThreadCount > secondaryThreads.length) {
|
|
+ //noinspection StatementWithEmptyBody
|
|
+ while (!threadsLock.tryAcquire());
|
|
+ try {
|
|
+ // Check again to make sure secondaryThreads hasn't already been extended since the last check
|
|
+ int oldSecondaryThreadCount = secondaryThreads.length;
|
|
+ if (newSecondaryThreadCount > oldSecondaryThreadCount) {
|
|
+ // Add surplus threads
|
|
+ secondaryThreads = Arrays.copyOf(secondaryThreads, newSecondaryThreadCount);
|
|
+ baseThreads = Arrays.copyOf(baseThreads, newSecondaryThreadCount + 1);
|
|
+ for (int i = oldSecondaryThreadCount; i < newSecondaryThreadCount; i++) {
|
|
+ baseThreads[i + 1] = secondaryThreads[i] = new SecondaryThread(i);
|
|
+ secondaryThreads[i].start();
|
|
+ }
|
|
+ }
|
|
+ } finally {
|
|
+ threadsLock.release();
|
|
+ }
|
|
+ }
|
|
+ // Wake up the appropriate surplus thread if it was sleeping
|
|
+ secondaryThreads[newSecondaryThreadCount - 1].signal(surplusThreadActivationSignalReason);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Called by a {@link BaseThread} when it becomes no longer restricted.
|
|
+ */
|
|
+ @BaseThreadOnly @YieldFree
|
|
+ public static void threadIsNoLongerRestricted() {
|
|
+ restrictedBaseThreadCount.decrementAndGet();
|
|
+ }
|
|
+
|
|
+}
|
|
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..1c56597ccce2f11db1c8a903e24c07eeb2143564
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/thread/wait/SignalReason.java
|
|
@@ -0,0 +1,43 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.thread.wait;
|
|
+
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+
|
|
+/**
|
|
+ * An interface to indicate the reason of a call to {@link BaseThread#signal}.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+public interface SignalReason {
|
|
+
|
|
+ /**
|
|
+ * Signals another {@link BaseThread} that is waiting for the same reason.
|
|
+ *
|
|
+ * @return Whether any thread was signalled.
|
|
+ */
|
|
+ boolean signalAnother();
|
|
+
|
|
+ @AnyThreadSafe @YieldFree
|
|
+ static SignalReason createForTaskQueue(boolean mainThreadOnly, boolean baseThreadOnly, boolean yielding) {
|
|
+ if (mainThreadOnly && !baseThreadOnly) {
|
|
+ throw new IllegalArgumentException();
|
|
+ }
|
|
+ return new SignalReason() {
|
|
+
|
|
+ @Override
|
|
+ public boolean signalAnother() {
|
|
+ return TaskWaitingBaseThreads.signal(this, mainThreadOnly, baseThreadOnly, yielding);
|
|
+ }
|
|
+
|
|
+ };
|
|
+ }
|
|
+
|
|
+ @AnyThreadSafe @YieldFree
|
|
+ static SignalReason createNonRetrying() {
|
|
+ return () -> false;
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/galemc/gale/executor/thread/wait/TaskWaitingBaseThreads.java b/src/main/java/org/galemc/gale/executor/thread/wait/TaskWaitingBaseThreads.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..2623dea4cb52c16d43749538a5c0254e1ea92667
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/thread/wait/TaskWaitingBaseThreads.java
|
|
@@ -0,0 +1,124 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.thread.wait;
|
|
+
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.BaseThreadOnly;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.MainThreadClaim;
|
|
+import org.galemc.gale.executor.thread.SecondaryThreadPool;
|
|
+
|
|
+/**
|
|
+ * This class keeps track of all the {@link BaseThread}s that are waiting for tasks to be added to a task queue.
|
|
+ *
|
|
+ * @author Martijn Muijsers under AGPL-3.0
|
|
+ */
|
|
+public final class TaskWaitingBaseThreads {
|
|
+
|
|
+ private TaskWaitingBaseThreads() {}
|
|
+
|
|
+ /**
|
|
+ * The base threads waiting for potentially yielding {@link BaseThread}-only tasks to be added.
|
|
+ */
|
|
+ private static final WaitingBaseThreadSet forBaseThreadYieldingTasks = new WaitingBaseThreadSet();
|
|
+
|
|
+ /**
|
|
+ * The base threads waiting for yield-free {@link BaseThread}-only tasks to be added.
|
|
+ */
|
|
+ private static final WaitingBaseThreadSet forBaseThreadFreeTasks = new WaitingBaseThreadSet() {
|
|
+
|
|
+ @Override
|
|
+ public boolean pollAndSignal(SignalReason reason) {
|
|
+ if (super.pollAndSignal(reason)) {
|
|
+ return true;
|
|
+ }
|
|
+ /*
|
|
+ Waiting for BaseThread-only potentially yielding tasks implies waiting for BaseThread-only
|
|
+ yield-free tasks, so we signal thread waiting for potentially yielding tasks too.
|
|
+ */
|
|
+ return forBaseThreadYieldingTasks.pollAndSignal(reason);
|
|
+ }
|
|
+
|
|
+ };
|
|
+
|
|
+ /**
|
|
+ * The base threads waiting for potentially yielding any-thread tasks to be added.
|
|
+ */
|
|
+ private static final WaitingBaseThreadSet forAnyThreadYieldingTasks = new WaitingBaseThreadSet();
|
|
+
|
|
+ /**
|
|
+ * The base threads waiting for yield-free any-thread tasks to be added.
|
|
+ */
|
|
+ private static final WaitingBaseThreadSet forAnyThreadFreeTasks = new WaitingBaseThreadSet() {
|
|
+
|
|
+ @Override
|
|
+ public boolean pollAndSignal(SignalReason reason) {
|
|
+ if (super.pollAndSignal(reason)) {
|
|
+ return true;
|
|
+ }
|
|
+ /*
|
|
+ Waiting for any-thread potentially yielding tasks implies waiting for any-thread
|
|
+ yield-free tasks, so we signal thread waiting for potentially yielding tasks too.
|
|
+ */
|
|
+ return forAnyThreadYieldingTasks.pollAndSignal(reason);
|
|
+ }
|
|
+
|
|
+ };
|
|
+
|
|
+ /**
|
|
+ * Signal a thread due to a new task being added.
|
|
+ *
|
|
+ * @param reason The {@link SignalReason} that the signal will originate from.
|
|
+ * @param mainThreadOnly Whether the added task is main-thread-only.
|
|
+ * @param baseThreadOnly Whether the added task is {@link BaseThread}-only.
|
|
+ * @param yielding Whether the added task is potentially yielding.
|
|
+ * @return Whether a thread was signalled (this return value is always accurate).
|
|
+ */
|
|
+ @AnyThreadSafe @YieldFree
|
|
+ public static boolean signal(SignalReason reason, boolean mainThreadOnly, boolean baseThreadOnly, boolean yielding) {
|
|
+ if (mainThreadOnly && !MainThreadClaim.canMainThreadBeClaimed) {
|
|
+ return false;
|
|
+ }
|
|
+ return (baseThreadOnly ? (yielding ? forBaseThreadYieldingTasks : forBaseThreadFreeTasks) : (yielding ? forAnyThreadYieldingTasks : forAnyThreadFreeTasks)).pollAndSignal(reason);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Adds a thread to the sets of threads waiting for new tasks to be added to task queues.
|
|
+ *
|
|
+ * @param currentThread The current thread, as well as the thread to register as waiting for new tasks.
|
|
+ */
|
|
+ @BaseThreadOnly @YieldFree
|
|
+ public static void add(BaseThread currentThread) {
|
|
+ boolean isOriginalServerThread = MinecraftServer.SERVER == null || currentThread == MinecraftServer.SERVER.originalServerThread;
|
|
+ // If the thread can accept tasks that must run on a BaseThread, wait for those
|
|
+ if (isOriginalServerThread || !SecondaryThreadPool.canOnlyPollAsyncTasksOnSecondaryThreads) {
|
|
+ if (!currentThread.isRestricted) {
|
|
+ forBaseThreadYieldingTasks.add(currentThread);
|
|
+ }
|
|
+ forBaseThreadFreeTasks.add(currentThread);
|
|
+ }
|
|
+ // If the thread can accept tasks that can run on any thread, wait for those
|
|
+ if (!isOriginalServerThread || MinecraftServer.canPollAsyncTasksOnOriginalServerThread) {
|
|
+ if (!currentThread.isRestricted) {
|
|
+ forAnyThreadYieldingTasks.add(currentThread);
|
|
+ }
|
|
+ forAnyThreadFreeTasks.add(currentThread);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Removes a thread from the sets of threads waiting for new tasks.
|
|
+ *
|
|
+ * @param currentThread The current thread, as well as the thread to unregister as waiting for new tasks.
|
|
+ */
|
|
+ @BaseThreadOnly @YieldFree
|
|
+ public static void remove(BaseThread currentThread) {
|
|
+ forBaseThreadYieldingTasks.remove(currentThread);
|
|
+ forBaseThreadFreeTasks.remove(currentThread);
|
|
+ forAnyThreadYieldingTasks.remove(currentThread);
|
|
+ forAnyThreadFreeTasks.remove(currentThread);
|
|
+ }
|
|
+
|
|
+}
|
|
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..b06c3a0e299e2907547510f88ee00742b9bd8150
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/galemc/gale/executor/thread/wait/WaitingBaseThreadSet.java
|
|
@@ -0,0 +1,75 @@
|
|
+// Gale - base thread pool
|
|
+
|
|
+package org.galemc.gale.executor.thread.wait;
|
|
+
|
|
+import org.galemc.gale.executor.annotation.AnyThreadSafe;
|
|
+import org.galemc.gale.executor.annotation.YieldFree;
|
|
+import org.galemc.gale.executor.lock.YieldingLock;
|
|
+import org.galemc.gale.executor.thread.BaseThread;
|
|
+import org.galemc.gale.executor.thread.SecondaryThreadPool;
|
|
+
|
|
+import java.util.concurrent.ConcurrentSkipListSet;
|
|
+
|
|
+/**
|
|
+ * A set of waiting {@link BaseThread}s. 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 class WaitingBaseThreadSet implements SignalReason {
|
|
+
|
|
+ /**
|
|
+ * A set of the {@link BaseThread#baseThreadIndex} of the threads in this collection.
|
|
+ */
|
|
+ private final ConcurrentSkipListSet<Integer> skipListSet = new ConcurrentSkipListSet<>();
|
|
+
|
|
+ /**
|
|
+ * Adds a waiting {@link BaseThread}.
|
|
+ */
|
|
+ public void add(BaseThread thread) {
|
|
+ this.skipListSet.add(thread.baseThreadIndex);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Removes a waiting {@link BaseThread}.
|
|
+ */
|
|
+ public void remove(BaseThread thread) {
|
|
+ this.skipListSet.add(thread.baseThreadIndex);
|
|
+ }
|
|
+
|
|
+ public boolean pollAndSignal() {
|
|
+ return this.pollAndSignal(this);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Attempts to signal one waiting {@link BaseThread}.
|
|
+ * <br>
|
|
+ * 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.
|
|
+ * <br>
|
|
+ * 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 BaseThread#signal
|
|
+ */
|
|
+ @AnyThreadSafe @YieldFree
|
|
+ public boolean pollAndSignal(SignalReason reason) {
|
|
+ Integer baseThreadIndex = this.skipListSet.pollFirst();
|
|
+ if (baseThreadIndex != null) {
|
|
+ SecondaryThreadPool.getBaseThreadByIndex(baseThreadIndex).signal(reason);
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean signalAnother() {
|
|
+ return this.pollAndSignal();
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/spigotmc/SpigotCommand.java b/src/main/java/org/spigotmc/SpigotCommand.java
|
|
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 {
|
|
|
|
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 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..970908ba3eafbc79f01c0681e214dfdb95658ec6 100644
|
|
--- a/src/main/java/org/spigotmc/WatchdogThread.java
|
|
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
|
|
@@ -13,7 +13,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
|
|
{
|
|
|
|
public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper
|
|
- private static WatchdogThread instance;
|
|
+ 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
|
|
@@ -206,7 +206,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
|
|
log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Gale!):" ); // Paper // Gale - branding changes
|
|
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.getServer().originalServerThread.getId(), Integer.MAX_VALUE ), log ); // Gale - base thread pool
|
|
log.log( Level.SEVERE, "------------------------------" );
|
|
//
|
|
// Paper start - Only print full dump on long timeouts
|