diff options
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.patch | 390 |
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); |