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.
This commit is contained in:
Aikar 2020-05-16 21:38:19 -04:00
parent 4e7644ce04
commit 90072b811c
7 changed files with 284 additions and 163 deletions

View File

@ -1587,11 +1587,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // apply fixes + // apply fixes
+ +
+ try { + try {
+ if (chunkData.poiData != null) {
+ chunkData.poiData = chunkData.poiData.clone(); // clone data for safety, file IO thread does not clone
+ }
+ chunkData.chunkData = chunkManager.getChunkData(this.world.getWorldProvider().getDimensionManager(), + chunkData.chunkData = chunkManager.getChunkData(this.world.getWorldProvider().getDimensionManager(),
+ chunkManager.getWorldPersistentDataSupplier(), chunkData.chunkData.clone(), chunkPos, this.world); // clone data for safety, file IO thread does not clone + chunkManager.getWorldPersistentDataSupplier(), chunkData.chunkData, chunkPos, this.world); // clone data for safety, file IO thread does not clone
+ } catch (final Throwable ex) { + } catch (final Throwable ex) {
+ PaperFileIOThread.LOGGER.error("Could not apply datafixers for chunk task: " + this.toString(), ex); + PaperFileIOThread.LOGGER.error("Could not apply datafixers for chunk task: " + this.toString(), ex);
+ this.complete(ChunkLoadTask.createEmptyHolder()); + this.complete(ChunkLoadTask.createEmptyHolder());

View File

@ -226,7 +226,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+import java.util.Set; +import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue;
@@ -0,0 +0,0 @@ public final class MCUtil { @@ -0,0 +0,0 @@ public final class MCUtil {
return null; return null;

View File

@ -19,7 +19,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ <version>8.0.1</version> <!-- Paper --> + <version>8.0.1</version> <!-- Paper -->
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- deprecated API depend --> <dependency>
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java --- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java

View File

@ -2083,84 +2083,167 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@@ -0,0 +0,0 @@ @@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.pooled; +package com.destroystokyo.paper.util.pooled;
+ +
+import net.minecraft.server.MCUtil;
+import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.commons.lang3.mutable.MutableInt;
+
+import java.util.ArrayDeque; +import java.util.ArrayDeque;
+import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantLock; +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 <aikar@aikar.co>
+ * @license MIT
+ */
+public final class PooledObjects<E> { +public final class PooledObjects<E> {
+ +
+ public static final PooledObjects<MutableInt> 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<MutableInt> POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 1024, 16);
+ +
+ private final PooledObjectHandler<E> handler; + private final PooledObjectHandler<E> handler;
+ private final int maxPoolSize; + private final int bucketCount;
+ private final int expectingThreads; + private final int bucketSize;
+ private final ArrayDeque<E>[] buckets;
+ private final ReentrantLock[] locks;
+ private final AtomicLong bucketIdCounter = new AtomicLong(0);
+ +
+ private final IsolatedPool<E> mainPool; + public PooledObjects(final PooledObjectHandler<E> handler, int maxPoolSize) {
+ // use these under contention + this(handler, maxPoolSize, 8);
+ private final IsolatedPool<E>[] contendedPools; + }
+ + public PooledObjects(final PooledObjectHandler<E> handler, int maxPoolSize, int bucketCount) {
+ public PooledObjects(final PooledObjectHandler<E> handler, final int maxPoolSize, int expectingThreads) {
+ if (handler == null) { + if (handler == null) {
+ throw new NullPointerException("Handler must not be null"); + throw new NullPointerException("Handler must not be null");
+ } + }
+ if (maxPoolSize <= 0) { + if (maxPoolSize <= 0) {
+ throw new IllegalArgumentException("Max pool size must be greater-than 0"); + throw new IllegalArgumentException("Max pool size must be greater-than 0");
+ } + }
+ if (expectingThreads <= 0) { + if (bucketCount < 1) {
+ expectingThreads = Runtime.getRuntime().availableProcessors(); + 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.handler = handler;
+ this.maxPoolSize = maxPoolSize; + this.bucketSize = maxPoolSize / bucketCount;
+ this.expectingThreads = expectingThreads; + for (int i = 0; i < bucketCount; i++) {
+ this.mainPool = new IsolatedPool<>(handler, maxPoolSize); + this.buckets[i] = new ArrayDeque<>(bucketSize / 4);
+ final IsolatedPool<E>[] contendedPools = new IsolatedPool[2 * expectingThreads]; + this.locks[i] = new ReentrantLock();
+ }
+ }
+ +
+ for (int i = 0; i < contendedPools.length; ++i) { + public AutoReleased acquireCleaner(Object holder) {
+ contendedPools[i] = new IsolatedPool<>(handler, Math.max(1, maxPoolSize / 2)); + return acquireCleaner(holder, this::release);
+ }
+
+ public AutoReleased acquireCleaner(Object holder, Consumer<E> 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() { + public E acquire() {
+ E ret; + for (int base = (int) (this.bucketIdCounter.getAndIncrement() % bucketCount), i = 0; i < bucketCount; i++ ) {
+ PooledObjects.IsolatedPool<E> pooled = this.mainPool; + int bucketId = (base + i) % bucketCount;
+ int lastIndex = -1; + if (this.buckets[bucketId].isEmpty()) continue;
+ while ((ret = pooled.tryAcquireUncontended()) == null) { + // lock will alloc an object if blocked, so spinwait instead since lock duration is super fast
+ int index; + lockBucket(bucketId);
+ while (lastIndex == (index = fastRandomBounded(ThreadLocalRandom.current().nextInt() & 0xFFFFFFFFL, this.contendedPools.length))); + E value = this.buckets[bucketId].poll();
+ lastIndex = index; + this.locks[bucketId].unlock();
+ pooled = this.contendedPools[index]; + 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) { + public void release(final E value) {
+ PooledObjects.IsolatedPool<E> pooled = this.mainPool; + int attempts = 3; // cap on contention
+ int lastIndex = -1; + do {
+ while (!pooled.tryReleaseUncontended(value)) { + // find least filled bucket before locking
+ int index; + int smallestIdx = -1;
+ while (lastIndex == (index = fastRandomBounded(ThreadLocalRandom.current().nextInt() & 0xFFFFFFFFL, this.contendedPools.length))); + int smallest = Integer.MAX_VALUE;
+ lastIndex = index; + for (int i = 0; i < bucketCount; i++ ) {
+ pooled = this.contendedPools[index]; + ArrayDeque<E> 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<E> 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 */ + /** This object is restricted from interacting with any pool */
+ public static interface PooledObjectHandler<E> { + public interface PooledObjectHandler<E> {
+ +
+ /** + /**
+ * Must return a non-null object + * Must return a non-null object
@ -2171,89 +2254,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+ default void onRelease(final E value) {} + default void onRelease(final E value) {}
+ } + }
+
+ protected static class IsolatedPool<E> {
+
+ protected final PooledObjectHandler<E> handler;
+
+ // We use arraydeque as it doesn't create garbage per element...
+ protected final ArrayDeque<E> pool;
+ protected final int maxPoolSize;
+
+ protected final ReentrantLock lock = new ReentrantLock();
+
+ public IsolatedPool(final PooledObjectHandler<E> 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 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 new file mode 100644
@ -3355,21 +3355,80 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+import java.util.Queue; +import java.util.Queue;
+import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.function.Supplier; +import java.util.function.Supplier;
+ +
+public final class MCUtil { +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<Runnable>(),
+ 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<Runnable>(),
+ new ThreadFactoryBuilder().setNameFormat("Paper Object Cleaner").build()
+ );
+ +
+ public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE); + 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()) { + public static Runnable once(Runnable run) {
+ throw new IllegalStateException(reason); + 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 <T>
+ * @return
+ */
+ public static <T> Runnable registerListCleaner(Object obj, List<T> list, Consumer<T> 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 <T>
+ * @return
+ */
+ public static <T> Runnable registerCleaner(Object obj, T resource, java.util.function.Consumer<T> cleaner) {
+ return registerCleaner(obj, () -> cleaner.accept(resource));
+ } + }
+ +
+ public static List<ChunkCoordIntPair> getSpiralOutChunks(BlockPosition blockposition, int radius) { + public static List<ChunkCoordIntPair> getSpiralOutChunks(BlockPosition blockposition, int radius) {
@ -3753,6 +3812,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ } + }
+} +}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
}
// Spigot start
+ MCUtil.asyncExecutor.shutdown(); // Paper
+ try { MCUtil.asyncExecutor.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper
+ } catch (java.lang.InterruptedException ignored) {} // Paper
if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
LOGGER.info("Saving usercache.json");
this.getUserCache().c();
diff --git a/src/main/java/net/minecraft/server/NBTTagCompound.java b/src/main/java/net/minecraft/server/NBTTagCompound.java diff --git a/src/main/java/net/minecraft/server/NBTTagCompound.java b/src/main/java/net/minecraft/server/NBTTagCompound.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/NBTTagCompound.java --- a/src/main/java/net/minecraft/server/NBTTagCompound.java

View File

@ -5,7 +5,6 @@ Subject: [PATCH] Optimize Network Manager and add advanced packet support
Adds ability for 1 packet to bundle other packets to follow it Adds ability for 1 packet to bundle other packets to follow it
Adds ability for a packet to delay sending more packets until a state is ready. Adds ability for a packet to delay sending more packets until a state is ready.
Adds ability to clean up a packet when it is finished (not sent, or finished encoding), such as freeing buffers
Removes synchronization from sending packets Removes synchronization from sending packets
Removes processing packet queue off of main thread Removes processing packet queue off of main thread
@ -51,6 +50,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.packetListener = packetlistener; this.packetListener = packetlistener;
} }
+ // Paper start + // 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 class InnerUtil { // Attempt to hide these methods from ProtocolLib so it doesn't accidently pick them up.
+ private static java.util.List<Packet> buildExtraPackets(Packet packet) { + private static java.util.List<Packet> buildExtraPackets(Packet packet) {
+ java.util.List<Packet> extra = packet.getExtraPackets(); + java.util.List<Packet> extra = packet.getExtraPackets();
@ -95,9 +101,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper start - handle oversized packets better + // Paper start - handle oversized packets better
+ boolean connected = this.isConnected(); + boolean connected = this.isConnected();
+ if (!connected && !preparing) { + if (!connected && !preparing) {
+ packet.onPacketDone();
+ return; // Do nothing + return; // Do nothing
+ } + }
+ packet.onPacketDispatch(getPlayer());
+ if (connected && (InnerUtil.canSendImmediate(this, packet) || ( + if (connected && (InnerUtil.canSendImmediate(this, packet) || (
+ MCUtil.isMainThread() && packet.isReady() && this.packetQueue.isEmpty() && + MCUtil.isMainThread() && packet.isReady() && this.packetQueue.isEmpty() &&
+ (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) + (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())
@ -113,13 +119,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } else { + } else {
+ java.util.List<NetworkManager.QueuedPacket> packets = new java.util.ArrayList<>(1 + extraPackets.size()); + java.util.List<NetworkManager.QueuedPacket> 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 + 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;) { + for (int i = 0, len = extraPackets.size(); i < len;) {
+ Packet extra = extraPackets.get(i); + Packet extra = extraPackets.get(i);
+ boolean end = ++i == len; + boolean end = ++i == len;
+ packets.add(new NetworkManager.QueuedPacket(extra, end ? genericfuturelistener : null)); // append listener to the end + packets.add(new NetworkManager.QueuedPacket(extra, end ? genericfuturelistener : null)); // append listener to the end
+ } + }
+
+ this.packetQueue.addAll(packets); // atomic + this.packetQueue.addAll(packets); // atomic
+ } + }
+ this.sendPacketQueue(); + this.sendPacketQueue();
@ -128,6 +134,30 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER
@@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> { @@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
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 {
@@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
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);
});
@@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
} }
@ -188,7 +218,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
return this.socketAddress; 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) { public void close(IChatBaseComponent ichatbasecomponent) {
// Spigot Start // Spigot Start
this.preparing = false; this.preparing = false;
@ -213,25 +253,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
void a(T t0); void a(T t0);
// Paper start // 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 boolean isReady() { return true; }
+ default java.util.List<Packet> getExtraPackets() { return null; } + default java.util.List<Packet> getExtraPackets() { return null; }
default boolean packetTooLarge(NetworkManager manager) { default boolean packetTooLarge(NetworkManager manager) {
return false; return false;
} }
diff --git a/src/main/java/net/minecraft/server/PacketEncoder.java b/src/main/java/net/minecraft/server/PacketEncoder.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/PacketEncoder.java
+++ b/src/main/java/net/minecraft/server/PacketEncoder.java
@@ -0,0 +0,0 @@ public class PacketEncoder extends MessageToByteEncoder<Packet<?>> {
} 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 diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java --- a/src/main/java/net/minecraft/server/PlayerList.java

View File

@ -14,7 +14,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/server/MinecraftServer.java --- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas @@ -0,0 +0,0 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
// Spigot start } catch (java.lang.InterruptedException ignored) {} // Paper
if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
LOGGER.info("Saving usercache.json"); LOGGER.info("Saving usercache.json");
- this.getUserCache().c(); - this.getUserCache().c();

View File

@ -56,6 +56,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -0,0 +0,0 @@ @@ -0,0 +0,0 @@
<version>7.3.1</version>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <!-- wrapper to use either java 8 sun cleaner or java9+ official cleaner -->
+ <groupId>co.aikar</groupId>
+ <artifactId>cleaner</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
<!-- deprecated API depend -->
<dependency>
<groupId>com.googlecode.json-simple</groupId>
@@ -0,0 +0,0 @@
<!-- This builds a completely 'ready to start' jar with all dependencies inside --> <!-- This builds a completely 'ready to start' jar with all dependencies inside -->
<build> <build>