Files
Shiroha/shiroha-server/minecraft-patches/features/0034-Async-protocol-switching-optimization.patch
T
NanaChiyo0721 1bb895c165
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
Fix incorrect ticket lock size computing
Yeah this would fully solve the issue of chunk unloading race condition

The same fix was already initially added at "Force clamp grid-exponent to 1~6"
2026-08-09 14:57:32 +08:00

289 lines
16 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
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..d3fb20f465c0b69662d81b53201963634d8b53d2 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -1067,4 +1067,127 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
}
// Paper end - Optimize network
+ // Shiroha start - async protocol switcher
+ public <T extends PacketListener> void setupInboundProtocolAsync(
+ ProtocolInfo<T> 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 0485ce2a56adedc200cc0cd441df7cce88da66a8..344d2aa66f1fb720ca7abcc426ef438e8651ecd4 100644
--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
@@ -197,8 +197,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) {
@@ -244,6 +245,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 c86b1fd5e200c478d178be32791b6e8c960d47e2..862a764889ed9ca985afdf672aebeb342c18b0c2 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -2898,7 +2898,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
@@ -3783,12 +3789,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..1b4229085f9d4189efb20c083bbfe2e6712e72a4 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 259eb7574841b1b885093b534440f8a40db4e8e2..fcfa2b7b6e8f4c00763b8c23d6eb2699aced3e75 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().addConnection(player);
// 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) {