From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf <spottedleaf@spottedleaf.dev> Date: Mon, 6 Apr 2020 04:20:44 -0700 Subject: [PATCH] Execute chunk tasks mid-tick This will help the server load chunks if tick times are high. diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java index b27021a42cbed3f0648a8d0903d00d03922ae221..eada966d7f108a6081be7a848f5c1dfcb1eed676 100644 --- a/src/main/java/co/aikar/timings/MinecraftTimings.java +++ b/src/main/java/co/aikar/timings/MinecraftTimings.java @@ -45,6 +45,8 @@ public final class MinecraftTimings { public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); + public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks"); + private static final Map<Class<?>, String> taskNameCache = new MapMaker().weakKeys().makeMap(); private MinecraftTimings() {} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 088334869cb62797a1e1d1bbb6187f03189d852d..c245c1f4611f7273c8da629f774e0c64e9f98fc2 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -332,6 +332,76 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa return s0; } + // Paper start - execute chunk tasks mid tick + static final long CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME = 25L * 1000L; // 25us + static final long MAX_CHUNK_EXEC_TIME = 1000L; // 1us + + static final long TASK_EXECUTION_FAILURE_BACKOFF = 5L * 1000L; // 5us + + private static long lastMidTickExecute; + private static long lastMidTickExecuteFailure; + + private boolean tickMidTickTasks() { + // 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()) { + long currTime = System.nanoTime(); + if (currTime - world.lastMidTickExecuteFailure <= TASK_EXECUTION_FAILURE_BACKOFF) { + continue; + } + if (!world.getChunkSource().pollTask()) { + // we need to back off if this fails + world.lastMidTickExecuteFailure = currTime; + } else { + executed = true; + } + } + + return executed; + } + + public final void executeMidTickTasks() { + org.spigotmc.AsyncCatcher.catchOp("mid tick chunk task execution"); + long startTime = System.nanoTime(); + if ((startTime - lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - lastMidTickExecuteFailure) <= TASK_EXECUTION_FAILURE_BACKOFF) { + // it's shown to be bad to constantly hit the queue (chunk loads slow to a crawl), even if no tasks are executed. + // so, backoff to prevent this + return; + } + + co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming(); + try { + for (;;) { + boolean moreTasks = this.tickMidTickTasks(); + long currTime = System.nanoTime(); + long diff = currTime - startTime; + + if (!moreTasks || diff >= MAX_CHUNK_EXEC_TIME) { + if (!moreTasks) { + lastMidTickExecuteFailure = currTime; + } + + // note: negative values reduce the time + long overuse = diff - MAX_CHUNK_EXEC_TIME; + if (overuse >= (10L * 1000L * 1000L)) { // 10ms + // make sure something like a GC or dumb plugin doesn't screw us over... + overuse = 10L * 1000L * 1000L; // 10ms + } + + double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME; + long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME); + + lastMidTickExecute = currTime + extraSleep; + return; + } + } + } finally { + co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming(); + } + } + // Paper end - execute chunk tasks mid tick + public MinecraftServer(OptionSet options, DataPackConfig datapackconfiguration, Thread thread, RegistryAccess.RegistryHolder iregistrycustom_dimension, LevelStorageSource.LevelStorageAccess convertable_conversionsession, WorldData savedata, PackRepository resourcepackrepository, Proxy proxy, DataFixer datafixer, ServerResources datapackresources, @Nullable MinecraftSessionService minecraftsessionservice, @Nullable GameProfileRepository gameprofilerepository, @Nullable GameProfileCache usercache, ChunkProgressListenerFactory worldloadlistenerfactory) { super("Server"); SERVER = this; // Paper - better singleton @@ -1312,6 +1382,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa private boolean pollTaskInternal() { if (super.pollTask()) { + this.executeMidTickTasks(); // Paper - execute chunk tasks mid tick return true; } else { if (this.haveTime()) { diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java index ad352b4b67632f9984c4d10994a9acfe434a4996..df415f79dfd2ae9a709747b112022b38437daac4 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -1023,6 +1023,7 @@ public class ServerChunkCache extends ChunkSource { iterator1 = shuffled.iterator(); } + int chunksTicked = 0; // Paper try { while (iterator1.hasNext()) { LevelChunk chunk1 = iterator1.next(); @@ -1044,6 +1045,7 @@ public class ServerChunkCache extends ChunkSource { if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { this.level.tickChunk(chunk1, k); + if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper } } // Paper start - optimise chunk tick iteration diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 5e65df1a9a8282c4ffa06801379b79ab0ed1b45c..7e837b2896cac64a982d9025c4e190dfa7ebc451 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -201,7 +201,9 @@ public class ServerLevel extends Level implements WorldGenLevel { private final StructureFeatureManager structureFeatureManager; private final StructureCheck structureCheck; private final boolean tickTime; - + // Paper start - execute chunk tasks mid tick + public long lastMidTickExecuteFailure; + // Paper end - execute chunk tasks mid tick // CraftBukkit start private int tickPosition; diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java index 3831895e2219d846022500553d9b714c7d654b3a..86bcedec97f1bc95621380da6ad074bdcc4bfeab 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -896,6 +896,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public <T extends Entity> void guardEntityTick(Consumer<T> tickConsumer, T entity) { try { tickConsumer.accept(entity); + MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick } catch (Throwable throwable) { if (throwable instanceof ThreadDeath) throw throwable; // Paper // Paper start - Prevent tile entity and entity crashes