Skip to content

Commit dc9a1ff

Browse files
committed
🧵 Fix rare concurrency issue on ProtocolizedBuiltPacketImpl
A concurrency problem could occur if a packet using this class was sent to two clients with different protocols simultaneously, and they had never been cached.
1 parent b6e89b5 commit dc9a1ff

2 files changed

Lines changed: 45 additions & 24 deletions

File tree

core/src/main/java/net/transferproxy/network/packet/built/ProtocolizedBuiltPacketImpl.java

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ public class ProtocolizedBuiltPacketImpl implements ProtocolizedBuiltPacket {
4545

4646
private final IntFunction<Packet> packetFactory;
4747
private final int[] protocols;
48-
private final IntObjectMap<byte[]> dataMap = new IntObjectHashMap<>(2);
48+
private volatile IntObjectMap<byte[]> dataMap;
4949
private final boolean lazy;
5050

51-
public <T> ProtocolizedBuiltPacketImpl(final @NotNull Packet packet, final boolean lazy, final int... protocols) {
51+
public ProtocolizedBuiltPacketImpl(final @NotNull Packet packet, final boolean lazy, final int... protocols) {
5252
this(u -> packet, lazy, protocols);
5353
Objects.requireNonNull(packet, "packet must not be null");
5454
}
@@ -78,32 +78,49 @@ public ProtocolizedBuiltPacketImpl(final @NotNull IntFunction<Packet> packetFact
7878
if (protocols.length == 0) {
7979
throw new IllegalArgumentException("Protocols must not be empty. Use BuiltPacket instead if the packet is not protocolized.");
8080
}
81+
final IntObjectMap<byte[]> initialMap = new IntObjectHashMap<>(lazy ? 2 : protocols.length);
8182
if (!lazy) {
8283
for (final int protocol : protocols) {
83-
this.compute(protocol);
84+
final byte[] data = this.computeBytes(protocol);
85+
initialMap.put(protocol, data);
8486
}
8587
}
88+
this.dataMap = initialMap;
8689
}
8790

8891
@Override
8992
public ByteBuf get(final @NotNull ByteBufAllocator allocator, final int protocol) {
90-
byte[] data = this.dataMap.get(protocol);
93+
IntObjectMap<byte[]> localMap = this.dataMap;
94+
byte[] data = localMap.get(protocol);
9195
if (data == null) {
92-
if (this.lazy && this.isAvailable(protocol)) {
93-
data = this.compute(protocol, protocol, allocator);
94-
} else {
95-
final int low = this.findLow(protocol);
96-
data = this.dataMap.get(low);
96+
synchronized (this) {
97+
localMap = this.dataMap;
98+
data = localMap.get(protocol);
9799
if (data == null) {
98-
data = this.compute(low, protocol, allocator);
99-
this.dataMap.put(low, data);
100-
} else {
101-
this.dataMap.put(protocol, data);
100+
final IntObjectMap<byte[]> newMap = copyOf(localMap);
101+
102+
if (this.lazy && this.isAvailable(protocol)) {
103+
data = this.computeBytes(protocol, allocator);
104+
newMap.put(protocol, data);
105+
} else {
106+
final int low = this.findLow(protocol);
107+
final byte[] lowData = localMap.get(low);
108+
if (lowData == null) {
109+
data = this.computeBytes(low, allocator);
110+
newMap.put(low, data);
111+
newMap.put(protocol, data);
112+
} else {
113+
data = lowData;
114+
newMap.put(protocol, data);
115+
}
116+
}
117+
118+
this.dataMap = newMap;
102119
}
103120
}
104121
}
105-
final int length = data.length;
106-
final ByteBuf buf = allocator.buffer(length, length);
122+
123+
final ByteBuf buf = allocator.buffer(data.length, data.length);
107124
buf.writeBytes(data);
108125
return buf;
109126
}
@@ -118,17 +135,13 @@ private int findLow(final int protocol) {
118135
return nearLow;
119136
}
120137

121-
private byte[] compute(final int protocol) {
122-
return this.compute(protocol, protocol);
123-
}
124-
125138
@VisibleForTesting
126-
byte[] compute(final int protocol, final int updateProtocol) {
127-
return this.compute(protocol, updateProtocol, ByteBufAllocator.DEFAULT);
139+
byte[] computeBytes(final int protocol) {
140+
return this.computeBytes(protocol, ByteBufAllocator.DEFAULT);
128141
}
129142

130143
@VisibleForTesting
131-
byte[] compute(final int protocol, final int updateProtocol, final @NotNull ByteBufAllocator allocator) {
144+
byte[] computeBytes(final int protocol, final @NotNull ByteBufAllocator allocator) {
132145
final Packet packet = this.packetFactory.apply(protocol);
133146

134147
final ByteBuf buf = allocator.buffer();
@@ -138,7 +151,7 @@ byte[] compute(final int protocol, final int updateProtocol, final @NotNull Byte
138151

139152
final byte[] data = new byte[buf.readableBytes()];
140153
buf.getBytes(buf.readerIndex(), data);
141-
this.dataMap.put(updateProtocol, data);
154+
142155
return data;
143156
} finally {
144157
buf.release();
@@ -154,4 +167,12 @@ private boolean isAvailable(final int protocol) {
154167
return false;
155168
}
156169

170+
private static IntObjectMap<byte[]> copyOf(final IntObjectMap<byte[]> source) {
171+
final IntObjectMap<byte[]> copy = new IntObjectHashMap<>(source.size() + 1);
172+
for (final IntObjectMap.PrimitiveEntry<byte[]> entry : source.entries()) {
173+
copy.put(entry.key(), entry.value());
174+
}
175+
return copy;
176+
}
177+
157178
}

core/src/test/java/net/transferproxy/network/packet/built/ProtocolizedBuiltPacketImplTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ void testBufferIsCorrectlyCached() {
9090
assertDoesNotThrow(() -> builtPacket.get(allocator, protocol)).release();
9191
}
9292

93-
verify(builtPacket, times(3)).compute(anyInt(), anyInt());
93+
verify(builtPacket, times(3)).computeBytes(anyInt(), any(ByteBufAllocator.class));
9494
}
9595

9696
}

0 commit comments

Comments
 (0)