Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split incoming/outgoing packet registry, transition protocol states correctly #841

Open
wants to merge 55 commits into
base: master
Choose a base branch
from

Conversation

AlexProgrammerDE
Copy link
Contributor

@AlexProgrammerDE AlexProgrammerDE commented Jul 20, 2024

These changes are necessary to avoid race-conditions in the code and to prepare the codebase for ViaVersion support.

@AlexProgrammerDE AlexProgrammerDE marked this pull request as draft July 20, 2024 20:35
@AlexProgrammerDE AlexProgrammerDE marked this pull request as ready for review July 26, 2024 07:52
@AlexProgrammerDE
Copy link
Contributor Author

PR is ready for review

Copy link
Member

@Konicai Konicai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, especially like the cleanup in the listeners

Comment on lines +182 to +184
// The developer who sends ClientboundStartConfigurationPacket needs to setOutboundState to CONFIGURATION
// after sending the packet. We can't do it in this class because it needs to be a method call right after it was sent.
// Using nettys event loop to change outgoing state may cause differences to vanilla.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we not check for ClientboundStartConfigurationPacket in packetSent, and setOutboundState to CONFIGURATION there? packetSent should be invoked more or less right after the onSent callback would be?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have the developers explicitly switch them like in vanilla. The entire onSent thing is made because of that reason. Stuff in packetSent may be not be handled immediately because packets sometimes get handled on the event loop. However onSent is guaranteed to be handled inside the netty pipeline.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClientboundStartConfigurationPacket is not isPriority
therefore it gets handled on the event loop
so if you
send(ClientboundStartConfigurationPacket)
and
send(SomeConfigurationPacket)
as a developer right after each other, it'll error because the event loop is having a race condition with the netty pipeline
One other fix would be setting ClientboundStartConfigurationPacket as isPriority, but I don't wanna touch that with this PR
onSent allows the dev to change the state the next packets gets decoded with immediately without the chance of race conditions because of off-pipeline processing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry if I don't understand.

Stuff in packetSent may be not be handled immediately because packets sometimes get handled on the event loop. However onSent is guaranteed to be handled inside the netty pipeline.

I read them as being called right after eachother? https://github.com/AlexProgrammerDE/MCProtocolLib/blob/2b310d2618f23127640a85194623126b1eef594b/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpSession.java#L271-L275

Also, isPriority doesn't seem to apply to outbound packets?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah you're right. I was thinking too much from the clients perspective that all packets are in the event loop. Would still be weird though to add back packetSent...
It makes it harder to compare logic with vanilla.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does have the distinction now that any packets handled by ServerListener in the packetSent event are packets that have been sent by the library user, not internal MCPL. I think its worth it to not introduce state switching to the library user for a single case (if i understand correctly)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now not even possible anymore since explicit flushes after the writeAndFlush are needed due to race conditions. The code doesn't use onSent anymore for state switching. Developers need to manually switch state after sending the packet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would still be weird though to add back packetSent...
It makes it harder to compare logic with vanilla.

Minecraft is also the maintainer AND user of its network code—I think it is worth it here for the sake of ease of use.

Minecraft has a method for sending ClientboundStartConfigurationPacket and switching outbound protocol, but I'm not sure this project's structure would facilitate something like that?

@Konicai Konicai changed the title Implement incoming/outgoing packet registry spliitting to exactly replicate vanilla behaviour. Split incoming/outgoing packet registry, transition protocol states correctly Jul 28, 2024
This replicates the same approach Mojang uses in their networking code.
@AlexProgrammerDE
Copy link
Contributor Author

Race conditions and weird disconnects are fixed. This PR can be merged.

Comment on lines +182 to +184
// The developer who sends ClientboundStartConfigurationPacket needs to setOutboundState to CONFIGURATION
// after sending the packet. We can't do it in this class because it needs to be a method call right after it was sent.
// Using nettys event loop to change outgoing state may cause differences to vanilla.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would still be weird though to add back packetSent...
It makes it harder to compare logic with vanilla.

Minecraft is also the maintainer AND user of its network code—I think it is worth it here for the sake of ease of use.

Minecraft has a method for sending ClientboundStartConfigurationPacket and switching outbound protocol, but I'm not sure this project's structure would facilitate something like that?

if (keepAlivePacket.getPingId() == this.lastPingId) {
long time = System.currentTimeMillis() - this.lastPingTime;
session.setFlag(MinecraftConstants.PING_KEY, time);
KeepAliveState currentKeepAliveState = this.keepAliveState;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What specifically is this being cached to a variable for? The field value seems to be mutated when updating the keep alive, but replaced on login and config start/end.

If we are trying to prevent races with the keep alive thread, have we considered making KeepAliveState immutable?

Also, could keepAlivePending be replaced with the field being null when not pending, and have the field be volatile?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is coded closer to mojangs impl.
The pending field and other fields are part of the commonpacketlistener. And each protocol state keeps track of separate keepalives.

# Conflicts:
#	protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ClientListener.java
#	protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ServerListener.java
@@ -84,7 +90,8 @@ public MinecraftProtocol(PacketCodec codec) {
this.codec = codec;
this.targetState = ProtocolState.STATUS;

this.setState(ProtocolState.HANDSHAKE);
this.setInboundState(ProtocolState.HANDSHAKE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we could just have a setState/setStates method just for convenience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants