From 897dd2c84035e6d46635cde5c17f48b7eda3411e Mon Sep 17 00:00:00 2001 From: Aikar Date: Sat, 16 May 2020 21:38:19 -0400 Subject: [PATCH] Foundational work for Future Memory usage improvements This commit doesn't do much on its own, but adds a new Java Cleaner API that lets us hook into Garbage Collector events to reclaim pooled objects and return them to the pool. Adds framework for Network Packets to know when a packet has finished dispatching to get an idea when a packet is done sending to players. Rewrites PooledObjects impl to properly respect max pool size and remove almost all risk of contention. Bumps the Paper Async Task Queue to use 2 threads, and properly shuts it down on shutdown. --- Spigot-Server-Patches/0001-POM-Changes.patch | 27 +- Spigot-Server-Patches/0004-MC-Utils.patch | 353 +++++++++++------- Spigot-Server-Patches/0009-Timings-v2.patch | 24 +- ...ient-crashes-server-lists-and-Mojang.patch | 4 +- ...023-Further-improve-server-tick-loop.patch | 6 +- .../0032-Optimize-explosions.patch | 4 +- ...ckPhysicsEvent-if-a-plugin-has-a-lis.patch | 6 +- ...-possibility-for-getServer-singleton.patch | 4 +- .../0100-Optimize-UserCache-Thread-Safe.patch | 6 +- .../0133-String-based-Action-Bar-API.patch | 4 +- ...le-async-calls-to-restart-the-server.patch | 6 +- ...oleAppender-for-console-improvements.patch | 12 +- .../0154-Basic-PlayerProfile-API.patch | 10 +- ...nt-extended-PaperServerListPingEvent.patch | 4 +- ...dd-Early-Warning-Feature-to-WatchDog.patch | 4 +- ...arseException-in-Entity-and-TE-names.patch | 4 +- .../0305-Hook-into-CB-plugin-rewrites.patch | 4 +- .../0321-Optimize-World-Time-Updates.patch | 4 +- ...-Manager-and-add-advanced-packet-sup.patch | 92 +++-- .../0360-Server-Tick-Events.patch | 6 +- ...isPrimaryThread-and-MinecraftServer-.patch | 4 +- .../0377-Chunk-debug-command.patch | 6 +- .../0378-incremental-chunk-saving.patch | 6 +- ...90-Asynchronous-chunk-IO-and-loading.patch | 19 +- .../0414-Optimize-Hoppers.patch | 4 +- ...hunkMap-memory-use-for-visibleChunks.patch | 4 +- ...asks-Speed-up-processing-of-chunk-lo.patch | 16 +- ...-Add-tick-times-API-and-mspt-command.patch | 6 +- .../0467-Improved-Watchdog-Support.patch | 16 +- .../0501-Implement-Mob-Goal-API.patch | 4 +- 30 files changed, 395 insertions(+), 274 deletions(-) diff --git a/Spigot-Server-Patches/0001-POM-Changes.patch b/Spigot-Server-Patches/0001-POM-Changes.patch index 4ee8b894b0..f157e49106 100644 --- a/Spigot-Server-Patches/0001-POM-Changes.patch +++ b/Spigot-Server-Patches/0001-POM-Changes.patch @@ -5,7 +5,7 @@ Subject: [PATCH] POM Changes diff --git a/pom.xml b/pom.xml -index 9fc92e347f24a0210a9190513e93cba3b6772557..6cc18aa360c20448fca59cf5490d69267c8e2521 100644 +index 9fc92e347f24a0210a9190513e93cba3b6772557..adf32845001fae7a870f588184c2efaf0ab41504 100644 --- a/pom.xml +++ b/pom.xml @@ -1,15 +1,14 @@ @@ -55,7 +55,20 @@ index 9fc92e347f24a0210a9190513e93cba3b6772557..6cc18aa360c20448fca59cf5490d6926 ${project.version} compile -@@ -106,34 +111,22 @@ +@@ -50,6 +55,12 @@ + 7.3.1 + compile + ++ ++ ++ co.aikar ++ cleaner ++ 1.0-SNAPSHOT ++ + + + com.googlecode.json-simple +@@ -106,34 +117,22 @@ @@ -101,7 +114,7 @@ index 9fc92e347f24a0210a9190513e93cba3b6772557..6cc18aa360c20448fca59cf5490d6926 -@@ -143,6 +136,7 @@ +@@ -143,6 +142,7 @@ maven-jar-plugin 3.2.0 @@ -109,7 +122,7 @@ index 9fc92e347f24a0210a9190513e93cba3b6772557..6cc18aa360c20448fca59cf5490d6926 false -@@ -150,8 +144,9 @@ +@@ -150,8 +150,9 @@ org.bukkit.craftbukkit.Main CraftBukkit @@ -121,7 +134,7 @@ index 9fc92e347f24a0210a9190513e93cba3b6772557..6cc18aa360c20448fca59cf5490d6926 Bukkit ${api.version} Bukkit Team -@@ -190,6 +185,7 @@ +@@ -190,6 +191,7 @@ shade @@ -129,7 +142,7 @@ index 9fc92e347f24a0210a9190513e93cba3b6772557..6cc18aa360c20448fca59cf5490d6926 ${shadeSourcesJar} -@@ -213,10 +209,11 @@ +@@ -213,10 +215,11 @@ jline org.bukkit.craftbukkit.libs.jline @@ -145,7 +158,7 @@ index 9fc92e347f24a0210a9190513e93cba3b6772557..6cc18aa360c20448fca59cf5490d6926 org.apache.commons.codec org.bukkit.craftbukkit.libs.org.apache.commons.codec -@@ -258,10 +255,6 @@ +@@ -258,10 +261,6 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 diff --git a/Spigot-Server-Patches/0004-MC-Utils.patch b/Spigot-Server-Patches/0004-MC-Utils.patch index 3242652166..6a4038d40d 100644 --- a/Spigot-Server-Patches/0004-MC-Utils.patch +++ b/Spigot-Server-Patches/0004-MC-Utils.patch @@ -2077,90 +2077,173 @@ index 0000000000000000000000000000000000000000..e51104e65a07b6ea7bbbcbb6afb066ef +} diff --git a/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java new file mode 100644 -index 0000000000000000000000000000000000000000..e272b512520486cf7d46fe4e1021ca148d4cf74f +index 0000000000000000000000000000000000000000..9841212a60346870535e81b22851261e12380650 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java @@ -0,0 +1,174 @@ +package com.destroystokyo.paper.util.pooled; + ++import net.minecraft.server.MCUtil; +import org.apache.commons.lang3.mutable.MutableInt; ++ +import java.util.ArrayDeque; +import java.util.concurrent.ThreadLocalRandom; ++import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantLock; ++import java.util.function.Consumer; + ++/** ++ * Object pooling with thread safe, low contention design. Pooled objects have no additional object overhead ++ * due to usage of ArrayDeque per insertion/removal unless a resizing is needed in the buckets. ++ * Supports up to bucket size (default 8) threads concurrently accessing if all buckets have a value. ++ * Releasing may conditionally have contention if multiple buckets have same current size, but randomization will be used. ++ * ++ * Original interface API by Spottedleaf ++ * Implementation by Aikar ++ * @license MIT ++ */ +public final class PooledObjects { + -+ public static final PooledObjects POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 200, -1); ++ /** ++ * Wrapper for an object that will be have a cleaner registered for it, and may be automatically returned to pool. ++ */ ++ public class AutoReleased { ++ private final E object; ++ private final Runnable cleaner; ++ ++ public AutoReleased(E object, Runnable cleaner) { ++ this.object = object; ++ this.cleaner = cleaner; ++ } ++ ++ public final E getObject() { ++ return object; ++ } ++ ++ public final Runnable getCleaner() { ++ return cleaner; ++ } ++ } ++ ++ public static final PooledObjects POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 1024, 16); + + private final PooledObjectHandler handler; -+ private final int maxPoolSize; -+ private final int expectingThreads; ++ private final int bucketCount; ++ private final int bucketSize; ++ private final ArrayDeque[] buckets; ++ private final ReentrantLock[] locks; ++ private final AtomicLong bucketIdCounter = new AtomicLong(0); + -+ private final IsolatedPool mainPool; -+ // use these under contention -+ private final IsolatedPool[] contendedPools; -+ -+ public PooledObjects(final PooledObjectHandler handler, final int maxPoolSize, int expectingThreads) { ++ public PooledObjects(final PooledObjectHandler handler, int maxPoolSize) { ++ this(handler, maxPoolSize, 8); ++ } ++ public PooledObjects(final PooledObjectHandler handler, int maxPoolSize, int bucketCount) { + if (handler == null) { + throw new NullPointerException("Handler must not be null"); + } + if (maxPoolSize <= 0) { + throw new IllegalArgumentException("Max pool size must be greater-than 0"); + } -+ if (expectingThreads <= 0) { -+ expectingThreads = Runtime.getRuntime().availableProcessors(); ++ if (bucketCount < 1) { ++ throw new IllegalArgumentException("Bucket count must be greater-than 0"); + } -+ ++ int remainder = maxPoolSize % bucketCount; ++ if (remainder > 0) { ++ // Auto adjust up to the next bucket divisible size ++ maxPoolSize = maxPoolSize - remainder + bucketCount; ++ } ++ //noinspection unchecked ++ this.buckets = new ArrayDeque[bucketCount]; ++ this.locks = new ReentrantLock[bucketCount]; ++ this.bucketCount = bucketCount; + this.handler = handler; -+ this.maxPoolSize = maxPoolSize; -+ this.expectingThreads = expectingThreads; -+ this.mainPool = new IsolatedPool<>(handler, maxPoolSize); -+ final IsolatedPool[] contendedPools = new IsolatedPool[2 * expectingThreads]; ++ this.bucketSize = maxPoolSize / bucketCount; ++ for (int i = 0; i < bucketCount; i++) { ++ this.buckets[i] = new ArrayDeque<>(bucketSize / 4); ++ this.locks[i] = new ReentrantLock(); ++ } ++ } + -+ for (int i = 0; i < contendedPools.length; ++i) { -+ contendedPools[i] = new IsolatedPool<>(handler, Math.max(1, maxPoolSize / 2)); ++ public AutoReleased acquireCleaner(Object holder) { ++ return acquireCleaner(holder, this::release); ++ } ++ ++ public AutoReleased acquireCleaner(Object holder, Consumer releaser) { ++ E resource = acquire(); ++ Runnable cleaner = MCUtil.registerCleaner(holder, resource, releaser); ++ return new AutoReleased(resource, cleaner); ++ } ++ ++ ++ public long size() { ++ long size = 0; ++ for (int i = 0; i < bucketCount; i++) { ++ size += this.buckets[i].size(); + } + -+ this.contendedPools = contendedPools; ++ return size; + } -+ -+ // Taken from -+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ -+ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c -+ // Original license is public domain -+ public static int fastRandomBounded(final long randomInteger, final long limit) { -+ // randomInteger must be [0, pow(2, 32)) -+ // limit must be [0, pow(2, 32)) -+ return (int)((randomInteger * limit) >>> 32); -+ } -+ + public E acquire() { -+ E ret; -+ PooledObjects.IsolatedPool pooled = this.mainPool; -+ int lastIndex = -1; -+ while ((ret = pooled.tryAcquireUncontended()) == null) { -+ int index; -+ while (lastIndex == (index = fastRandomBounded(ThreadLocalRandom.current().nextInt() & 0xFFFFFFFFL, this.contendedPools.length))); -+ lastIndex = index; -+ pooled = this.contendedPools[index]; ++ for (int base = (int) (this.bucketIdCounter.getAndIncrement() % bucketCount), i = 0; i < bucketCount; i++ ) { ++ int bucketId = (base + i) % bucketCount; ++ if (this.buckets[bucketId].isEmpty()) continue; ++ // lock will alloc an object if blocked, so spinwait instead since lock duration is super fast ++ lockBucket(bucketId); ++ E value = this.buckets[bucketId].poll(); ++ this.locks[bucketId].unlock(); ++ if (value != null) { ++ this.handler.onAcquire(value); ++ return value; ++ } + } ++ return this.handler.createNew(); ++ } + -+ return ret; ++ private void lockBucket(int bucketId) { ++ // lock will alloc an object if blocked, try to avoid unless 2 failures ++ ReentrantLock lock = this.locks[bucketId]; ++ if (!lock.tryLock()) { ++ Thread.yield(); ++ } else { ++ return; ++ } ++ if (!lock.tryLock()) { ++ Thread.yield(); ++ lock.lock(); ++ } + } + + public void release(final E value) { -+ PooledObjects.IsolatedPool pooled = this.mainPool; -+ int lastIndex = -1; -+ while (!pooled.tryReleaseUncontended(value)) { -+ int index; -+ while (lastIndex == (index = fastRandomBounded(ThreadLocalRandom.current().nextInt() & 0xFFFFFFFFL, this.contendedPools.length))); -+ lastIndex = index; -+ pooled = this.contendedPools[index]; -+ } ++ int attempts = 3; // cap on contention ++ do { ++ // find least filled bucket before locking ++ int smallestIdx = -1; ++ int smallest = Integer.MAX_VALUE; ++ for (int i = 0; i < bucketCount; i++ ) { ++ ArrayDeque bucket = this.buckets[i]; ++ int size = bucket.size(); ++ if (size < this.bucketSize && (smallestIdx == -1 || size < smallest || (size == smallest && ThreadLocalRandom.current().nextBoolean()))) { ++ smallestIdx = i; ++ smallest = size; ++ } ++ } ++ if (smallestIdx == -1) return; // Can not find a bucket to fill ++ ++ lockBucket(smallestIdx); ++ ArrayDeque bucket = this.buckets[smallestIdx]; ++ if (bucket.size() < this.bucketSize) { ++ this.handler.onRelease(value); ++ bucket.push(value); ++ this.locks[smallestIdx].unlock(); ++ return; ++ } else { ++ this.locks[smallestIdx].unlock(); ++ } ++ } while (attempts-- > 0); + } + + /** This object is restricted from interacting with any pool */ -+ public static interface PooledObjectHandler { ++ public interface PooledObjectHandler { + + /** + * Must return a non-null object @@ -2171,89 +2254,6 @@ index 0000000000000000000000000000000000000000..e272b512520486cf7d46fe4e1021ca14 + + default void onRelease(final E value) {} + } -+ -+ protected static class IsolatedPool { -+ -+ protected final PooledObjectHandler handler; -+ -+ // We use arraydeque as it doesn't create garbage per element... -+ protected final ArrayDeque pool; -+ protected final int maxPoolSize; -+ -+ protected final ReentrantLock lock = new ReentrantLock(); -+ -+ public IsolatedPool(final PooledObjectHandler handler, final int maxPoolSize) { -+ this.handler = handler; -+ this.pool = new ArrayDeque<>(); -+ this.maxPoolSize = maxPoolSize; -+ } -+ -+ protected E acquireOrCreateNoLock() { -+ E ret; -+ -+ ret = this.pool.poll(); -+ -+ if (ret == null) { -+ ret = this.handler.createNew(); -+ } -+ this.handler.onAcquire(ret); -+ -+ return ret; -+ } -+ -+ public E tryAcquireUncontended() { -+ if (!this.lock.tryLock()) { -+ return null; -+ } -+ try { -+ return this.acquireOrCreateNoLock(); -+ } finally { -+ this.lock.unlock(); -+ } -+ } -+ -+ public E acquire() { -+ this.lock.lock(); -+ try { -+ return this.acquireOrCreateNoLock(); -+ } finally { -+ this.lock.unlock(); -+ } -+ } -+ -+ protected void releaseNoLock(final E value) { -+ if (this.pool.size() >= this.maxPoolSize) { -+ this.handler.onRelease(value); -+ return; // can't accept, we're at capacity -+ } -+ -+ this.pool.add(value); -+ this.handler.onRelease(value); -+ } -+ -+ public boolean tryReleaseUncontended(final E value) { -+ if (!this.lock.tryLock()) { -+ return false; -+ } -+ -+ try { -+ this.releaseNoLock(value); -+ } finally { -+ this.lock.unlock(); -+ } -+ -+ return true; -+ } -+ -+ public void release(final E value) { -+ this.lock.lock(); -+ try { -+ this.releaseNoLock(value); -+ } finally { -+ this.lock.unlock(); -+ } -+ } -+ } +} diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java new file mode 100644 @@ -3335,10 +3335,10 @@ index 75308712d0642d5ab168de653023349df8aee5ed..aa7501d366b15e7f7f64b7d98a1dccff // CraftBukkit end diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..9fb9a96ccb37f5c7f39403e24e7b3bdb9279fe81 +index 0000000000000000000000000000000000000000..7164f46516bdf49ed52062f2d72f33418506bae0 --- /dev/null +++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -0,0 +1,414 @@ +@@ -0,0 +1,473 @@ +package net.minecraft.server; + +import com.destroystokyo.paper.block.TargetBlockInfo; @@ -3355,21 +3355,80 @@ index 0000000000000000000000000000000000000000..9fb9a96ccb37f5c7f39403e24e7b3bdb +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; -+import java.util.concurrent.Executor; -+import java.util.concurrent.Executors; ++import java.util.concurrent.LinkedBlockingQueue; ++import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.function.Consumer; +import java.util.function.Supplier; + +public final class MCUtil { -+ private static final Executor asyncExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build()); ++ public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor( ++ 0, 2, 60L, TimeUnit.SECONDS, ++ new LinkedBlockingQueue(), ++ new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build() ++ ); ++ public static final ThreadPoolExecutor cleanerExecutor = new ThreadPoolExecutor( ++ 1, 1, 0L, TimeUnit.SECONDS, ++ new LinkedBlockingQueue(), ++ new ThreadFactoryBuilder().setNameFormat("Paper Object Cleaner").build() ++ ); + + public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE); + -+ public static void ensureTickThread(final String reason) { -+ if (MinecraftServer.getServer().serverThread != Thread.currentThread()) { -+ throw new IllegalStateException(reason); -+ } ++ ++ public static Runnable once(Runnable run) { ++ AtomicBoolean ran = new AtomicBoolean(false); ++ return () -> { ++ if (ran.compareAndSet(false, true)) { ++ run.run(); ++ } ++ }; ++ } ++ ++ private static Runnable makeCleanerCallback(Runnable run) { ++ return once(() -> cleanerExecutor.execute(run)); ++ } ++ ++ /** ++ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! ++ * @param obj ++ * @param run ++ * @return ++ */ ++ public static Runnable registerCleaner(Object obj, Runnable run) { ++ // Wrap callback in its own method above or the lambda will leak object ++ Runnable cleaner = makeCleanerCallback(run); ++ co.aikar.cleaner.Cleaner.register(obj, cleaner); ++ return cleaner; ++ } ++ ++ /** ++ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! ++ * @param obj ++ * @param list ++ * @param cleaner ++ * @param ++ * @return ++ */ ++ public static Runnable registerListCleaner(Object obj, List list, Consumer cleaner) { ++ return registerCleaner(obj, () -> { ++ list.forEach(cleaner); ++ list.clear(); ++ }); ++ } ++ ++ /** ++ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! ++ * @param obj ++ * @param resource ++ * @param cleaner ++ * @param ++ * @return ++ */ ++ public static Runnable registerCleaner(Object obj, T resource, java.util.function.Consumer cleaner) { ++ return registerCleaner(obj, () -> cleaner.accept(resource)); + } + + public static List getSpiralOutChunks(BlockPosition blockposition, int radius) { @@ -3753,6 +3812,20 @@ index 0000000000000000000000000000000000000000..9fb9a96ccb37f5c7f39403e24e7b3bdb + } + } +} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index b4a0bd79511a3b1185a165991c937375aeecf3d1..786d38438cc1bd5a736b2dfa80aca9b9c6253e65 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -741,6 +741,9 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant= 5000000000L) { -@@ -997,14 +1018,12 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit @@ -840,7 +840,7 @@ index b4a0bd79511a3b1185a165991c937375aeecf3d1..67bdd577477730f1775f87189c9fcee6 } this.methodProfiler.enter("snooper"); -@@ -1017,6 +1036,13 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant(ResourcePackLoader::new); this.craftingManager = new CraftingManager(); this.tagRegistry = new TagRegistry(); -@@ -2186,7 +2188,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant org.ow2.asm -@@ -246,10 +263,18 @@ +@@ -252,10 +269,18 @@ META-INF/services/java.sql.Driver @@ -185,7 +185,7 @@ index 4b1f8c53737f998fa57859146d5ddb999cdc8d41..d34f772fae3543cec6a130831b1f3eaa System.setOut(new PrintStream(new LoggerOutputStream(logger, Level.INFO), true)); System.setErr(new PrintStream(new LoggerOutputStream(logger, Level.WARN), true)); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 8d6a0890073adbbb39db202f80d4b83cef2ceca9..284793e4bf04cddae3e070a1fa0afdd18001fd2e 100644 +index 80d8b0b0eac47b8d8e62db60da9daf0da8671fb3..87595425a358a13c8f2393619d51a981140556cf 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -57,7 +57,7 @@ import org.apache.commons.lang3.Validate; @@ -225,7 +225,7 @@ index 8d6a0890073adbbb39db202f80d4b83cef2ceca9..284793e4bf04cddae3e070a1fa0afdd1 Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this)); } // CraftBukkit end -@@ -951,7 +955,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant= 5000000000L) { this.Z = i; this.serverPing.setPlayerSample(new ServerPing.ServerPingPlayerSample(this.getMaxPlayers(), this.getPlayerCount())); diff --git a/Spigot-Server-Patches/0269-Add-Early-Warning-Feature-to-WatchDog.patch b/Spigot-Server-Patches/0269-Add-Early-Warning-Feature-to-WatchDog.patch index 501d3869f3..736e84fac2 100644 --- a/Spigot-Server-Patches/0269-Add-Early-Warning-Feature-to-WatchDog.patch +++ b/Spigot-Server-Patches/0269-Add-Early-Warning-Feature-to-WatchDog.patch @@ -36,10 +36,10 @@ index adef07d4d521b4aaa6f3389b04aa27e29bec0229..214b577b326bc794fa3721deb6171228 public static int tabSpamLimit = 500; private static void tabSpamLimiters() { diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index c502aedb8dc4e7a5d7ba9d16a200c20ca3d24cd4..056cbdeec8a1c17de44d59f16b77a995c82a3abb 100644 +index d1667eba3398efecc8913c2778931030a90d6195..b7c83cd82ca1c9b6bdaaf566e800b8d15ad7d966 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -870,6 +870,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant8.0.1 compile - + diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java index 9b4a0f0678a7e8e347ef062ad15562484a74452b..4ae41fd2557dcc2a8e31d39ed978b2b26093dd06 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java diff --git a/Spigot-Server-Patches/0321-Optimize-World-Time-Updates.patch b/Spigot-Server-Patches/0321-Optimize-World-Time-Updates.patch index b01022a40a..d56e204895 100644 --- a/Spigot-Server-Patches/0321-Optimize-World-Time-Updates.patch +++ b/Spigot-Server-Patches/0321-Optimize-World-Time-Updates.patch @@ -8,10 +8,10 @@ the updates per world, so that we can re-use the same packet object for every player unless they have per-player time enabled. diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index c97bbe933dd05829d9da7bc71d03d2c0a26a4ad1..3d9cc2ce67b5dc033df397e8d1c31f718792dcc4 100644 +index 66097d8cbc916d459ac0ab3b69fbac91a5b57ed3..207dd30539fa3961ba96aafe1e3cfef5020a885c 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1166,12 +1166,24 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant> { @@ -46,11 +45,18 @@ index b1dededc15cce686ead74a99bee64c89ac1de22c..35085ca3992e7c21139540d0c404e156 this.channel.attr(NetworkManager.c).set(enumprotocol); this.channel.config().setAutoRead(true); NetworkManager.LOGGER.debug("Enabled auto read"); -@@ -158,19 +163,75 @@ public class NetworkManager extends SimpleChannelInboundHandler> { +@@ -158,19 +163,82 @@ public class NetworkManager extends SimpleChannelInboundHandler> { NetworkManager.LOGGER.debug("Set listener of {} to {}", this, packetlistener); this.packetListener = packetlistener; } + // Paper start ++ private EntityPlayer getPlayer() { ++ if (packetListener instanceof PlayerConnection) { ++ return ((PlayerConnection) packetListener).player; ++ } else { ++ return null; ++ } ++ } + private static class InnerUtil { // Attempt to hide these methods from ProtocolLib so it doesn't accidently pick them up. + private static java.util.List buildExtraPackets(Packet packet) { + java.util.List extra = packet.getExtraPackets(); @@ -95,9 +101,9 @@ index b1dededc15cce686ead74a99bee64c89ac1de22c..35085ca3992e7c21139540d0c404e156 + // Paper start - handle oversized packets better + boolean connected = this.isConnected(); + if (!connected && !preparing) { -+ packet.onPacketDone(); + return; // Do nothing + } ++ packet.onPacketDispatch(getPlayer()); + if (connected && (InnerUtil.canSendImmediate(this, packet) || ( + MCUtil.isMainThread() && packet.isReady() && this.packetQueue.isEmpty() && + (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) @@ -113,13 +119,13 @@ index b1dededc15cce686ead74a99bee64c89ac1de22c..35085ca3992e7c21139540d0c404e156 + } else { + java.util.List packets = new java.util.ArrayList<>(1 + extraPackets.size()); + packets.add(new NetworkManager.QueuedPacket(packet, null)); // delay the future listener until the end of the extra packets -+ + + for (int i = 0, len = extraPackets.size(); i < len;) { + Packet extra = extraPackets.get(i); + boolean end = ++i == len; + packets.add(new NetworkManager.QueuedPacket(extra, end ? genericfuturelistener : null)); // append listener to the end + } - ++ + this.packetQueue.addAll(packets); // atomic + } + this.sendPacketQueue(); @@ -127,7 +133,31 @@ index b1dededc15cce686ead74a99bee64c89ac1de22c..35085ca3992e7c21139540d0c404e156 } private void dispatchPacket(Packet packet, @Nullable GenericFutureListener> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER -@@ -214,21 +275,46 @@ public class NetworkManager extends SimpleChannelInboundHandler> { +@@ -194,6 +262,11 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + if (genericfuturelistener != null) { + channelfuture.addListener(genericfuturelistener); + } ++ // Paper start ++ if (packet.hasFinishListener()) { ++ channelfuture.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(getPlayer(), channelFuture)); ++ } ++ // Paper end + + channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } else { +@@ -207,6 +280,11 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + if (genericfuturelistener != null) { + channelfuture1.addListener(genericfuturelistener); + } ++ // Paper start ++ if (packet.hasFinishListener()) { ++ channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(getPlayer(), channelFuture)); ++ } ++ // Paper end + + channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + }); +@@ -214,21 +292,46 @@ public class NetworkManager extends SimpleChannelInboundHandler> { } @@ -184,11 +214,21 @@ index b1dededc15cce686ead74a99bee64c89ac1de22c..35085ca3992e7c21139540d0c404e156 public void a() { this.o(); -@@ -257,9 +343,11 @@ public class NetworkManager extends SimpleChannelInboundHandler> { +@@ -257,9 +360,21 @@ public class NetworkManager extends SimpleChannelInboundHandler> { return this.socketAddress; } -+ public void clearPacketQueue() { QueuedPacket packet; while ((packet = packetQueue.poll()) != null) packet.getPacket().onPacketDone(); } // Paper ++ // Paper start ++ public void clearPacketQueue() { ++ EntityPlayer player = getPlayer(); ++ packetQueue.forEach(queuedPacket -> { ++ Packet packet = queuedPacket.getPacket(); ++ if (packet.hasFinishListener()) { ++ packet.onPacketDispatchFinish(player, null); ++ } ++ }); ++ packetQueue.clear(); ++ } // Paper end public void close(IChatBaseComponent ichatbasecomponent) { // Spigot Start this.preparing = false; @@ -196,7 +236,7 @@ index b1dededc15cce686ead74a99bee64c89ac1de22c..35085ca3992e7c21139540d0c404e156 // Spigot End if (this.channel.isOpen()) { this.channel.close(); // We can't wait as this may be called from an event loop. -@@ -335,7 +423,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { +@@ -335,7 +450,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { } else if (this.i() != null) { this.i().a(new ChatMessage("multiplayer.disconnect.generic", new Object[0])); } @@ -206,34 +246,32 @@ index b1dededc15cce686ead74a99bee64c89ac1de22c..35085ca3992e7c21139540d0c404e156 final PacketListener packetListener = this.i(); if (packetListener instanceof PlayerConnection) { diff --git a/src/main/java/net/minecraft/server/Packet.java b/src/main/java/net/minecraft/server/Packet.java -index 2d8e6a2f4a0c3c5d74a647d7164b0028781d3bf5..df1b4877b1560f982a1fcaf98404c8fe73e29973 100644 +index 2d8e6a2f4a0c3c5d74a647d7164b0028781d3bf5..ffc9a1f7d58d67611c4ab46462ac13a921042313 100644 --- a/src/main/java/net/minecraft/server/Packet.java +++ b/src/main/java/net/minecraft/server/Packet.java -@@ -11,6 +11,9 @@ public interface Packet { +@@ -11,6 +11,20 @@ public interface Packet { void a(T t0); // Paper start -+ default void onPacketDone() {} ++ ++ /** ++ * @param player Null if not at PLAY stage yet ++ */ ++ default void onPacketDispatch(@javax.annotation.Nullable EntityPlayer player) {} ++ ++ /** ++ * @param player Null if not at PLAY stage yet ++ * @param future Can be null if packet was cancelled ++ */ ++ default void onPacketDispatchFinish(@javax.annotation.Nullable EntityPlayer player, @javax.annotation.Nullable io.netty.channel.ChannelFuture future) {} ++ default boolean hasFinishListener() { return false; } + default boolean isReady() { return true; } + default java.util.List getExtraPackets() { return null; } default boolean packetTooLarge(NetworkManager manager) { return false; } -diff --git a/src/main/java/net/minecraft/server/PacketEncoder.java b/src/main/java/net/minecraft/server/PacketEncoder.java -index b0cfef52cbb5e23beae528668e4e98cedecf603c..f46d028016a425a29674e768ae9310c825c088f2 100644 ---- a/src/main/java/net/minecraft/server/PacketEncoder.java -+++ b/src/main/java/net/minecraft/server/PacketEncoder.java -@@ -48,7 +48,7 @@ public class PacketEncoder extends MessageToByteEncoder> { - } else { - throw throwable; - } -- } -+ } finally { try { packet.onPacketDone(); } catch (Exception e) { e.printStackTrace(); } ; } // Paper - - // Paper start - int packetLength = bytebuf.readableBytes(); diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java -index e148940ab3721cff27cf791c159c11b9b94191e4..e917d37382dab70ed9e6b62decf1557c33b26065 100644 +index 5136905b71085445eb6bac00e9200af8cc7fbe27..14c82861158eed8c91336590fb71c69d185d22f8 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -143,6 +143,7 @@ public abstract class PlayerList { diff --git a/Spigot-Server-Patches/0360-Server-Tick-Events.patch b/Spigot-Server-Patches/0360-Server-Tick-Events.patch index db2d5cb2bb..981bed288c 100644 --- a/Spigot-Server-Patches/0360-Server-Tick-Events.patch +++ b/Spigot-Server-Patches/0360-Server-Tick-Events.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Server Tick Events Fires event at start and end of a server tick diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 3d9cc2ce67b5dc033df397e8d1c31f718792dcc4..249eaf56bc0ec9eb99fdf8958d3ebe2b18999819 100644 +index 207dd30539fa3961ba96aafe1e3cfef5020a885c..ae4d62d52c0763849d06709fc405018711db6f90 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1089,6 +1089,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant chunkGenerator; private final WorldServer world; diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index 3342278bcd42a6d5a1793e33bc7fe4356be02451..2dcecc1bbd00e46b0a9b5e48bc580475fc8b4cb3 100644 +index be20d770df41a656cf2aabfec87e0bdc639053f4..de8b8f54cd906c1154a6790b9220d3e0976c74bd 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java @@ -4,7 +4,13 @@ import com.destroystokyo.paper.block.TargetBlockInfo; @@ -226,8 +226,8 @@ index 3342278bcd42a6d5a1793e33bc7fe4356be02451..2dcecc1bbd00e46b0a9b5e48bc580475 +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; - import java.util.concurrent.Executor; -@@ -452,4 +461,170 @@ public final class MCUtil { + import java.util.concurrent.LinkedBlockingQueue; +@@ -511,4 +520,170 @@ public final class MCUtil { return null; } diff --git a/Spigot-Server-Patches/0378-incremental-chunk-saving.patch b/Spigot-Server-Patches/0378-incremental-chunk-saving.patch index 6dc53a9e7c..5c1a01b75e 100644 --- a/Spigot-Server-Patches/0378-incremental-chunk-saving.patch +++ b/Spigot-Server-Patches/0378-incremental-chunk-saving.patch @@ -62,7 +62,7 @@ index e6d08756f76360b29b29f18305e5ec84d09f2d54..6713b7667ae4fe3f1f555a71321832b4 public void close() throws IOException { // CraftBukkit start diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 0ee1d8e4869bfd0ccba21a227e115a36ff027984..7ecf781263179d87c943b08e192d8f010cf20d3e 100644 +index 8b499c815c77bf5b356d4216ba6cbf2a329c9aca..cfed5f51431ec5aecb538a321327bfb6e8a0bd88 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -168,6 +168,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0; // Paper diff --git a/Spigot-Server-Patches/0456-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch b/Spigot-Server-Patches/0456-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch index 71ba807c1b..3b16c1f78f 100644 --- a/Spigot-Server-Patches/0456-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch +++ b/Spigot-Server-Patches/0456-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch @@ -63,10 +63,10 @@ index fd998e4fb1534690a2ef8c1bca55e0ae9fe855f9..8f849d83d08b39f1cd9184f484a2089a if (optional.isPresent()) { diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index d9941b38ca037a31f520784b3706080f1d322fb4..71ab65e00fe31ea4047cf8a5921c6deba13de6b9 100644 +index f8a1f0b96f2eb8535e3080db979bb383d5a18a11..88b41b1d0c2045d01449256a5875ae73765c5595 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -529,7 +529,7 @@ public final class MCUtil { +@@ -588,7 +588,7 @@ public final class MCUtil { WorldServer world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); PlayerChunkMap chunkMap = world.getChunkProvider().playerChunkMap; diff --git a/Spigot-Server-Patches/0459-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch b/Spigot-Server-Patches/0459-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch index 6aa92c7107..df6324af80 100644 --- a/Spigot-Server-Patches/0459-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch +++ b/Spigot-Server-Patches/0459-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch @@ -135,10 +135,10 @@ index 8f849d83d08b39f1cd9184f484a2089a7a3124ef..5806ca545191e609bab04e522e358948 protected boolean executeNext() { // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 77adc64e30cbc1d4542eb8f4a446788c1fdc61be..3c25436f158316d2e09cbf4673365eddb03ecef4 100644 +index f9faa30ef914b1dd2dada9b7d89e80b34d2f1d0d..97cca4495a8dab4434e917a5d94192a28581925c 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -907,6 +907,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { @@ -187,7 +187,7 @@ index 77adc64e30cbc1d4542eb8f4a446788c1fdc61be..3c25436f158316d2e09cbf4673365edd return !this.canOversleep(); }); isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); -@@ -1175,13 +1194,16 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant1.3 test