From c52fdc2744f9c96920ce91c1a48415e0adf87acd Mon Sep 17 00:00:00 2001 From: Martijn Muijsers Date: Thu, 22 Dec 2022 15:31:03 +0100 Subject: [PATCH] Do not wait beyond timeout during oversleep --- patches/server/0115-Base-thread-pools.patch | 273 +++++++++++++++----- 1 file changed, 203 insertions(+), 70 deletions(-) diff --git a/patches/server/0115-Base-thread-pools.patch b/patches/server/0115-Base-thread-pools.patch index 333e57e..8bac3ad 100644 --- a/patches/server/0115-Base-thread-pools.patch +++ b/patches/server/0115-Base-thread-pools.patch @@ -485,7 +485,7 @@ index 0c4c62674b4c7e8e3921c7eb3ef726759ac75075..31b488a3c1b81b99bf5bda9f90c3cf69 WorldLoader.InitConfig worldloader_c = Main.loadOrCreateConfig(dedicatedserversettings.getProperties(), convertable_conversionsession, flag, resourcepackrepository); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 609d57dd7373531d35295152e543261e3b0fbe09..79c56e1aeb12ecaed2a17e01a4f9ec905b936b9a 100644 +index 609d57dd7373531d35295152e543261e3b0fbe09..ea3516cbc28fac0620bcb71d3872c50d197362f3 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -40,7 +40,6 @@ import java.util.Optional; @@ -552,7 +552,7 @@ index 609d57dd7373531d35295152e543261e3b0fbe09..79c56e1aeb12ecaed2a17e01a4f9ec90 private PlayerList playerList; private volatile boolean running; private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart -@@ -262,10 +280,66 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= nextTickTime check will be true after this moment ++ Add 10000 nanoseconds, to make sure the currentTime() >= nextTickTime check will be true after this moment + regardless of the nanosecond granularity of the Condition#await function, which is probably somewhere around -+ 26 ns. ++ 26 nanoseconds. + */ -+ nextTickStartNanoTime = 1_000_000L * nextTickTime + 1000L; ++ nextTickStartNanoTime = 1_000_000L * this.nextTickTime + 10_000L; ++ } ++ ++ /** ++ * Sets {@link #delayedTasksMaxNextTickTime}, and sets {@link #delayedTasksMaxNextTickNanoTime} accordingly. ++ * ++ * @see #setNextTickTime ++ */ ++ private void setDelayedTasksMaxNextTickTime(long delayedTasksMaxNextTickTime) { ++ this.delayedTasksMaxNextTickTime = delayedTasksMaxNextTickTime; ++ delayedTasksMaxNextTickNanoTime = 1_000_000L * this.delayedTasksMaxNextTickTime + 10_000L; + } + + /** @@ -612,18 +623,56 @@ index 609d57dd7373531d35295152e543261e3b0fbe09..79c56e1aeb12ecaed2a17e01a4f9ec90 + public static volatile boolean isInSpareTime = false; + + /** ++ * Whether the server is currently waiting for the next tick, which is one of the cases where ++ * {@link #isInSpareTime} is true. Specifically, the other case where {@link #isInSpareTime} is true is ++ * while {@link #isOversleep} is true. ++ */ ++ public static volatile boolean isWaitingUntilNextTick = 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; + ++ /** ++ * The stop condition provided to the current call of {@link #managedBlock}, or null if no {@link #managedBlock} ++ * call is ongoing. ++ */ ++ public static volatile @Nullable BooleanSupplier currentManagedBlockStopCondition; ++ ++ /** ++ * Whether the {@link #currentManagedBlockStopCondition} has become true ++ * during the last {@link #managedBlock} call. ++ */ ++ public static volatile boolean currentManagedBlockStopConditionHasBecomeTrue = false; ++ ++ public static final SignalReason managedBlockStopConditionBecameTrueSignalReason = SignalReason.createNonRetrying(); ++ ++ public static void signalServerThreadIfCurrentManagedBlockStopConditionBecameTrue() { ++ if (currentManagedBlockStopConditionHasBecomeTrue) { ++ // We already signalled the thread ++ return; ++ } ++ if (currentManagedBlockStopCondition == null) { ++ // There is no ongoing managedBlock cal ++ return; ++ } ++ if (!currentManagedBlockStopCondition.getAsBoolean()) { ++ // The stop condition is not true ++ return; ++ } ++ currentManagedBlockStopConditionHasBecomeTrue = true; ++ serverThread.signal(managedBlockStopConditionBecameTrueSignalReason); ++ } ++ + // Gale start - base thread pools + private final PackRepository packRepository; private final ServerScoreboard scoreboard; @Nullable -@@ -294,7 +368,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); public int autosavePeriod; public Commands vanillaCommandDispatcher; @@ -632,7 +681,7 @@ index 609d57dd7373531d35295152e543261e3b0fbe09..79c56e1aeb12ecaed2a17e01a4f9ec90 // CraftBukkit end // Spigot start public static final int TPS = 20; -@@ -310,9 +384,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -@@ -347,7 +424,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop holdergetter = this.registries.compositeAccess().registryOrThrow(Registries.BLOCK).asLookup().filterFeatures(this.worldData.enabledFeatures()); this.structureTemplateManager = new StructureTemplateManager(worldstem.resourceManager(), convertable_conversionsession, datafixer, holdergetter); @@ -680,7 +729,7 @@ index 609d57dd7373531d35295152e543261e3b0fbe09..79c56e1aeb12ecaed2a17e01a4f9ec90 this.executor = Util.backgroundExecutor(); } // CraftBukkit start -@@ -613,7 +693,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop public private boolean canOversleep() { - return this.mayHaveDelayedTasks && Util.getMillis() < this.delayedTasksMaxNextTickTime; + return Util.getMillis() < this.delayedTasksMaxNextTickTime && mayHaveDelayedTasks(); // Gale - base thread pools } private boolean canSleepForTickNoOversleep() { -@@ -1295,7 +1412,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { return !this.canSleepForTickNoOversleep(); // Paper - move oversleep into full server tick + // Gale start - base thread pools }); + isInSpareTime = false; ++ isWaitingUntilNextTick = false; + // Gale end - base thread pools lastTickOversleepTime = (System.nanoTime() - tickOversleepStart) / 1000000L; // Gale - YAPFA - last tick time } @@ -981,7 +1048,7 @@ index 609d57dd7373531d35295152e543261e3b0fbe09..79c56e1aeb12ecaed2a17e01a4f9ec90 private void updateStatusIcon(ServerStatus metadata) { Optional optional = Optional.of(this.getFile("server-icon.png")).filter(File::isFile); -@@ -1406,14 +1477,19 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= 5000000000L) { this.lastServerStatus = i; -@@ -1449,7 +1525,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { this.playerList.saveAll(playerSaveInterval); } @@ -1010,7 +1077,7 @@ index 609d57dd7373531d35295152e543261e3b0fbe09..79c56e1aeb12ecaed2a17e01a4f9ec90 if (level.paperConfig().chunks.autoSaveInterval.value() > 0) { level.saveIncrementally(fullSave); } -@@ -1462,7 +1538,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper -@@ -1616,7 +1690,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); newLevels.put(level.dimension(), level); this.levels = Collections.unmodifiableMap(newLevels); @@ -1076,7 +1143,7 @@ index 609d57dd7373531d35295152e543261e3b0fbe09..79c56e1aeb12ecaed2a17e01a4f9ec90 } public void removeLevel(ServerLevel level) { -@@ -1645,6 +1731,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); newLevels.remove(level.dimension()); this.levels = Collections.unmodifiableMap(newLevels); @@ -1091,7 +1158,7 @@ index 609d57dd7373531d35295152e543261e3b0fbe09..79c56e1aeb12ecaed2a17e01a4f9ec90 } // CraftBukkit end -@@ -1652,8 +1746,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { ++ if (this == MinecraftServer.serverThread) { ++ // -1 indicates to not use a timeout (this value is not later set to any other negative value) ++ long waitForNanos = -1; ++ if (MinecraftServer.isWaitingUntilNextTick) { ++ /* ++ During waiting until the next tick, we wait until the next tick start. ++ If it already passed, we do not have to use a timeout, because we will be notified ++ when the stop condition becomes true. ++ */ ++ waitForNanos = MinecraftServer.nextTickStartNanoTime - System.nanoTime(); ++ if (waitForNanos < 0) { ++ waitForNanos = -1; ++ } ++ } else if (MinecraftServer.SERVER.isOversleep) { ++ /* ++ During this phase, MinecraftServer#mayHaveDelayedTasks() is checked, and we may not ++ be notified when it changes. Therefore, if the next tick start has not passed, we will ++ wait until then, but if it has, we wait for a short interval to make sure we keep ++ checking the stop condition (but not for longer than until the last time we can be ++ executing extra delayed tasks). ++ */ ++ waitForNanos = MinecraftServer.nextTickStartNanoTime - System.nanoTime(); ++ if (waitForNanos < 0) { ++ waitForNanos = Math.min(Math.max(0, MinecraftServer.delayedTasksMaxNextTickNanoTime - System.nanoTime()), SERVER_THREAD_WAIT_NANOS_DURING_OVERSLEEP_WITH_DELAYED_TASKS); ++ } ++ } ++ if (waitForNanos >= 0) { + // Set the last signal reason to null in case the timeout elapses without a signal + this.lastSignalReason = null; -+ // Wait, but at most until the start of the next tick ++ // Wait, but at most for the determined time + waitedWithTimeout = true; -+ this.preBlockThread(); -+ //noinspection ResultOfMethodCallIgnored -+ this.waitCondition.await(nanosUntilNextTickStart, TimeUnit.NANOSECONDS); -+ this.postBlockThread(); ++ // Skip if the time is too short ++ if (waitForNanos >= SERVER_THREAD_WAIT_NANOS_MINIMUM) { ++ this.preBlockThread(); ++ //noinspection ResultOfMethodCallIgnored ++ this.waitCondition.await(waitForNanos, TimeUnit.NANOSECONDS); ++ this.postBlockThread(); ++ } + } + } +