ByteBuf.toString(Charset) is not thread-safe
Motivation: Calling ByteBuf.toString(Charset) on the same buffer from multiple threads at the same time produces unexpected results, such as various exceptions and/or corrupted output. This is because ByteBufUtil.decodeString(...) is taking the source ByteBuffer for CharsetDecoder.decode() from ByteBuf.internalNioBuffer(int, int), which is not thread-safe. Modification: Call ByteBuf.nioBuffer() instead of ByteBuf.internalNioBuffer() to get the source buffer to pass to CharsetDecoder.decode(). Result: Fixes the possible race condition.
This commit is contained in:
parent
2fd6cb0a0f
commit
0c741eb4c4
@ -587,8 +587,7 @@ public final class ByteBufUtil {
|
||||
dst.clear();
|
||||
}
|
||||
if (src.nioBufferCount() == 1) {
|
||||
// Use internalNioBuffer(...) to reduce object creation.
|
||||
decodeString(decoder, src.internalNioBuffer(readerIndex, len), dst);
|
||||
decodeString(decoder, src.nioBuffer(readerIndex, len), dst);
|
||||
} else {
|
||||
// We use a heap buffer as CharsetDecoder is most likely able to use a fast-path if src and dst buffers
|
||||
// are both backed by a byte array.
|
||||
|
@ -32,8 +32,10 @@ import java.nio.channels.Channels;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@ -1698,6 +1700,43 @@ public abstract class AbstractByteBufTest {
|
||||
copied.release();
|
||||
}
|
||||
|
||||
@Test(timeout = 5000)
|
||||
public void testToStringMultipleThreads() throws Throwable {
|
||||
buffer.clear();
|
||||
buffer.writeBytes("Hello, World!".getBytes(CharsetUtil.ISO_8859_1));
|
||||
|
||||
final AtomicInteger counter = new AtomicInteger(60000);
|
||||
final AtomicReference<Throwable> errorRef = new AtomicReference<Throwable>();
|
||||
List<Thread> threads = new ArrayList<Thread>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (errorRef.get() == null && counter.decrementAndGet() > 0) {
|
||||
assertEquals("Hello, World!", buffer.toString(CharsetUtil.ISO_8859_1));
|
||||
}
|
||||
} catch (Throwable cause) {
|
||||
errorRef.compareAndSet(null, cause);
|
||||
}
|
||||
}
|
||||
});
|
||||
threads.add(thread);
|
||||
}
|
||||
for (Thread thread : threads) {
|
||||
thread.start();
|
||||
}
|
||||
|
||||
for (Thread thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
Throwable error = errorRef.get();
|
||||
if (error != null) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexOf() {
|
||||
buffer.clear();
|
||||
|
Loading…
Reference in New Issue
Block a user