Skip to content

Fix login compression setup ordering/race condition#13929

Open
Beaness wants to merge 1 commit into
PaperMC:mainfrom
Beaness:compressionfix
Open

Fix login compression setup ordering/race condition#13929
Beaness wants to merge 1 commit into
PaperMC:mainfrom
Beaness:compressionfix

Conversation

@Beaness
Copy link
Copy Markdown

@Beaness Beaness commented Jun 3, 2026

Netty does not guarantee that a write future listener will run before later packets are queued for write. This can cause packets send after the compression packet to not be compressed even though the client expects them to be compressed. When using nio/epoll the write future is often completed right after the write is ran so this race condition doesn't really happen, however I do suspect there might be cases where it can rarely happen (like kernel buffers being full)

However if you implement io uring support you'll notice that this race condition constantly happens. This is is due to the future being resolved way "later" due to the split in submission / completion queue in io uring. This explains why previous attempts to adding io uring support in Paper had issues with compression #9141

This pr fixes it by actually setting up the compression right after writing the compression packet.

tdlr: fixes login compression errors that are rare with NIO/epoll and common with io_uring

@Beaness Beaness requested a review from a team as a code owner June 3, 2026 13:28
@github-project-automation github-project-automation Bot moved this to Awaiting review in Paper PR Queue Jun 3, 2026
@Biquaternions
Copy link
Copy Markdown
Contributor

I also was investigating on this issue and made a similar fix to yours, although I handled it with the PacketSenderListener:

+            this.connection
+                .send(
+                    new ClientboundLoginCompressionPacket(this.server.getCompressionThreshold()),
+                    PacketSendListener.thenRun(() -> {
+                        this.connection.setupCompression(this.server.getCompressionThreshold(), true);
+                        this.connection.send(new ClientboundLoginFinishedPacket(gameProfile));
+                    })
+                );

Something I wanted to note, is that Mojang seems to have created a Netty adapter that does literally nothing at Connection#configurePacketHandler L681.
I have the unconfirmed suspicion that this handler, considering its name, was made by Mojang to probably delay the packet sending enough to let the compression/decompression handlers be registered, which apparently io_uring is outspeeding. A more complete fix might as well include removing this "hackfix", or better organize how these handlers are registered, similar to how is done in Velocity.
Hope the Paper team can give more insight on this.
This is the handler that I'm referring to:

    public void configurePacketHandler(final ChannelPipeline pipeline) {
        pipeline.addLast("hackfix", new ChannelOutboundHandlerAdapter() {
            @Override
            public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise) throws Exception {
                super.write(ctx, msg, promise);
            }
        }).addLast(HandlerNames.PACKET_HANDLER, this);
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Awaiting review

Development

Successfully merging this pull request may close these issues.

2 participants