From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MrHua269 Date: Wed, 8 Jul 2026 22:23:29 +0800 Subject: [PATCH] Async protocol switching optimization diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java index 40f54536422fa38bd84017fc634808016b4de58b..d0dff5850717bd083b7a29104a61754ce1b2f822 100644 --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java @@ -1067,4 +1067,127 @@ public class Connection extends SimpleChannelInboundHandler> { } } // Paper end - Optimize network + // Shiroha start - async protocol switcher + public void setupInboundProtocolAsync( + ProtocolInfo protocol, + T packetListener, + @Nullable Runnable callback, + boolean resumeAutoReading + ) { + this.validateListener(protocol, packetListener); + if (protocol.flow() != this.getReceiving()) { + throw new IllegalStateException("Invalid inbound protocol: " + protocol.id()); + } else { + this.packetListener = packetListener; + this.disconnectListener = null; + + UnconfiguredPipelineHandler.InboundConfigurationTask configMessage = UnconfiguredPipelineHandler.setupInboundProtocol(protocol); + BundlerInfo bundlerInfo = protocol.bundlerInfo(); + if (bundlerInfo != null) { + PacketBundlePacker newBundler = new PacketBundlePacker(bundlerInfo); + configMessage = configMessage.andThen(context -> context.pipeline().addAfter("decoder", "bundler", newBundler)); + } + + // Here we could execute it in event loop async to prevent waiting io task on main + // stop reading new packets to prevent some packets came into pipeline too early + this.channel.config().setAutoRead(false); + + // do our configuration task + final UnconfiguredPipelineHandler.InboundConfigurationTask finalInboundConfigurationTask = configMessage; + Runnable toExecute = () -> this.channel.writeAndFlush(finalInboundConfigurationTask).addListener(future -> { + try { + if (future.isSuccess()) { + if (callback != null) callback.run(); // retire callback if there have one + return; + } + + final Throwable ex = future.cause(); + + // here we process our exceptions like that blocking one + if (ex instanceof ClosedChannelException) { + LOGGER.info("Connection closed during protocol change"); + } else { + this.channel.pipeline().fireExceptionCaught(ex); + } + }finally { + // reset auto back and resume reading if needed + if (resumeAutoReading) { + this.channel.config().setAutoRead(true); + this.channel.read(); + } + } + }); + + // we need to do this inside the event loop + if (!this.channel.eventLoop().inEventLoop()) { + this.channel.eventLoop().execute(toExecute); + return; + } + + toExecute.run(); + } + } + + public void setupOutboundProtocolAsync( + ProtocolInfo protocol, + @Nullable Runnable callback, + boolean resumeAutoReading + ) { + if (protocol.flow() != this.getSending()) { + throw new IllegalStateException("Invalid outbound protocol: " + protocol.id()); + } else { + UnconfiguredPipelineHandler.OutboundConfigurationTask configMessage = UnconfiguredPipelineHandler.setupOutboundProtocol(protocol); + BundlerInfo bundlerInfo = protocol.bundlerInfo(); + if (bundlerInfo != null) { + PacketBundleUnpacker newUnbundler = new PacketBundleUnpacker(bundlerInfo); + configMessage = configMessage.andThen( + context -> context.pipeline().addAfter("encoder", "unbundler", newUnbundler) + ); + } + + boolean isLoginProtocol = protocol.id() == ConnectionProtocol.LOGIN; + + // Here we could execute it in event loop async to prevent waiting io task on main + // stop reading new packets to prevent some packets came into pipeline too early + this.channel.config().setAutoRead(false); + + // do our configuration task + final UnconfiguredPipelineHandler.OutboundConfigurationTask finalOutboundConfigurationTask = configMessage; + final Runnable writeTask = () -> this.channel.writeAndFlush( + finalOutboundConfigurationTask.andThen(context -> this.sendLoginDisconnect = isLoginProtocol) + ).addListener(future -> { + try { + if (future.isSuccess()) { + if (callback != null) callback.run(); // retire callback if there have one + return; + } + + final Throwable ex = future.cause(); + + // here we process our exceptions like that blocking one + if (ex instanceof ClosedChannelException) { + LOGGER.info("Connection closed during protocol change"); + } else { + this.channel.pipeline().fireExceptionCaught(ex); + } + }finally { + // reset auto back and resume reading if needed + if (resumeAutoReading) { + this.channel.config().setAutoRead(true); + this.channel.read(); // read once + } + } + }); + + // we need to do this inside the event loop + if (!this.channel.eventLoop().inEventLoop()) { + this.channel.eventLoop().execute(writeTask); + return; + } + + writeTask.run(); + } + } + // Shiroha end + } diff --git a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java index efcf294b523ca6b039eb2544546bf863ba717749..343c98fb35eb4cb736d26f67a11c00efb8622cf8 100644 --- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java @@ -188,8 +188,9 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis public void handleConfigurationFinished(final ServerboundFinishConfigurationPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.server.packetProcessor()); this.finishCurrentTask(JoinWorldTask.TYPE); - this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()))); + // this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()))); // Shiroha - Async protocol switch - move down + Runnable afterSwitch = () -> { // Shiroha - Async protocol switch try { PlayerList playerList = this.server.getPlayerList(); if (playerList.getPlayer(this.gameProfile.id()) != null) { @@ -235,6 +236,18 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis LOGGER.error("Couldn't place player in world", e); this.disconnect(DISCONNECT_REASON_INVALID_DATA); } + // Shiroha start - Async protocol switch + }; + if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { + this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()))); + afterSwitch.run(); // directly run callback as we won't process any packet this time + } else { + this.connection.setupOutboundProtocolAsync(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess())), () -> { + // push back + io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(afterSwitch); + }, false); // we will start auto read once we set up inbound handler at placeNewPlayer in PlayerList + } + // Shiroha end } @Override diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java index eda7e765c7c2ffee305edc81e0c7a6b1e5bfa18d..4c9ea818be83ed3c4dacc762f4d0e8c0f177cd19 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -2884,7 +2884,13 @@ public class ServerGamePacketListenerImpl } // Folia end - rewrite login process - move connection ownership to global region this.waitingForSwitchToConfig = true; // Folia - rewrite login process - fix bad ordering of this field write - moved down this.send(ClientboundStartConfigurationPacket.INSTANCE); + if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Shiroha - Async protocol switch this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); + // Shiroha start - Async protcol switch + } else { + this.connection.setupOutboundProtocolAsync(ConfigurationProtocols.CLIENTBOUND, null, true); + } + // Shiroha end } @Override @@ -3769,12 +3775,26 @@ public class ServerGamePacketListenerImpl } final ServerConfigurationPacketListenerImpl listener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation())); // Paper + if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Shiroha - Async protocol switch this.connection .setupInboundProtocol( ConfigurationProtocols.SERVERBOUND, listener // Paper ); new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper + } // Shiroha - Async protocol switch - add "{" + // Shiroha start - Async protocol switch - move up + else + this.connection.setupInboundProtocolAsync( + ConfigurationProtocols.SERVERBOUND, + listener, + () -> { + new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper + }, + true + ); + // Shiroha end + // new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper // Shiroha - Async protocol switch - move up } @Override diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java index f2329d1a9d9d4bf4c9d771e25e54f2f9ef65a76c..7de3bbecea2f11e4e1cb65408729f346760dc9b8 100644 --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -435,12 +435,30 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, public void handleLoginAcknowledgement(final ServerboundLoginAcknowledgedPacket packet) { net.minecraft.network.protocol.PacketUtils.ensureRunningOnSameThread(packet, this, this.server.packetProcessor()); // CraftBukkit Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet"); - this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); + /*this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); // Shiroha - Async protocol switch - Rewrite CommonListenerCookie cookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred); ServerConfigurationPacketListenerImpl configPacketListener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, cookie); this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, configPacketListener); configPacketListener.startConfiguration(); - this.state = ServerLoginPacketListenerImpl.State.ACCEPTED; + this.state = ServerLoginPacketListenerImpl.State.ACCEPTED;*/ // Shiroha - Async protocol switch - Rewrite + + // Shiroha start - Async protocol switch + CommonListenerCookie cookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred); + ServerConfigurationPacketListenerImpl configPacketListener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, cookie); + + Runnable afterSwitch = () -> io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(configPacketListener::startConfiguration); // push back to main thread + + if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { + this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); + this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, configPacketListener); + afterSwitch.run(); + return; + } + + this.connection.setupInboundProtocolAsync(ConfigurationProtocols.SERVERBOUND, configPacketListener, () -> { + this.connection.setupOutboundProtocolAsync(ConfigurationProtocols.CLIENTBOUND, afterSwitch, true); // start auto read when everything is ready + }, false); // we will resume auto reading once the outbound protocol is also setup + // Shiroha end } @Override diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java index e7579a9873362317dd82b63306cf650af9472574..9e90197b96fcac958c5073ca8f560e77bb811460 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -237,9 +237,11 @@ public abstract class PlayerList { // only after setting the connection listener to game type, add the connection to this regions list level.getCurrentWorldData().connections.add(connection); // Folia end - rewrite login process + if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Shiroha - Async protocol switch // we will run async switch once these main thread logics became done connection.setupInboundProtocol( GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()), playerConnection), playerConnection ); + } // Shiroha - Async protocol switch playerConnection.suspendFlushing(); GameRules gameRules = level.getGameRules(); boolean immediateRespawn = gameRules.get(GameRules.IMMEDIATE_RESPAWN); @@ -403,6 +405,17 @@ public abstract class PlayerList { ); } // Paper end - Send empty chunk + // Shiroha start - Async protocol switch + if (io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { + // auto read will be enabled once the async switch is done + connection.setupInboundProtocolAsync( + GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()), playerConnection), + playerConnection, + null, // we don't need to do anything more + true // start auto read which we have disabled in configuration handler + ); + } + // Shiroha end } public void updateEntireScoreboard(final ServerScoreboard scoreboard, final ServerPlayer player) {