Skip to content

Commit 08c03aa

Browse files
Fix chat race condition (#1042)
1 parent 02c4d61 commit 08c03aa

2 files changed

Lines changed: 27 additions & 14 deletions

File tree

proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import com.velocitypowered.proxy.util.except.QuietDecoderException;
5252
import io.netty.buffer.ByteBuf;
5353
import io.netty.channel.Channel;
54+
import io.netty.channel.ChannelFuture;
5455
import io.netty.channel.ChannelFutureListener;
5556
import io.netty.channel.ChannelHandler;
5657
import io.netty.channel.ChannelHandlerContext;
@@ -224,12 +225,16 @@ public EventLoop eventLoop() {
224225
* Writes and immediately flushes a message to the connection.
225226
*
226227
* @param msg the message to write
228+
*
229+
* @return A {@link ChannelFuture} that will complete when packet is successfully sent
227230
*/
228-
public void write(Object msg) {
231+
@Nullable
232+
public ChannelFuture write(Object msg) {
229233
if (channel.isActive()) {
230-
channel.writeAndFlush(msg, channel.voidPromise());
234+
return channel.writeAndFlush(msg, channel.newPromise());
231235
} else {
232236
ReferenceCountUtil.release(msg);
237+
return null;
233238
}
234239
}
235240

proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
import com.velocitypowered.proxy.connection.MinecraftConnection;
2121
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
2222
import com.velocitypowered.proxy.protocol.MinecraftPacket;
23+
import io.netty.channel.ChannelFuture;
24+
import org.checkerframework.checker.nullness.qual.Nullable;
2325
import java.time.Instant;
2426
import java.util.concurrent.CompletableFuture;
25-
import java.util.function.BiConsumer;
27+
import java.util.function.Function;
2628

2729
/**
2830
* A precisely ordered queue which allows for outside entries into the ordered queue through
@@ -58,9 +60,8 @@ public void queuePacket(CompletableFuture<MinecraftPacket> nextPacket, Instant t
5860
MinecraftConnection smc = player.ensureAndGetCurrentServer().ensureConnected();
5961

6062
CompletableFuture<WrappedPacket> nextInLine = WrappedPacket.wrap(timestamp, nextPacket);
61-
awaitChat(smc, this.packetFuture,
63+
this.packetFuture = awaitChat(smc, this.packetFuture,
6264
nextInLine); // we await chat, binding `this.packetFuture` -> `nextInLine`
63-
this.packetFuture = nextInLine;
6465
}
6566
}
6667

@@ -84,21 +85,26 @@ public <K, V extends MinecraftPacket> void hijack(K packet,
8485
}
8586
}
8687

87-
private static BiConsumer<WrappedPacket, Throwable> writePacket(MinecraftConnection connection) {
88-
return (wrappedPacket, throwable) -> {
89-
if (wrappedPacket != null && !connection.isClosed()) {
90-
wrappedPacket.write(connection);
88+
private static Function<WrappedPacket, WrappedPacket> writePacket(MinecraftConnection connection) {
89+
return wrappedPacket -> {
90+
if (!connection.isClosed()) {
91+
ChannelFuture future = wrappedPacket.write(connection);
92+
if (future != null) {
93+
future.awaitUninterruptibly();
94+
}
9195
}
96+
97+
return wrappedPacket;
9298
};
9399
}
94100

95-
private static <T extends MinecraftPacket> void awaitChat(
101+
private static <T extends MinecraftPacket> CompletableFuture<WrappedPacket> awaitChat(
96102
MinecraftConnection connection,
97103
CompletableFuture<WrappedPacket> binder,
98104
CompletableFuture<WrappedPacket> future
99105
) {
100106
// the binder will run -> then the future will get the `write packet` caller
101-
binder.whenComplete((ignored1, ignored2) -> future.whenComplete(writePacket(connection)));
107+
return binder.thenCompose(ignored -> future.thenApply(writePacket(connection)));
102108
}
103109

104110
private static <K, V extends MinecraftPacket> CompletableFuture<WrappedPacket> hijackCurrentPacket(
@@ -113,7 +119,7 @@ private static <K, V extends MinecraftPacket> CompletableFuture<WrappedPacket> h
113119
// map the new packet into a better "designed" packet with the hijacked packet's timestamp
114120
WrappedPacket.wrap(previous.timestamp,
115121
future.thenApply(item -> packetMapper.map(previous.timestamp, item)))
116-
.whenCompleteAsync(writePacket(connection), connection.eventLoop())
122+
.thenApplyAsync(writePacket(connection), connection.eventLoop())
117123
.whenComplete(
118124
(packet, throwable) -> awaitedFuture.complete(throwable != null ? null : packet));
119125
});
@@ -148,10 +154,12 @@ private WrappedPacket(Instant timestamp, MinecraftPacket packet) {
148154
this.packet = packet;
149155
}
150156

151-
public void write(MinecraftConnection connection) {
157+
@Nullable
158+
public ChannelFuture write(MinecraftConnection connection) {
152159
if (packet != null) {
153-
connection.write(packet);
160+
return connection.write(packet);
154161
}
162+
return null;
155163
}
156164

157165
private static CompletableFuture<WrappedPacket> wrap(Instant timestamp,

0 commit comments

Comments
 (0)