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:
Thomas Devanneaux 2018-01-20 00:18:02 -08:00 committed by Norman Maurer
parent 2fd6cb0a0f
commit 0c741eb4c4
2 changed files with 40 additions and 2 deletions

View File

@ -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.

View File

@ -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();