aboutsummaryrefslogtreecommitdiffhomepage
path: root/Spigot-Server-Patches-Unmapped/0329-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
diff options
context:
space:
mode:
Diffstat (limited to 'Spigot-Server-Patches-Unmapped/0329-Optimize-Network-Manager-and-add-advanced-packet-sup.patch')
-rw-r--r--Spigot-Server-Patches-Unmapped/0329-Optimize-Network-Manager-and-add-advanced-packet-sup.patch390
1 files changed, 390 insertions, 0 deletions
diff --git a/Spigot-Server-Patches-Unmapped/0329-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/Spigot-Server-Patches-Unmapped/0329-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
new file mode 100644
index 0000000000..053340619f
--- /dev/null
+++ b/Spigot-Server-Patches-Unmapped/0329-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
@@ -0,0 +1,390 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <[email protected]>
+Date: Wed, 6 May 2020 04:53:35 -0400
+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 a packet to delay sending more packets until a state is ready.
+
+Removes synchronization from sending packets
+Removes processing packet queue off of main thread
+ - for the few cases where it is allowed, order is not necessary nor
+ should it even be happening concurrently in first place (handshaking/login/status)
+
+Ensures packets sent asynchronously are dispatched on main thread
+
+This helps ensure safety for ProtocolLib as packet listeners
+are commonly accessing world state. This will allow you to schedule
+a packet to be sent async, but itll be dispatched sync for packet
+listeners to process.
+
+This should solve some deadlock risks
+
+Also adds Netty Channel Flush Consolidation to reduce the amount of flushing
+
+Also avoids spamming closed channel exception by rechecking closed state in dispatch
+and then catch exceptions and close if they fire.
+
+Part of this commit was authored by: Spottedleaf
+
+diff --git a/src/main/java/net/minecraft/network/NetworkManager.java b/src/main/java/net/minecraft/network/NetworkManager.java
+index 6d40ade5a52383ed86d28d272c3dc83dbdcbd218..ab70eeaeca222de7de7cab1b3db14b2c4761c3c3 100644
+--- a/src/main/java/net/minecraft/network/NetworkManager.java
++++ b/src/main/java/net/minecraft/network/NetworkManager.java
+@@ -25,8 +25,15 @@ import net.minecraft.network.chat.ChatMessage;
+ import net.minecraft.network.chat.IChatBaseComponent;
+ import net.minecraft.network.protocol.EnumProtocolDirection;
+ import net.minecraft.network.protocol.Packet;
++import net.minecraft.network.protocol.game.PacketPlayOutBoss;
++import net.minecraft.network.protocol.game.PacketPlayOutChat;
++import net.minecraft.network.protocol.game.PacketPlayOutKeepAlive;
+ import net.minecraft.network.protocol.game.PacketPlayOutKickDisconnect;
++import net.minecraft.network.protocol.game.PacketPlayOutTabComplete;
++import net.minecraft.network.protocol.game.PacketPlayOutTitle;
+ import net.minecraft.server.CancelledPacketHandleException;
++import net.minecraft.server.MCUtil;
++import net.minecraft.server.level.EntityPlayer;
+ import net.minecraft.server.network.LoginListener;
+ import net.minecraft.server.network.PlayerConnection;
+ import net.minecraft.util.LazyInitVar;
+@@ -75,6 +82,10 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
+ public int protocolVersion;
+ public java.net.InetSocketAddress virtualHost;
+ private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush");
++ // Optimize network
++ public boolean isPending = true;
++ public boolean queueImmunity = false;
++ public EnumProtocol protocol;
+ // Paper end
+
+ public NetworkManager(EnumProtocolDirection enumprotocoldirection) {
+@@ -98,6 +109,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
+ }
+
+ public void setProtocol(EnumProtocol enumprotocol) {
++ protocol = enumprotocol; // Paper
+ this.channel.attr(NetworkManager.c).set(enumprotocol);
+ this.channel.config().setAutoRead(true);
+ NetworkManager.LOGGER.debug("Enabled auto read");
+@@ -168,19 +180,84 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
+ Validate.notNull(packetlistener, "packetListener", new Object[0]);
+ this.packetListener = packetlistener;
+ }
++ // Paper start
++ public 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<Packet> buildExtraPackets(Packet packet) {
++ java.util.List<Packet> extra = packet.getExtraPackets();
++ if (extra == null || extra.isEmpty()) {
++ return null;
++ }
++ java.util.List<Packet> ret = new java.util.ArrayList<>(1 + extra.size());
++ buildExtraPackets0(extra, ret);
++ return ret;
++ }
++
++ private static void buildExtraPackets0(java.util.List<Packet> extraPackets, java.util.List<Packet> into) {
++ for (Packet extra : extraPackets) {
++ into.add(extra);
++ java.util.List<Packet> extraExtra = extra.getExtraPackets();
++ if (extraExtra != null && !extraExtra.isEmpty()) {
++ buildExtraPackets0(extraExtra, into);
++ }
++ }
++ }
++ // Paper start
++ private static boolean canSendImmediate(NetworkManager networkManager, Packet<?> packet) {
++ return networkManager.isPending || networkManager.protocol != EnumProtocol.PLAY ||
++ packet instanceof PacketPlayOutKeepAlive ||
++ packet instanceof PacketPlayOutChat ||
++ packet instanceof PacketPlayOutTabComplete ||
++ packet instanceof PacketPlayOutTitle ||
++ packet instanceof PacketPlayOutBoss;
++ }
++ // Paper end
++ }
++ // Paper end
+
+ public void sendPacket(Packet<?> packet) {
+ this.sendPacket(packet, (GenericFutureListener) null);
+ }
+
+ public void sendPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {
+- if (this.isConnected()) {
+- this.p();
+- this.b(packet, genericfuturelistener);
+- } else {
+- this.packetQueue.add(new NetworkManager.QueuedPacket(packet, genericfuturelistener));
++ // Paper start - handle oversized packets better
++ boolean connected = this.isConnected();
++ if (!connected && !preparing) {
++ 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())
++ ))) {
++ this.dispatchPacket(packet, genericfuturelistener);
++ return;
+ }
++ // write the packets to the queue, then flush - antixray hooks there already
++ java.util.List<Packet> extraPackets = InnerUtil.buildExtraPackets(packet);
++ boolean hasExtraPackets = extraPackets != null && !extraPackets.isEmpty();
++ if (!hasExtraPackets) {
++ this.packetQueue.add(new NetworkManager.QueuedPacket(packet, genericfuturelistener));
++ } else {
++ 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
++
++ 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();
++ // Paper end
+ }
+
+ private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER
+@@ -194,51 +271,116 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
+ this.channel.config().setAutoRead(false);
+ }
+
++ EntityPlayer player = getPlayer(); // Paper
+ if (this.channel.eventLoop().inEventLoop()) {
+ if (enumprotocol != enumprotocol1) {
+ this.setProtocol(enumprotocol);
+ }
++ // Paper start
++ if (!isConnected()) {
++ packet.onPacketDispatchFinish(player, null);
++ return;
++ }
++ try {
++ // Paper end
+
+ ChannelFuture channelfuture = this.channel.writeAndFlush(packet);
+
+ if (genericfuturelistener != null) {
+ channelfuture.addListener(genericfuturelistener);
+ }
++ // Paper start
++ if (packet.hasFinishListener()) {
++ channelfuture.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture));
++ }
++ // Paper end
+
+ channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
++ // Paper start
++ } catch (Exception e) {
++ LOGGER.error("NetworkException: " + player, e);
++ close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));;
++ packet.onPacketDispatchFinish(player, null);
++ }
++ // Paper end
+ } else {
+ this.channel.eventLoop().execute(() -> {
+ if (enumprotocol != enumprotocol1) {
+ this.setProtocol(enumprotocol);
+ }
+
++ // Paper start
++ if (!isConnected()) {
++ packet.onPacketDispatchFinish(player, null);
++ return;
++ }
++ try {
++ // Paper end
+ ChannelFuture channelfuture1 = this.channel.writeAndFlush(packet);
+
++
+ if (genericfuturelistener != null) {
+ channelfuture1.addListener(genericfuturelistener);
+ }
++ // Paper start
++ if (packet.hasFinishListener()) {
++ channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture));
++ }
++ // Paper end
+
+ channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
++ // Paper start
++ } catch (Exception e) {
++ LOGGER.error("NetworkException: " + player, e);
++ close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));;
++ packet.onPacketDispatchFinish(player, null);
++ }
++ // Paper end
+ });
+ }
+
+ }
+
+- private void sendPacketQueue() { this.p(); } // Paper - OBFHELPER
+- private void p() {
+- if (this.channel != null && this.channel.isOpen()) {
+- Queue queue = this.packetQueue;
+-
++ // Paper start - rewrite this to be safer if ran off main thread
++ private boolean sendPacketQueue() { return this.p(); } // OBFHELPER // void -> boolean
++ private boolean p() { // void -> boolean
++ if (!isConnected()) {
++ return true;
++ }
++ if (MCUtil.isMainThread()) {
++ return processQueue();
++ } else if (isPending) {
++ // Should only happen during login/status stages
+ synchronized (this.packetQueue) {
+- NetworkManager.QueuedPacket networkmanager_queuedpacket;
+-
+- while ((networkmanager_queuedpacket = (NetworkManager.QueuedPacket) this.packetQueue.poll()) != null) {
+- this.b(networkmanager_queuedpacket.a, networkmanager_queuedpacket.b);
+- }
++ return this.processQueue();
++ }
++ }
++ return false;
++ }
++ private boolean processQueue() {
++ if (this.packetQueue.isEmpty()) return true;
++ // If we are on main, we are safe here in that nothing else should be processing queue off main anymore
++ // But if we are not on main due to login/status, the parent is synchronized on packetQueue
++ java.util.Iterator<QueuedPacket> iterator = this.packetQueue.iterator();
++ while (iterator.hasNext()) {
++ NetworkManager.QueuedPacket queued = iterator.next(); // poll -> peek
++
++ // Fix NPE (Spigot bug caused by handleDisconnection())
++ if (queued == null) {
++ return true;
++ }
+
++ Packet<?> packet = queued.getPacket();
++ if (!packet.isReady()) {
++ return false;
++ } else {
++ iterator.remove();
++ this.dispatchPacket(packet, queued.getGenericFutureListener());
+ }
+ }
++ return true;
+ }
++ // Paper end
+
+ public void a() {
+ this.p();
+@@ -271,9 +413,21 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
+ return this.socketAddress;
+ }
+
++ // 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;
++ clearPacketQueue(); // Paper
+ // Spigot End
+ if (this.channel.isOpen()) {
+ this.channel.close(); // We can't wait as this may be called from an event loop.
+@@ -341,7 +495,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
+ public void handleDisconnection() {
+ if (this.channel != null && !this.channel.isOpen()) {
+ if (this.o) {
+- NetworkManager.LOGGER.warn("handleDisconnection() called twice");
++ //NetworkManager.LOGGER.warn("handleDisconnection() called twice"); // Paper - Do not log useless message
+ } else {
+ this.o = true;
+ if (this.k() != null) {
+@@ -349,7 +503,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
+ } else if (this.j() != null) {
+ this.j().a(new ChatMessage("multiplayer.disconnect.generic"));
+ }
+- this.packetQueue.clear(); // Free up packet queue.
++ clearPacketQueue(); // Paper
+ // Paper start - Add PlayerConnectionCloseEvent
+ final PacketListener packetListener = this.j();
+ if (packetListener instanceof PlayerConnection) {
+diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java
+index 0783b0777c8d7788bbf6780b464b709bf6dc2191..b644c91cecd8a347319dfe8c8923fd05919a9795 100644
+--- a/src/main/java/net/minecraft/network/protocol/Packet.java
++++ b/src/main/java/net/minecraft/network/protocol/Packet.java
+@@ -1,5 +1,6 @@
+ package net.minecraft.network.protocol;
+
++import io.netty.channel.ChannelFuture; // Paper
+ import java.io.IOException;
+ import net.minecraft.network.PacketDataSerializer;
+ import net.minecraft.network.PacketListener;
+@@ -13,6 +14,20 @@ public interface Packet<T extends PacketListener> {
+ void a(T t0);
+
+ // Paper start
++
++ /**
++ * @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 ChannelFuture future) {}
++ default boolean hasFinishListener() { return false; }
++ default boolean isReady() { return true; }
++ default java.util.List<Packet> getExtraPackets() { return null; }
+ default boolean packetTooLarge(NetworkManager manager) {
+ return false;
+ }
+diff --git a/src/main/java/net/minecraft/server/network/ServerConnection.java b/src/main/java/net/minecraft/server/network/ServerConnection.java
+index d992cb5cd827e0fe655809e1088939cdad9c2301..dc362724ea0cc1b2f9d9ceffff483217b4356c40 100644
+--- a/src/main/java/net/minecraft/server/network/ServerConnection.java
++++ b/src/main/java/net/minecraft/server/network/ServerConnection.java
+@@ -16,6 +16,7 @@ import io.netty.channel.epoll.EpollServerSocketChannel;
+ import io.netty.channel.nio.NioEventLoopGroup;
+ import io.netty.channel.socket.ServerSocketChannel;
+ import io.netty.channel.socket.nio.NioServerSocketChannel;
++import io.netty.handler.flush.FlushConsolidationHandler; // Paper
+ import io.netty.handler.timeout.ReadTimeoutHandler;
+ import java.io.IOException;
+ import java.net.InetAddress;
+@@ -54,10 +55,12 @@ public class ServerConnection {
+ private final List<NetworkManager> connectedChannels = Collections.synchronizedList(Lists.newArrayList());
+ // Paper start - prevent blocking on adding a new network manager while the server is ticking
+ private final java.util.Queue<NetworkManager> pending = new java.util.concurrent.ConcurrentLinkedQueue<>();
++ private static final boolean disableFlushConsolidation = Boolean.getBoolean("Paper.disableFlushConsolidate"); // Paper
+ private void addPending() {
+ NetworkManager manager = null;
+ while ((manager = pending.poll()) != null) {
+ connectedChannels.add(manager);
++ manager.isPending = false;
+ }
+ }
+ // Paper end
+@@ -92,6 +95,7 @@ public class ServerConnection {
+ ;
+ }
+
++ if (!disableFlushConsolidation) channel.pipeline().addFirst(new FlushConsolidationHandler()); // Paper
+ channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("legacy_query", new LegacyPingHandler(ServerConnection.this)).addLast("splitter", new PacketSplitter()).addLast("decoder", new PacketDecoder(EnumProtocolDirection.SERVERBOUND)).addLast("prepender", new PacketPrepender()).addLast("encoder", new PacketEncoder(EnumProtocolDirection.CLIENTBOUND));
+ int j = ServerConnection.this.e.k();
+ Object object = j > 0 ? new NetworkManagerServer(j) : new NetworkManager(EnumProtocolDirection.SERVERBOUND);