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/com/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java index 5fdaefc128956581be4bb9b34199fd6410563991..b7edc1121797bc1c57e25f540ed0124fa8b36b7a 100644 --- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java @@ -312,6 +312,7 @@ public final class PaperTickList<T> extends ServerTickList<T> { // extend to avo toTick.tickState = STATE_SCHEDULED; this.addToNotTickingReady(toTick); } + MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick } catch (final Throwable thr) { // start copy from TickListServer // TODO check on update CrashReport crashreport = CrashReport.forThrowable(thr, "Exception while ticking"); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 154b0dd9bcfefbd7e7eae8c2f28bbabcd0fde531..099db3a627c1d3c08f3b923bb02a1cb8a0a268ab 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -330,6 +330,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 @@ -1324,6 +1394,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 7ea86cbeb72f08d751c14006f428fe5921916061..108f2212f8bd00247bf73ff4f3ba42830abad459 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -1022,6 +1022,7 @@ public class ServerChunkCache extends ChunkSource { Collections.shuffle(shuffled); iterator = shuffled.iterator(); } + int chunksTicked = 0; // Paper try { while (iterator.hasNext()) { LevelChunk chunk = iterator.next(); ChunkHolder playerchunk = chunk.playerChunk; @@ -1044,6 +1045,7 @@ public class ServerChunkCache extends ChunkSource { this.level.tickChunk(chunk, k); // this.level.timings.doTickTiles.stopTiming(); // Spigot // Paper } + if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper } } // Paper start - optimise chunk tick iteration } finally { diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 7fd55f26ad278aae27c102638ad8ab6776f27c7d..12b13945702a03132d1b4c42864c8036d1357020 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -190,7 +190,9 @@ public class ServerLevel extends Level implements WorldGenLevel { final Int2ObjectMap<EnderDragonPart> dragonParts; private final StructureFeatureManager structureFeatureManager; 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 82da0f2226f71a5081e24c17a8a39be22e3c5316..6ca0134c76adc335bb5125831e332fd709f09de7 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -912,6 +912,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