From d382017dc6554cffb457866d2ce5f1ca9e2a91e4 Mon Sep 17 00:00:00 2001 From: Chris Vest Date: Mon, 11 Jan 2021 16:10:00 +0100 Subject: [PATCH 1/6] Add support for iterating underlying buffer components Motivation: It's desirable to be able to access the contents of a Buf via an array or a ByteBuffer. However, we would also like to have a unified API that works for both composite and non-composite buffers. Even for nested composite buffers. Modification: Add a forEachReadable method, which uses internal iteration to process all buffer components. The internal iteration allows us to hide any nesting of composite buffers. The consumer in the internal iteration is presented with a Component object, which exposes the contents in various ways. The data is exposed from the Component via methods, such that anything that is expensive to create, will not have to be paid for unless it is used. This mechanism also let us avoid any allocation unnecessary allocation; the ByteBuffers and arrays will necessarily have to be allocated, but the consumer may or may not need allocation depending on how it's implemented, and the component objects do not need to be allocated, because the non-composite buffers can directly implement the Component interface. Result: It's now possible to access the contents of Buf instances as arrays or ByteBuffers, without having to copy the data. --- src/main/java/io/netty/buffer/api/Buf.java | 25 +- .../java/io/netty/buffer/api/Component.java | 57 ++ .../io/netty/buffer/api/CompositeBuf.java | 24 +- .../io/netty/buffer/api/memseg/MemSegBuf.java | 78 +- .../java/io/netty/buffer/api/BufTest.java | 844 ++++++++++++++++-- .../java/io/netty/buffer/api/Fixture.java | 4 + 6 files changed, 924 insertions(+), 108 deletions(-) create mode 100644 src/main/java/io/netty/buffer/api/Component.java diff --git a/src/main/java/io/netty/buffer/api/Buf.java b/src/main/java/io/netty/buffer/api/Buf.java index 6906be4..4bbee0b 100644 --- a/src/main/java/io/netty/buffer/api/Buf.java +++ b/src/main/java/io/netty/buffer/api/Buf.java @@ -17,6 +17,7 @@ package io.netty.buffer.api; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.function.Consumer; /** * A reference counted buffer of memory, with separate reader and writer offsets. @@ -187,7 +188,7 @@ public interface Buf extends Rc, BufAccessors { * Give the native memory address backing this buffer, or return 0 if this buffer has no native memory address. * @return The native memory address, if any, otherwise 0. */ - long getNativeAddress(); + long nativeAddress(); /** * Set the read-only state of this buffer. @@ -482,4 +483,26 @@ public interface Buf extends Rc, BufAccessors { * or is {@linkplain #readOnly() read-only}. */ void compact(); + + /** + * Get the number of "components" in this buffer. For composite buffers, this is the number of transitive + * constituent buffers, while non-composite buffers only have one component. + * + * @return The number of components in this buffer. + */ + int componentCount(); + + /** + * Process all readable components of this buffer, and return the number of components consumed. + *

+ * The number of components consumed may be less than the {@linkplain #componentCount() component count} if not all + * of them have readable data. + * + * Note that the {@link Component} instance passed to the consumer could be reused for multiple + * calls, so the data must be extracted from the component in the context of the iteration. + * + * @param consumer The consumer that will be used to process the buffer components. + * @return The number of readable components processed, which may be less than {@link #componentCount()}. + */ + int forEachReadable(Consumer consumer); } diff --git a/src/main/java/io/netty/buffer/api/Component.java b/src/main/java/io/netty/buffer/api/Component.java new file mode 100644 index 0000000..997cdc9 --- /dev/null +++ b/src/main/java/io/netty/buffer/api/Component.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.buffer.api; + +import java.nio.ByteBuffer; +import java.util.function.Consumer; + +/** + * A view onto the buffer component being processed in a given iteration of {@link Buf#forEachReadable(Consumer)}. + *

+ * Instances of this interface are allowed to be mutable behind the scenes, and the data is only guaranteed to be + * consistent within the given iteration. + */ +public interface Component { + + /** + * Check if this component is backed by a cached byte array than can be accessed cheaply. + * + * @return {@code true} if {@link #array()} is a cheap operation, otherwise {@code false}. + */ + boolean hasCachedArray(); + + /** + * Get a byte array of the contents of this component. + *

+ * Note that the array is meant to be read-only. It may either be a direct reference to the + * concrete array instance that is backing this component, or it is a fresh copy. + * + * @return A byte array of the contents of this component. + */ + byte[] array(); + + /** + * Give the native memory address backing this buffer, or return 0 if this buffer has no native memory address. + * @return The native memory address, if any, otherwise 0. + */ + long nativeAddress(); + + /** + * Build a {@link ByteBuffer} instance for this memory component. + * @return A new {@link ByteBuffer} for this memory component. + */ + ByteBuffer byteBuffer(); +} diff --git a/src/main/java/io/netty/buffer/api/CompositeBuf.java b/src/main/java/io/netty/buffer/api/CompositeBuf.java index e7c6208..a4d9bca 100644 --- a/src/main/java/io/netty/buffer/api/CompositeBuf.java +++ b/src/main/java/io/netty/buffer/api/CompositeBuf.java @@ -19,6 +19,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.Objects; +import java.util.function.Consumer; final class CompositeBuf extends RcSupport implements Buf { /** @@ -203,7 +204,7 @@ final class CompositeBuf extends RcSupport implements Buf { } @Override - public long getNativeAddress() { + public long nativeAddress() { return 0; } @@ -700,6 +701,27 @@ final class CompositeBuf extends RcSupport implements Buf { writerOffset(woff - distance); } + @Override + public int componentCount() { + int sum = 0; + for (Buf buf : bufs) { + sum += buf.componentCount(); + } + return sum; + } + + @Override + public int forEachReadable(Consumer consumer) { + checkReadBounds(readerOffset(), Math.max(1, readableBytes())); + int visited = 0; + for (Buf buf : bufs) { + if (buf.readableBytes() > 0) { + visited += buf.forEachReadable(consumer); + } + } + return visited; + } + // @Override public byte readByte() { diff --git a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java index bc901e3..5285784 100644 --- a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java +++ b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java @@ -19,6 +19,7 @@ import io.netty.buffer.api.Allocator; import io.netty.buffer.api.AllocatorControl; import io.netty.buffer.api.Buf; import io.netty.buffer.api.ByteCursor; +import io.netty.buffer.api.Component; import io.netty.buffer.api.Drop; import io.netty.buffer.api.Owned; import io.netty.buffer.api.RcSupport; @@ -26,6 +27,7 @@ import jdk.incubator.foreign.MemorySegment; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.function.Consumer; import static jdk.incubator.foreign.MemoryAccess.getByteAtOffset; import static jdk.incubator.foreign.MemoryAccess.getCharAtOffset; @@ -42,7 +44,7 @@ import static jdk.incubator.foreign.MemoryAccess.setIntAtOffset; import static jdk.incubator.foreign.MemoryAccess.setLongAtOffset; import static jdk.incubator.foreign.MemoryAccess.setShortAtOffset; -class MemSegBuf extends RcSupport implements Buf { +class MemSegBuf extends RcSupport implements Buf, Component { private static final MemorySegment CLOSED_SEGMENT; static final Drop SEGMENT_CLOSE; @@ -58,6 +60,7 @@ class MemSegBuf extends RcSupport implements Buf { private final AllocatorControl alloc; private final boolean isSendable; + private final int baseOffset; // TODO remove this when JDK bug is fixed (slices of heap buffers) private MemorySegment seg; private MemorySegment wseg; private ByteOrder order; @@ -65,15 +68,17 @@ class MemSegBuf extends RcSupport implements Buf { private int woff; MemSegBuf(MemorySegment segmet, Drop drop, AllocatorControl alloc) { - this(segmet, drop, alloc, true); + this(segmet, drop, alloc, true, 0); } - private MemSegBuf(MemorySegment segment, Drop drop, AllocatorControl alloc, boolean isSendable) { + private MemSegBuf(MemorySegment segment, Drop drop, AllocatorControl alloc, boolean isSendable, + int baseOffset) { super(drop); this.alloc = alloc; seg = segment; wseg = segment; this.isSendable = isSendable; + this.baseOffset = baseOffset; order = ByteOrder.nativeOrder(); } @@ -130,7 +135,17 @@ class MemSegBuf extends RcSupport implements Buf { } @Override - public long getNativeAddress() { + public boolean hasCachedArray() { + return false; + } + + @Override + public byte[] array() { + return seg.toByteArray(); + } + + @Override + public long nativeAddress() { try { return seg.address().toRawLongValue(); } catch (UnsupportedOperationException e) { @@ -138,6 +153,24 @@ class MemSegBuf extends RcSupport implements Buf { } } + @Override + public ByteBuffer byteBuffer() { + var buffer = seg.asByteBuffer(); + int base = baseOffset; + if (buffer.isDirect()) { + // TODO Remove this when JDK bug is fixed. + ByteBuffer tmp = ByteBuffer.allocateDirect(buffer.capacity()); + tmp.put(buffer); + buffer = tmp.position(0); + base = 0; // TODO native memory segments do not have the buffer-of-slice bug. + } + if (readOnly()) { + buffer = buffer.asReadOnlyBuffer(); + } + // TODO avoid slicing and just set position+limit when JDK bug is fixed. + return buffer.slice(base + readerOffset(), readableBytes()).order(order); + } + @Override public Buf readOnly(boolean readOnly) { wseg = readOnly? CLOSED_SEGMENT : seg; @@ -161,7 +194,7 @@ class MemSegBuf extends RcSupport implements Buf { b.makeInaccessible(); }; var sendable = false; // Sending implies ownership change, which we can't do for slices. - return new MemSegBuf(slice, drop, alloc, sendable) + return new MemSegBuf(slice, drop, alloc, sendable, baseOffset + offset) .writerOffset(length) .order(order()) .readOnly(readOnly()); @@ -458,6 +491,18 @@ class MemSegBuf extends RcSupport implements Buf { woff -= distance; } + @Override + public int componentCount() { + return 1; + } + + @Override + public int forEachReadable(Consumer consumer) { + checkRead(readerOffset(), Math.max(1, readableBytes())); + consumer.accept(this); + return 1; + } + // @Override public byte readByte() { @@ -969,13 +1014,13 @@ class MemSegBuf extends RcSupport implements Buf { private void checkRead(int index, int size) { if (index < 0 || woff < index + size) { - throw accessCheckException(index); + throw readAccessCheckException(index); } } private void checkWrite(int index, int size) { if (index < 0 || wseg.byteSize() < index + size) { - throw accessCheckException(index); + throw writeAccessCheckException(index); } } @@ -989,16 +1034,21 @@ class MemSegBuf extends RcSupport implements Buf { return ioobe; } - private RuntimeException accessCheckException(int index) { + private RuntimeException readAccessCheckException(int index) { + if (seg == CLOSED_SEGMENT) { + throw bufferIsClosed(); + } + return outOfBounds(index); + } + + private RuntimeException writeAccessCheckException(int index) { if (seg == CLOSED_SEGMENT) { throw bufferIsClosed(); } if (wseg != seg) { return bufferIsReadOnly(); } - return new IndexOutOfBoundsException( - "Index " + index + " is out of bounds: [read 0 to " + woff + ", write 0 to " + - (seg.byteSize() - 1) + "]."); + return outOfBounds(index); } private static IllegalStateException bufferIsClosed() { @@ -1009,6 +1059,12 @@ class MemSegBuf extends RcSupport implements Buf { return new IllegalStateException("This buffer is read-only."); } + private IndexOutOfBoundsException outOfBounds(int index) { + return new IndexOutOfBoundsException( + "Index " + index + " is out of bounds: [read 0 to " + woff + ", write 0 to " + + (seg.byteSize() - 1) + "]."); + } + Object recoverableMemory() { return new RecoverableMemory(seg, alloc); } diff --git a/src/test/java/io/netty/buffer/api/BufTest.java b/src/test/java/io/netty/buffer/api/BufTest.java index 52a2186..23088d1 100644 --- a/src/test/java/io/netty/buffer/api/BufTest.java +++ b/src/test/java/io/netty/buffer/api/BufTest.java @@ -29,8 +29,10 @@ import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.ReadOnlyBufferException; import java.text.ParseException; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; @@ -47,6 +49,8 @@ import static io.netty.buffer.api.Fixture.Properties.COMPOSITE; import static io.netty.buffer.api.Fixture.Properties.DIRECT; import static io.netty.buffer.api.Fixture.Properties.HEAP; import static io.netty.buffer.api.Fixture.Properties.POOLED; +import static java.nio.ByteOrder.BIG_ENDIAN; +import static java.nio.ByteOrder.LITTLE_ENDIAN; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -77,6 +81,10 @@ public class BufTest { return fixtureCombinations().filter(f -> !f.isSlice()); } + static Stream nonCompositeAllocators() { + return fixtureCombinations().filter(f -> !f.isComposite()); + } + static Stream heapAllocators() { return fixtureCombinations().filter(Fixture::isHeap); } @@ -222,6 +230,7 @@ public class BufTest { private static Stream injectSlices(Fixture f) { Builder builder = Stream.builder(); builder.add(f); + var props = concat(f.getProperties(), Properties.SLICE); builder.add(new Fixture(f + ".slice(0, capacity())", () -> { var allocatorBase = f.get(); return new Allocator() { @@ -237,7 +246,7 @@ public class BufTest { allocatorBase.close(); } }; - }, Properties.SLICE)); + }, props)); builder.add(new Fixture(f + ".slice(1, capacity() - 2)", () -> { var allocatorBase = f.get(); return new Allocator() { @@ -253,7 +262,7 @@ public class BufTest { allocatorBase.close(); } }; - }, Properties.SLICE)); + }, props)); return builder.build(); } @@ -277,6 +286,12 @@ public class BufTest { return builder.build(); } + private static Properties[] concat(Properties[] props, Properties prop) { + props = Arrays.copyOf(props, props.length + 1); + props[props.length - 1] = prop; + return props; + } + @BeforeAll static void startExecutor() throws IOException, ParseException { executor = Executors.newSingleThreadExecutor(); @@ -754,12 +769,12 @@ public class BufTest { void sliceWithoutOffsetAndSizeHasSameEndianAsParent(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); buf.writeLong(0x0102030405060708L); try (Buf slice = buf.slice()) { assertEquals(0x0102030405060708L, slice.readLong()); } - buf.order(ByteOrder.LITTLE_ENDIAN); + buf.order(LITTLE_ENDIAN); try (Buf slice = buf.slice()) { assertEquals(0x0807060504030201L, slice.readLong()); } @@ -771,12 +786,12 @@ public class BufTest { void sliceWithOffsetAndSizeHasSameEndianAsParent(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); buf.writeLong(0x0102030405060708L); try (Buf slice = buf.slice(0, 8)) { assertEquals(0x0102030405060708L, slice.readLong()); } - buf.order(ByteOrder.LITTLE_ENDIAN); + buf.order(LITTLE_ENDIAN); try (Buf slice = buf.slice(0, 8)) { assertEquals(0x0807060504030201L, slice.readLong()); } @@ -888,12 +903,12 @@ public class BufTest { void copyIntoByteArray(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN).writeLong(0x0102030405060708L); + buf.order(BIG_ENDIAN).writeLong(0x0102030405060708L); byte[] array = new byte[8]; buf.copyInto(0, array, 0, array.length); assertThat(array).containsExactly(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); - buf.writerOffset(0).order(ByteOrder.LITTLE_ENDIAN).writeLong(0x0102030405060708L); + buf.writerOffset(0).order(LITTLE_ENDIAN).writeLong(0x0102030405060708L); buf.copyInto(0, array, 0, array.length); assertThat(array).containsExactly(0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01); @@ -918,7 +933,7 @@ public class BufTest { private static void testCopyIntoByteBuffer(Fixture fixture, Function bbAlloc) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN).writeLong(0x0102030405060708L); + buf.order(BIG_ENDIAN).writeLong(0x0102030405060708L); ByteBuffer buffer = bbAlloc.apply(8); buf.copyInto(0, buffer, 0, buffer.capacity()); assertEquals((byte) 0x01, buffer.get()); @@ -931,7 +946,7 @@ public class BufTest { assertEquals((byte) 0x08, buffer.get()); buffer.clear(); - buf.writerOffset(0).order(ByteOrder.LITTLE_ENDIAN).writeLong(0x0102030405060708L); + buf.writerOffset(0).order(LITTLE_ENDIAN).writeLong(0x0102030405060708L); buf.copyInto(0, buffer, 0, buffer.capacity()); assertEquals((byte) 0x08, buffer.get()); assertEquals((byte) 0x07, buffer.get()); @@ -1131,7 +1146,7 @@ public class BufTest { private static void testCopyIntoBuf(Fixture fixture, Function bbAlloc) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN).writeLong(0x0102030405060708L); + buf.order(BIG_ENDIAN).writeLong(0x0102030405060708L); Buf buffer = bbAlloc.apply(8); buffer.writerOffset(8); buf.copyInto(0, buffer, 0, buffer.capacity()); @@ -1145,7 +1160,7 @@ public class BufTest { assertEquals((byte) 0x08, buffer.readByte()); buffer.reset(); - buf.writerOffset(0).order(ByteOrder.LITTLE_ENDIAN).writeLong(0x0102030405060708L); + buf.writerOffset(0).order(LITTLE_ENDIAN).writeLong(0x0102030405060708L); buf.copyInto(0, buffer, 0, buffer.capacity()); buffer.writerOffset(8); assertEquals((byte) 0x08, buffer.readByte()); @@ -1186,7 +1201,7 @@ public class BufTest { buffer.close(); buf.reset(); - buf.order(ByteOrder.BIG_ENDIAN).writeLong(0x0102030405060708L); + buf.order(BIG_ENDIAN).writeLong(0x0102030405060708L); // Testing copyInto for overlapping writes: // // 0x0102030405060708 @@ -1216,7 +1231,7 @@ public class BufTest { void byteIterationOfBigEndianBuffers(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(0x28)) { - buf.order(ByteOrder.BIG_ENDIAN); // The byte order should have no impact. + buf.order(BIG_ENDIAN); // The byte order should have no impact. checkByteIteration(buf); buf.reset(); checkByteIterationOfRegion(buf); @@ -1228,7 +1243,7 @@ public class BufTest { void byteIterationOfLittleEndianBuffers(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(0x28)) { - buf.order(ByteOrder.LITTLE_ENDIAN); // The byte order should have no impact. + buf.order(LITTLE_ENDIAN); // The byte order should have no impact. checkByteIteration(buf); buf.reset(); checkByteIterationOfRegion(buf); @@ -1373,7 +1388,7 @@ public class BufTest { void reverseByteIterationOfBigEndianBuffers(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(0x28)) { - buf.order(ByteOrder.BIG_ENDIAN); // The byte order should have no impact. + buf.order(BIG_ENDIAN); // The byte order should have no impact. checkReverseByteIteration(buf); buf.reset(); checkReverseByteIterationOfRegion(buf); @@ -1385,7 +1400,7 @@ public class BufTest { void reverseByteIterationOfLittleEndianBuffers(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(0x28)) { - buf.order(ByteOrder.LITTLE_ENDIAN); // The byte order should have no impact. + buf.order(LITTLE_ENDIAN); // The byte order should have no impact. checkReverseByteIteration(buf); buf.reset(); checkReverseByteIterationOfRegion(buf); @@ -1530,7 +1545,7 @@ public class BufTest { public void heapBufferMustHaveZeroAddress(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - assertThat(buf.getNativeAddress()).isZero(); + assertThat(buf.nativeAddress()).isZero(); } } @@ -1539,7 +1554,7 @@ public class BufTest { public void directBufferMustHaveNonZeroAddress(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - assertThat(buf.getNativeAddress()).isNotZero(); + assertThat(buf.nativeAddress()).isNotZero(); } } @@ -1712,7 +1727,7 @@ public class BufTest { public void ensureWritableOnCompositeBuffersMustRespectExistingBigEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator()) { Buf composite; - try (Buf a = allocator.allocate(4, ByteOrder.BIG_ENDIAN)) { + try (Buf a = allocator.allocate(4, BIG_ENDIAN)) { composite = allocator.compose(a); } try (composite) { @@ -1729,7 +1744,7 @@ public class BufTest { public void ensureWritableOnCompositeBuffersMustRespectExistingLittleEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator()) { Buf composite; - try (Buf a = allocator.allocate(4, ByteOrder.LITTLE_ENDIAN)) { + try (Buf a = allocator.allocate(4, LITTLE_ENDIAN)) { composite = allocator.compose(a); } try (composite) { @@ -1782,7 +1797,7 @@ public class BufTest { assertThat(bytes).containsExactly(0, 0, 0, 0, 0, 0, 0, 0); var tlr = ThreadLocalRandom.current(); - buf.order(tlr.nextBoolean()? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); + buf.order(tlr.nextBoolean()? LITTLE_ENDIAN : BIG_ENDIAN); for (int j = 0; j < tlr.nextInt(0, 8); j++) { buf.writeByte((byte) 1); } @@ -1859,7 +1874,7 @@ public class BufTest { try (Allocator allocator = Allocator.heap(); Buf composite = allocator.compose()) { assertThat(composite.capacity()).isZero(); - try (Buf buf = allocator.allocate(8, ByteOrder.BIG_ENDIAN)) { + try (Buf buf = allocator.allocate(8, BIG_ENDIAN)) { Allocator.extend(composite, buf); } assertThat(composite.capacity()).isEqualTo(8); @@ -1873,7 +1888,7 @@ public class BufTest { try (Allocator allocator = Allocator.heap(); Buf composite = allocator.compose()) { assertThat(composite.capacity()).isZero(); - try (Buf buf = allocator.allocate(8, ByteOrder.LITTLE_ENDIAN)) { + try (Buf buf = allocator.allocate(8, LITTLE_ENDIAN)) { Allocator.extend(composite, buf); } assertThat(composite.capacity()).isEqualTo(8); @@ -1886,11 +1901,11 @@ public class BufTest { public void extendingBigEndianCompositeBufferMustThrowIfExtensionIsLittleEndian() { try (Allocator allocator = Allocator.heap()) { Buf composite; - try (Buf a = allocator.allocate(8, ByteOrder.BIG_ENDIAN)) { + try (Buf a = allocator.allocate(8, BIG_ENDIAN)) { composite = allocator.compose(a); } try (composite) { - try (Buf b = allocator.allocate(8, ByteOrder.LITTLE_ENDIAN)) { + try (Buf b = allocator.allocate(8, LITTLE_ENDIAN)) { var exc = assertThrows(IllegalArgumentException.class, () -> Allocator.extend(composite, b)); assertThat(exc).hasMessageContaining("byte order"); } @@ -1902,11 +1917,11 @@ public class BufTest { public void extendingLittleEndianCompositeBufferMustThrowIfExtensionIsBigEndian() { try (Allocator allocator = Allocator.heap()) { Buf composite; - try (Buf a = allocator.allocate(8, ByteOrder.LITTLE_ENDIAN)) { + try (Buf a = allocator.allocate(8, LITTLE_ENDIAN)) { composite = allocator.compose(a); } try (composite) { - try (Buf b = allocator.allocate(8, ByteOrder.BIG_ENDIAN)) { + try (Buf b = allocator.allocate(8, BIG_ENDIAN)) { var exc = assertThrows(IllegalArgumentException.class, () -> Allocator.extend(composite, b)); assertThat(exc).hasMessageContaining("byte order"); } @@ -1918,9 +1933,9 @@ public class BufTest { public void emptyCompositeBufferMustAllowExtendingWithBufferWithBigEndianByteOrder() { try (Allocator allocator = Allocator.heap()) { try (Buf composite = allocator.compose()) { - try (Buf b = allocator.allocate(8, ByteOrder.BIG_ENDIAN)) { + try (Buf b = allocator.allocate(8, BIG_ENDIAN)) { Allocator.extend(composite, b); - assertThat(composite.order()).isEqualTo(ByteOrder.BIG_ENDIAN); + assertThat(composite.order()).isEqualTo(BIG_ENDIAN); } } } @@ -1930,9 +1945,9 @@ public class BufTest { public void emptyCompositeBufferMustAllowExtendingWithBufferWithLittleEndianByteOrder() { try (Allocator allocator = Allocator.heap()) { try (Buf composite = allocator.compose()) { - try (Buf b = allocator.allocate(8, ByteOrder.LITTLE_ENDIAN)) { + try (Buf b = allocator.allocate(8, LITTLE_ENDIAN)) { Allocator.extend(composite, b); - assertThat(composite.order()).isEqualTo(ByteOrder.LITTLE_ENDIAN); + assertThat(composite.order()).isEqualTo(LITTLE_ENDIAN); } } } @@ -2040,8 +2055,8 @@ public class BufTest { @Test public void composeMustThrowWhenBuffersHaveMismatchedByteOrder() { try (Allocator allocator = Allocator.heap(); - Buf a = allocator.allocate(4, ByteOrder.BIG_ENDIAN); - Buf b = allocator.allocate(4, ByteOrder.LITTLE_ENDIAN)) { + Buf a = allocator.allocate(4, BIG_ENDIAN); + Buf b = allocator.allocate(4, LITTLE_ENDIAN)) { assertThrows(IllegalArgumentException.class, () -> allocator.compose(a, b)); } } @@ -2063,7 +2078,7 @@ public class BufTest { @MethodSource("nonSliceAllocators") public void bifurcatedPartMustContainFirstHalfOfBuffer(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); - Buf buf = allocator.allocate(16).order(ByteOrder.BIG_ENDIAN)) { + Buf buf = allocator.allocate(16).order(BIG_ENDIAN)) { buf.writeLong(0x0102030405060708L); assertThat(buf.readByte()).isEqualTo((byte) 0x01); try (Buf bif = buf.bifurcate()) { @@ -2099,7 +2114,7 @@ public class BufTest { @MethodSource("nonSliceAllocators") public void bifurcatedPartsMustBeIndividuallySendable(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); - Buf buf = allocator.allocate(16).order(ByteOrder.BIG_ENDIAN)) { + Buf buf = allocator.allocate(16).order(BIG_ENDIAN)) { buf.writeLong(0x0102030405060708L); assertThat(buf.readByte()).isEqualTo((byte) 0x01); try (Buf sentBif = buf.bifurcate().send().receive()) { @@ -2128,7 +2143,7 @@ public class BufTest { @MethodSource("nonSliceAllocators") public void mustBePossibleToBifurcateMoreThanOnce(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); - Buf buf = allocator.allocate(16).order(ByteOrder.BIG_ENDIAN)) { + Buf buf = allocator.allocate(16).order(BIG_ENDIAN)) { buf.writeLong(0x0102030405060708L); try (Buf a = buf.bifurcate()) { a.writerOffset(4); @@ -2156,15 +2171,15 @@ public class BufTest { @MethodSource("nonSliceAllocators") public void bifurcatedBufferMustHaveSameByteOrderAsParent(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); - Buf buf = allocator.allocate(8).order(ByteOrder.BIG_ENDIAN)) { + Buf buf = allocator.allocate(8).order(BIG_ENDIAN)) { buf.writeLong(0x0102030405060708L); try (Buf a = buf.bifurcate()) { - assertThat(a.order()).isEqualTo(ByteOrder.BIG_ENDIAN); - a.order(ByteOrder.LITTLE_ENDIAN); + assertThat(a.order()).isEqualTo(BIG_ENDIAN); + a.order(LITTLE_ENDIAN); a.writerOffset(4); try (Buf b = a.bifurcate()) { - assertThat(b.order()).isEqualTo(ByteOrder.LITTLE_ENDIAN); - assertThat(buf.order()).isEqualTo(ByteOrder.BIG_ENDIAN); + assertThat(b.order()).isEqualTo(LITTLE_ENDIAN); + assertThat(buf.order()).isEqualTo(BIG_ENDIAN); } } } @@ -2193,7 +2208,7 @@ public class BufTest { @MethodSource("nonSliceAllocators") public void ensureWritableOnBifurcatedBuffersWithOddOffsets(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); - Buf buf = allocator.allocate(10).order(ByteOrder.BIG_ENDIAN)) { + Buf buf = allocator.allocate(10).order(BIG_ENDIAN)) { buf.writeLong(0x0102030405060708L); buf.writeByte((byte) 0x09); buf.readByte(); @@ -2213,7 +2228,7 @@ public class BufTest { @Test public void bifurcateOnEmptyBigEndianCompositeBuffer() { try (Allocator allocator = Allocator.heap(); - Buf buf = allocator.compose().order(ByteOrder.BIG_ENDIAN)) { + Buf buf = allocator.compose().order(BIG_ENDIAN)) { verifyBifurcateEmptyCompositeBuffer(buf); } } @@ -2221,7 +2236,7 @@ public class BufTest { @Test public void bifurcateOnEmptyLittleEndianCompositeBuffer() { try (Allocator allocator = Allocator.heap(); - Buf buf = allocator.compose().order(ByteOrder.LITTLE_ENDIAN)) { + Buf buf = allocator.compose().order(LITTLE_ENDIAN)) { verifyBifurcateEmptyCompositeBuffer(buf); } } @@ -2286,7 +2301,7 @@ public class BufTest { @MethodSource("nonSliceAllocators") public void compactMustDiscardReadBytes(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); - Buf buf = allocator.allocate(16, ByteOrder.BIG_ENDIAN)) { + Buf buf = allocator.allocate(16, BIG_ENDIAN)) { buf.writeLong(0x0102030405060708L).writeInt(0x090A0B0C); assertEquals(0x01020304, buf.readInt()); assertEquals(12, buf.writerOffset()); @@ -2308,7 +2323,7 @@ public class BufTest { @MethodSource("nonSliceAllocators") public void compactMustThrowForUnownedBuffer(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); - Buf buf = allocator.allocate(8, ByteOrder.BIG_ENDIAN)) { + Buf buf = allocator.allocate(8, BIG_ENDIAN)) { buf.writeLong(0x0102030405060708L); assertEquals((byte) 0x01, buf.readByte()); try (Buf ignore = buf.acquire()) { @@ -2568,6 +2583,106 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("nonCompositeAllocators") + public void componentCountOfNonCompositeBufferMustBeOne(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThat(buf.componentCount()).isOne(); + } + } + + @Test + public void compositeBufferComponentCountMustBeTransitiveSum() { + try (Allocator allocator = Allocator.heap()) { + Buf buf; + try (Buf a = allocator.allocate(8); + Buf b = allocator.allocate(8); + Buf c = allocator.allocate(8); + Buf x = allocator.compose(b, c)) { + buf = allocator.compose(a, x); + } + assertThat(buf.componentCount()).isEqualTo(3); + } + } + + @ParameterizedTest + @MethodSource("nonCompositeAllocators") + public void forEachReadableMustVisitBuffer(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf bufBERW = allocator.allocate(8).order(BIG_ENDIAN).writeLong(0x0102030405060708L); + Buf bufLERW = allocator.allocate(8).order(LITTLE_ENDIAN).writeLong(0x0102030405060708L); + Buf bufBERO = allocator.allocate(8).order(BIG_ENDIAN).writeLong(0x0102030405060708L).readOnly(true); + Buf bufLERO = allocator.allocate(8).order(LITTLE_ENDIAN).writeLong(0x0102030405060708L).readOnly(true)) { + verifyForEachReadableSignleComponent(fixture, bufBERW); + verifyForEachReadableSignleComponent(fixture, bufLERW); + verifyForEachReadableSignleComponent(fixture, bufBERO); + verifyForEachReadableSignleComponent(fixture, bufLERO); + } + } + + private static void verifyForEachReadableSignleComponent(Fixture fixture, Buf buf) { + buf.forEachReadable(component -> { + var buffer = component.byteBuffer(); + assertThat(buffer.position()).isZero(); + assertThat(buffer.limit()).isEqualTo(8); + assertThat(buffer.capacity()).isEqualTo(8); + assertEquals(0x0102030405060708L, buffer.getLong()); + + if (fixture.isDirect()) { + assertThat(component.nativeAddress()).isNotZero(); + } else { + assertThat(component.nativeAddress()).isZero(); + } + + byte[] array = component.array(); + if (buffer.order() == BIG_ENDIAN) { + assertThat(array).containsExactly(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); + } else { + assertThat(array).containsExactly(0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01); + } + + if (buf.readOnly()) { + assertTrue(buffer.isReadOnly()); + assertThrows(ReadOnlyBufferException.class, () -> buffer.put(0, (byte) 0xFF)); + } else { + assertFalse(buffer.isReadOnly()); + buffer.put(0, (byte) 0xFF); + assertEquals((byte) 0xFF, buffer.get(0)); + } + }); + } + + @Test + public void forEachReadableMustVisitAllReadableConstituentBuffersInOrder() { + try (Allocator allocator = Allocator.heap()) { + Buf composite; + try (Buf a = allocator.allocate(4); + Buf b = allocator.allocate(4); + Buf c = allocator.allocate(4)) { + a.writeInt(1); + b.writeInt(2); + c.writeInt(3); + composite = allocator.compose(a, b, c); + } + var list = new LinkedList(List.of(1, 2, 3)); + composite.forEachReadable(component -> { + assertEquals(list.pollFirst().intValue(), component.byteBuffer().getInt()); + }); + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void forEachReadableOnClosedBufferMustThrow(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator()) { + var buf = allocator.allocate(8); + buf.writeLong(0); + buf.close(); + assertThrows(IllegalStateException.class, () -> buf.forEachReadable(component -> { })); + } + } + // @ParameterizedTest @MethodSource("allocators") @@ -2590,7 +2705,7 @@ public class BufTest { void relativeReadOfByteMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); byte value = 0x01; @@ -2629,6 +2744,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfByteReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getByte(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfByteMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -2645,7 +2769,7 @@ public class BufTest { void offsettedGetOfByteMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); byte value = 0x01; buf.writeByte(value); buf.setByte(0, (byte) 0x10); @@ -2664,6 +2788,17 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfByteReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + byte value = 0x01; + buf.writeByte(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getByte(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfByteMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -2673,6 +2808,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfByteReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getByte(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeReadOfUnsignedByteMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -2694,7 +2838,7 @@ public class BufTest { void relativeReadOfUnsignedByteMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); int value = 0x01; @@ -2724,6 +2868,23 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void relativeReadOfUnsignedByteReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsBeyondWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertEquals(0, buf.readableBytes()); + assertEquals(Long.BYTES, buf.writableBytes()); + int value = 0x01; + buf.writeUnsignedByte(value); + buf.readerOffset(1); + assertEquals(0, buf.readableBytes()); + assertEquals(7, buf.writableBytes()); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).readUnsignedByte()); + assertEquals(0, buf.readableBytes()); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedByteMustBoundsCheckOnNegativeOffset(Fixture fixture) { @@ -2733,6 +2894,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedByteReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedByte(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedByteMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -2749,7 +2919,7 @@ public class BufTest { void offsettedGetOfUnsignedByteMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x01; buf.writeUnsignedByte(value); buf.setByte(0, (byte) 0x10); @@ -2768,6 +2938,18 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedByteReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset( + Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + int value = 0x01; + buf.writeUnsignedByte(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedByte(1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedByteMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -2777,6 +2959,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedByteReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedByte(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeWriteOfByteMustBoundsCheckWhenWriteOffsetAndSizeIsBeyondCapacity(Fixture fixture) { @@ -2797,7 +2988,7 @@ public class BufTest { void relativeWriteOfByteMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); byte value = 0x01; buf.writeByte(value); buf.writerOffset(Long.BYTES); @@ -2845,7 +3036,7 @@ public class BufTest { void offsettedSetOfByteMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); byte value = 0x01; buf.setByte(0, value); buf.writerOffset(Long.BYTES); @@ -2880,7 +3071,7 @@ public class BufTest { void relativeWriteOfUnsignedByteMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x01; buf.writeUnsignedByte(value); buf.writerOffset(Long.BYTES); @@ -2928,7 +3119,7 @@ public class BufTest { void offsettedSetOfUnsignedByteMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x01; buf.setUnsignedByte(0, value); buf.writerOffset(Long.BYTES); @@ -2964,7 +3155,7 @@ public class BufTest { void relativeReadOfCharMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); char value = 0x0102; @@ -2994,6 +3185,23 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void relativeReadOfCharReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsBeyondWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertEquals(0, buf.readableBytes()); + assertEquals(Long.BYTES, buf.writableBytes()); + char value = 0x0102; + buf.writeChar(value); + buf.readerOffset(1); + assertEquals(1, buf.readableBytes()); + assertEquals(6, buf.writableBytes()); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).readChar()); + assertEquals(1, buf.readableBytes()); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfCharMustBoundsCheckOnNegativeOffset(Fixture fixture) { @@ -3003,6 +3211,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfCharReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getChar(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfCharMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -3019,7 +3236,7 @@ public class BufTest { void offsettedGetOfCharMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); char value = 0x0102; buf.writeChar(value); buf.setByte(0, (byte) 0x10); @@ -3038,6 +3255,17 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfCharReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + char value = 0x0102; + buf.writeChar(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getChar(1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfCharMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -3047,6 +3275,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfCharReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getChar(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeWriteOfCharMustBoundsCheckWhenWriteOffsetAndSizeIsBeyondCapacity(Fixture fixture) { @@ -3067,7 +3304,7 @@ public class BufTest { void relativeWriteOfCharMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); char value = 0x0102; buf.writeChar(value); buf.writerOffset(Long.BYTES); @@ -3115,7 +3352,7 @@ public class BufTest { void offsettedSetOfCharMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); char value = 0x0102; buf.setChar(0, value); buf.writerOffset(Long.BYTES); @@ -3151,7 +3388,7 @@ public class BufTest { void relativeReadOfShortMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); short value = 0x0102; @@ -3181,6 +3418,23 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void relativeReadOfShortReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsBeyondWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertEquals(0, buf.readableBytes()); + assertEquals(Long.BYTES, buf.writableBytes()); + short value = 0x0102; + buf.writeShort(value); + buf.readerOffset(1); + assertEquals(1, buf.readableBytes()); + assertEquals(6, buf.writableBytes()); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).readShort()); + assertEquals(1, buf.readableBytes()); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfShortMustBoundsCheckOnNegativeOffset(Fixture fixture) { @@ -3190,6 +3444,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfShortReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getShort(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfShortMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -3206,7 +3469,7 @@ public class BufTest { void offsettedGetOfShortMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); short value = 0x0102; buf.writeShort(value); buf.setByte(0, (byte) 0x10); @@ -3225,6 +3488,17 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfShortReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + short value = 0x0102; + buf.writeShort(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getShort(1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfShortMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -3234,6 +3508,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfShortReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getShort(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeReadOfUnsignedShortMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -3255,7 +3538,7 @@ public class BufTest { void relativeReadOfUnsignedShortMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); int value = 0x0102; @@ -3285,6 +3568,23 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void relativeReadOfUnsignedShortReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsBeyondWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertEquals(0, buf.readableBytes()); + assertEquals(Long.BYTES, buf.writableBytes()); + int value = 0x0102; + buf.writeUnsignedShort(value); + buf.readerOffset(1); + assertEquals(1, buf.readableBytes()); + assertEquals(6, buf.writableBytes()); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).readUnsignedShort()); + assertEquals(1, buf.readableBytes()); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedShortMustBoundsCheckOnNegativeOffset(Fixture fixture) { @@ -3294,6 +3594,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedShortReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedShort(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedShortMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -3310,7 +3619,7 @@ public class BufTest { void offsettedGetOfUnsignedShortMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x0102; buf.writeUnsignedShort(value); buf.setByte(0, (byte) 0x10); @@ -3329,6 +3638,18 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedShortReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset( + Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + int value = 0x0102; + buf.writeUnsignedShort(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedShort(1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedShortMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -3338,6 +3659,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedShortReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedShort(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeWriteOfShortMustBoundsCheckWhenWriteOffsetAndSizeIsBeyondCapacity(Fixture fixture) { @@ -3358,7 +3688,7 @@ public class BufTest { void relativeWriteOfShortMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); short value = 0x0102; buf.writeShort(value); buf.writerOffset(Long.BYTES); @@ -3406,7 +3736,7 @@ public class BufTest { void offsettedSetOfShortMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); short value = 0x0102; buf.setShort(0, value); buf.writerOffset(Long.BYTES); @@ -3441,7 +3771,7 @@ public class BufTest { void relativeWriteOfUnsignedShortMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x0102; buf.writeUnsignedShort(value); buf.writerOffset(Long.BYTES); @@ -3489,7 +3819,7 @@ public class BufTest { void offsettedSetOfUnsignedShortMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x0102; buf.setUnsignedShort(0, value); buf.writerOffset(Long.BYTES); @@ -3525,7 +3855,7 @@ public class BufTest { void relativeReadOfMediumMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); int value = 0x010203; @@ -3555,6 +3885,23 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void relativeReadOfMediumReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsBeyondWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertEquals(0, buf.readableBytes()); + assertEquals(Long.BYTES, buf.writableBytes()); + int value = 0x010203; + buf.writeMedium(value); + buf.readerOffset(1); + assertEquals(2, buf.readableBytes()); + assertEquals(5, buf.writableBytes()); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).readMedium()); + assertEquals(2, buf.readableBytes()); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfMediumMustBoundsCheckOnNegativeOffset(Fixture fixture) { @@ -3564,6 +3911,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfMediumReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getMedium(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfMediumMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -3580,7 +3936,7 @@ public class BufTest { void offsettedGetOfMediumMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x010203; buf.writeMedium(value); buf.setByte(0, (byte) 0x10); @@ -3599,6 +3955,17 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfMediumReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + int value = 0x010203; + buf.writeMedium(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getMedium(1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfMediumMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -3608,6 +3975,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfMediumReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getMedium(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeReadOfUnsignedMediumMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -3629,7 +4005,7 @@ public class BufTest { void relativeReadOfUnsignedMediumMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); int value = 0x010203; @@ -3659,6 +4035,23 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void relativeReadOfUnsignedMediumReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsBeyondWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertEquals(0, buf.readableBytes()); + assertEquals(Long.BYTES, buf.writableBytes()); + int value = 0x010203; + buf.writeUnsignedMedium(value); + buf.readerOffset(1); + assertEquals(2, buf.readableBytes()); + assertEquals(5, buf.writableBytes()); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).readUnsignedMedium()); + assertEquals(2, buf.readableBytes()); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedMediumMustBoundsCheckOnNegativeOffset(Fixture fixture) { @@ -3668,6 +4061,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedMediumReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedMedium(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedMediumMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -3684,7 +4086,7 @@ public class BufTest { void offsettedGetOfUnsignedMediumMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x010203; buf.writeUnsignedMedium(value); buf.setByte(0, (byte) 0x10); @@ -3703,6 +4105,18 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedMediumReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset( + Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + int value = 0x010203; + buf.writeUnsignedMedium(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedMedium(1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedMediumMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -3712,6 +4126,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedMediumReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedMedium(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeWriteOfMediumMustBoundsCheckWhenWriteOffsetAndSizeIsBeyondCapacity(Fixture fixture) { @@ -3732,7 +4155,7 @@ public class BufTest { void relativeWriteOfMediumMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x010203; buf.writeMedium(value); buf.writerOffset(Long.BYTES); @@ -3780,7 +4203,7 @@ public class BufTest { void offsettedSetOfMediumMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x010203; buf.setMedium(0, value); buf.writerOffset(Long.BYTES); @@ -3815,7 +4238,7 @@ public class BufTest { void relativeWriteOfUnsignedMediumMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x010203; buf.writeUnsignedMedium(value); buf.writerOffset(Long.BYTES); @@ -3863,7 +4286,7 @@ public class BufTest { void offsettedSetOfUnsignedMediumMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x010203; buf.setUnsignedMedium(0, value); buf.writerOffset(Long.BYTES); @@ -3899,7 +4322,7 @@ public class BufTest { void relativeReadOfIntMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); int value = 0x01020304; @@ -3929,6 +4352,23 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void relativeReadOfIntReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsBeyondWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertEquals(0, buf.readableBytes()); + assertEquals(Long.BYTES, buf.writableBytes()); + int value = 0x01020304; + buf.writeInt(value); + buf.readerOffset(1); + assertEquals(3, buf.readableBytes()); + assertEquals(4, buf.writableBytes()); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).readInt()); + assertEquals(3, buf.readableBytes()); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfIntMustBoundsCheckOnNegativeOffset(Fixture fixture) { @@ -3938,6 +4378,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfIntReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getInt(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfIntMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -3954,7 +4403,7 @@ public class BufTest { void offsettedGetOfIntMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x01020304; buf.writeInt(value); buf.setByte(0, (byte) 0x10); @@ -3973,6 +4422,17 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfIntReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + int value = 0x01020304; + buf.writeInt(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getInt(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfIntMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -3982,6 +4442,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfIntReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getInt(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeReadOfUnsignedIntMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -4003,7 +4472,7 @@ public class BufTest { void relativeReadOfUnsignedIntMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); long value = 0x01020304; @@ -4033,6 +4502,23 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void relativeReadOfUnsignedIntReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsBeyondWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertEquals(0, buf.readableBytes()); + assertEquals(Long.BYTES, buf.writableBytes()); + long value = 0x01020304; + buf.writeUnsignedInt(value); + buf.readerOffset(1); + assertEquals(3, buf.readableBytes()); + assertEquals(4, buf.writableBytes()); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).readUnsignedInt()); + assertEquals(3, buf.readableBytes()); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedIntMustBoundsCheckOnNegativeOffset(Fixture fixture) { @@ -4042,6 +4528,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedIntReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedInt(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedIntMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -4058,7 +4553,7 @@ public class BufTest { void offsettedGetOfUnsignedIntMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); long value = 0x01020304; buf.writeUnsignedInt(value); buf.setByte(0, (byte) 0x10); @@ -4077,6 +4572,18 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedIntReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset( + Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + long value = 0x01020304; + buf.writeUnsignedInt(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedInt(1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfUnsignedIntMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -4086,6 +4593,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfUnsignedIntReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getUnsignedInt(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeWriteOfIntMustBoundsCheckWhenWriteOffsetAndSizeIsBeyondCapacity(Fixture fixture) { @@ -4106,7 +4622,7 @@ public class BufTest { void relativeWriteOfIntMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x01020304; buf.writeInt(value); buf.writerOffset(Long.BYTES); @@ -4154,7 +4670,7 @@ public class BufTest { void offsettedSetOfIntMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); int value = 0x01020304; buf.setInt(0, value); buf.writerOffset(Long.BYTES); @@ -4189,7 +4705,7 @@ public class BufTest { void relativeWriteOfUnsignedIntMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); long value = 0x01020304; buf.writeUnsignedInt(value); buf.writerOffset(Long.BYTES); @@ -4237,7 +4753,7 @@ public class BufTest { void offsettedSetOfUnsignedIntMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); long value = 0x01020304; buf.setUnsignedInt(0, value); buf.writerOffset(Long.BYTES); @@ -4273,7 +4789,7 @@ public class BufTest { void relativeReadOfFloatMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); float value = Float.intBitsToFloat(0x01020304); @@ -4303,6 +4819,23 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void relativeReadOfFloatReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsBeyondWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertEquals(0, buf.readableBytes()); + assertEquals(Long.BYTES, buf.writableBytes()); + float value = Float.intBitsToFloat(0x01020304); + buf.writeFloat(value); + buf.readerOffset(1); + assertEquals(3, buf.readableBytes()); + assertEquals(4, buf.writableBytes()); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).readFloat()); + assertEquals(3, buf.readableBytes()); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfFloatMustBoundsCheckOnNegativeOffset(Fixture fixture) { @@ -4312,6 +4845,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfFloatReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getFloat(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfFloatMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -4328,7 +4870,7 @@ public class BufTest { void offsettedGetOfFloatMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); float value = Float.intBitsToFloat(0x01020304); buf.writeFloat(value); buf.setByte(0, (byte) 0x10); @@ -4347,6 +4889,17 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfFloatReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + float value = Float.intBitsToFloat(0x01020304); + buf.writeFloat(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getFloat(1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfFloatMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -4356,6 +4909,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfFloatReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getFloat(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeWriteOfFloatMustBoundsCheckWhenWriteOffsetAndSizeIsBeyondCapacity(Fixture fixture) { @@ -4376,7 +4938,7 @@ public class BufTest { void relativeWriteOfFloatMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); float value = Float.intBitsToFloat(0x01020304); buf.writeFloat(value); buf.writerOffset(Long.BYTES); @@ -4424,7 +4986,7 @@ public class BufTest { void offsettedSetOfFloatMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); float value = Float.intBitsToFloat(0x01020304); buf.setFloat(0, value); buf.writerOffset(Long.BYTES); @@ -4460,7 +5022,7 @@ public class BufTest { void relativeReadOfLongMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); long value = 0x0102030405060708L; @@ -4490,6 +5052,23 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void relativeReadOfLongReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsBeyondWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertEquals(0, buf.readableBytes()); + assertEquals(Long.BYTES, buf.writableBytes()); + long value = 0x0102030405060708L; + buf.writeLong(value); + buf.readerOffset(1); + assertEquals(7, buf.readableBytes()); + assertEquals(0, buf.writableBytes()); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).readLong()); + assertEquals(7, buf.readableBytes()); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfLongMustBoundsCheckOnNegativeOffset(Fixture fixture) { @@ -4499,6 +5078,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfLongReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getLong(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfLongMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -4515,7 +5103,7 @@ public class BufTest { void offsettedGetOfLongMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); long value = 0x0102030405060708L; buf.writeLong(value); buf.setByte(0, (byte) 0x10); @@ -4534,6 +5122,17 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfLongReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + long value = 0x0102030405060708L; + buf.writeLong(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getLong(1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfLongMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -4543,6 +5142,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfLongReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getLong(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeWriteOfLongMustBoundsCheckWhenWriteOffsetAndSizeIsBeyondCapacity(Fixture fixture) { @@ -4563,7 +5171,7 @@ public class BufTest { void relativeWriteOfLongMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); long value = 0x0102030405060708L; buf.writeLong(value); buf.writerOffset(Long.BYTES); @@ -4611,7 +5219,7 @@ public class BufTest { void offsettedSetOfLongMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); long value = 0x0102030405060708L; buf.setLong(0, value); buf.writerOffset(Long.BYTES); @@ -4647,7 +5255,7 @@ public class BufTest { void relativeReadOfDoubleMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); assertEquals(0, buf.readableBytes()); assertEquals(Long.BYTES, buf.writableBytes()); double value = Double.longBitsToDouble(0x0102030405060708L); @@ -4677,6 +5285,23 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void relativeReadOfDoubleReadOnllyMustBoundsCheckWhenReadOffsetAndSizeIsBeyondWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertEquals(0, buf.readableBytes()); + assertEquals(Long.BYTES, buf.writableBytes()); + double value = Double.longBitsToDouble(0x0102030405060708L); + buf.writeDouble(value); + buf.readerOffset(1); + assertEquals(7, buf.readableBytes()); + assertEquals(0, buf.writableBytes()); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).readDouble()); + assertEquals(7, buf.readableBytes()); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfDoubleMustBoundsCheckOnNegativeOffset(Fixture fixture) { @@ -4686,6 +5311,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfDoubleReadOnlyMustBoundsCheckOnNegativeOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getDouble(-1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfDoubleMustNotBoundsCheckWhenReadOffsetAndSizeIsEqualToWriteOffset(Fixture fixture) { @@ -4702,7 +5336,7 @@ public class BufTest { void offsettedGetOfDoubleMustReadWithDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); double value = Double.longBitsToDouble(0x0102030405060708L); buf.writeDouble(value); buf.setByte(0, (byte) 0x10); @@ -4721,6 +5355,17 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfDoubleReadOnlyMustBoundsCheckWhenReadOffsetAndSizeIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + double value = Double.longBitsToDouble(0x0102030405060708L); + buf.writeDouble(value); + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getDouble(1)); + } + } + @ParameterizedTest @MethodSource("allocators") void offsettedGetOfDoubleMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { @@ -4730,6 +5375,15 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + void offsettedGetOfDoubleReadOnlyMustBoundsCheckWhenReadOffsetIsGreaterThanWriteOffset(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThrows(IndexOutOfBoundsException.class, () -> buf.readOnly(true).getDouble(0)); + } + } + @ParameterizedTest @MethodSource("allocators") void relativeWriteOfDoubleMustBoundsCheckWhenWriteOffsetAndSizeIsBeyondCapacity(Fixture fixture) { @@ -4750,7 +5404,7 @@ public class BufTest { void relativeWriteOfDoubleMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); double value = Double.longBitsToDouble(0x0102030405060708L); buf.writeDouble(value); buf.writerOffset(Long.BYTES); @@ -4798,7 +5452,7 @@ public class BufTest { void offsettedSetOfDoubleMustHaveDefaultEndianByteOrder(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - buf.order(ByteOrder.BIG_ENDIAN); + buf.order(BIG_ENDIAN); double value = Double.longBitsToDouble(0x0102030405060708L); buf.setDouble(0, value); buf.writerOffset(Long.BYTES); diff --git a/src/test/java/io/netty/buffer/api/Fixture.java b/src/test/java/io/netty/buffer/api/Fixture.java index ba63ab4..9bcf4ad 100644 --- a/src/test/java/io/netty/buffer/api/Fixture.java +++ b/src/test/java/io/netty/buffer/api/Fixture.java @@ -56,6 +56,10 @@ public final class Fixture implements Supplier { return properties.contains(Properties.DIRECT); } + public boolean isComposite() { + return properties.contains(Properties.COMPOSITE); + } + public boolean isPooled() { return properties.contains(Properties.POOLED); } From 46ed14577c5f9aa9f3de337b38a5af924f09d5ea Mon Sep 17 00:00:00 2001 From: Chris Vest Date: Fri, 15 Jan 2021 15:54:03 +0100 Subject: [PATCH 2/6] Add Buf.forEachWritable Pass iteration indexes through. --- src/main/java/io/netty/buffer/api/Buf.java | 70 +++- .../java/io/netty/buffer/api/Component.java | 24 +- .../netty/buffer/api/ComponentProcessor.java | 21 ++ .../io/netty/buffer/api/CompositeBuf.java | 67 +++- .../io/netty/buffer/api/memseg/MemSegBuf.java | 87 ++++- .../java/io/netty/buffer/api/BufTest.java | 340 ++++++++++++++++-- 6 files changed, 552 insertions(+), 57 deletions(-) create mode 100644 src/main/java/io/netty/buffer/api/ComponentProcessor.java diff --git a/src/main/java/io/netty/buffer/api/Buf.java b/src/main/java/io/netty/buffer/api/Buf.java index 4bbee0b..86d8754 100644 --- a/src/main/java/io/netty/buffer/api/Buf.java +++ b/src/main/java/io/netty/buffer/api/Buf.java @@ -17,7 +17,6 @@ package io.netty.buffer.api; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.function.Consumer; /** * A reference counted buffer of memory, with separate reader and writer offsets. @@ -490,19 +489,74 @@ public interface Buf extends Rc, BufAccessors { * * @return The number of components in this buffer. */ - int componentCount(); + int countComponents(); /** - * Process all readable components of this buffer, and return the number of components consumed. + * Get the number of "components" in this buffer, that are readable. These are the components that would be + * processed by {@link #forEachReadable(int, ComponentProcessor)}. For composite buffers, this is the number of + * transitive constituent buffers that are readable, while non-composite buffers only have at most one readable + * component. *

- * The number of components consumed may be less than the {@linkplain #componentCount() component count} if not all - * of them have readable data. + * The number of readable components may be less than the {@link #countComponents() component count}, if not all of + * them have readable data. * + * @return The number of readable components in this buffer. + */ + int countReadableComponents(); + + /** + * Get the number of "components" in this buffer, that are writable. These are the components that would be + * processed by {@link #forEachWritable(int, ComponentProcessor)}. For composite buffers, this is the number of + * transitive constituent buffers that are writable, while non-composite buffers only have at most one writable + * component. + *

+ * The number of writable components may be less than the {@link #countComponents() component count}, if not all of + * them have space for writing. + * + * @return The number of writable components in this buffer. + */ + int countWritableComponents(); + + /** + * Process all readable components of this buffer, and return the number of components processed. + *

+ * The given {@linkplain ComponentProcessor processor} is called for each component in this buffer, and passed a + * component index, for the given component in the iteration, and a {@link Component} object for accessing the data + * within the given component. + *

+ * The component index is specific to the particular invokation of this method, and may change. The first call to + * the consumer will be passed the given initial index, and the next call will be passed the initial index plus one, + * and so on. + *

+ * The {@link ComponentProcessor} may stop the iteration at any time by returning {@code false}. This may cause the + * number of components processed to be returned as a negative number (to signal early return), and the number of + * components processed may then be less than the {@linkplain #countReadableComponents() readable component count}. + *

* Note that the {@link Component} instance passed to the consumer could be reused for multiple * calls, so the data must be extracted from the component in the context of the iteration. + *

+ * The {@link ByteBuffer} instances obtained from the component, share life time with that internal component. + * This means they can be accessed as long as the internal memory store remain unchanged. Methods that may cause + * such changes, are any method that requires the buffer to be {@linkplain #isOwned() owned}. + *

+ * The best way to ensure this doesn't cause any trouble, is to use the buffers directly as part of the iteration, + * or immediately after the iteration. + *

+ * Note that the arrays, memory addresses, and byte buffers exposed as components by this method, + * should not be used for changing the buffer contents. Doing so may cause undefined behaviour. + *

+ * Changes to position and limit of the byte buffers exposed via the processed components, are not reflected back to + * this buffer instance. * - * @param consumer The consumer that will be used to process the buffer components. - * @return The number of readable components processed, which may be less than {@link #componentCount()}. + * @param initialIndex The initial index of the iteration, and the index that will be passed to the first call to + * the {@linkplain ComponentProcessor#process(int, Component) processor}. + * @param processor The processor that will be used to process the buffer components. + * @return The number of readable components processed, as a positive number of all readable components were + * processed, or as a negative number if the iteration was stopped because + * {@link ComponentProcessor#process(int, Component)} returned {@code false}. + * In any case, the number of components processed may be less than {@link #countComponents()}. */ - int forEachReadable(Consumer consumer); + int forEachReadable(int initialIndex, ComponentProcessor processor); + + int forEachWritable(int initialIndex, ComponentProcessor processor); } diff --git a/src/main/java/io/netty/buffer/api/Component.java b/src/main/java/io/netty/buffer/api/Component.java index 997cdc9..fa72615 100644 --- a/src/main/java/io/netty/buffer/api/Component.java +++ b/src/main/java/io/netty/buffer/api/Component.java @@ -16,10 +16,10 @@ package io.netty.buffer.api; import java.nio.ByteBuffer; -import java.util.function.Consumer; /** - * A view onto the buffer component being processed in a given iteration of {@link Buf#forEachReadable(Consumer)}. + * A view onto the buffer component being processed in a given iteration of + * {@link Buf#forEachReadable(int, ComponentProcessor)}, or {@link Buf#forEachWritable(int, ComponentProcessor)}. *

* Instances of this interface are allowed to be mutable behind the scenes, and the data is only guaranteed to be * consistent within the given iteration. @@ -28,30 +28,44 @@ public interface Component { /** * Check if this component is backed by a cached byte array than can be accessed cheaply. + *

+ * Note that regardless of what this method returns, the array should not be used to modify the + * contents of this buffer component. * * @return {@code true} if {@link #array()} is a cheap operation, otherwise {@code false}. */ - boolean hasCachedArray(); + boolean hasArray(); /** * Get a byte array of the contents of this component. *

* Note that the array is meant to be read-only. It may either be a direct reference to the * concrete array instance that is backing this component, or it is a fresh copy. + * Writing to the array may produce undefined behaviour. * * @return A byte array of the contents of this component. */ byte[] array(); + int arrayOffset(); + /** * Give the native memory address backing this buffer, or return 0 if this buffer has no native memory address. + *

+ * Note that the address should not be used for writing to the buffer memory, and doing so may + * produce undefined behaviour. + * * @return The native memory address, if any, otherwise 0. */ long nativeAddress(); /** - * Build a {@link ByteBuffer} instance for this memory component. + * Get a {@link ByteBuffer} instance for this memory component. + *

+ * Note that the {@link ByteBuffer} is read-only, to prevent write accesses to the memory, + * when the buffer component is obtained through {@link Buf#forEachReadable(int, ComponentProcessor)}. + * * @return A new {@link ByteBuffer} for this memory component. */ - ByteBuffer byteBuffer(); + ByteBuffer buffer(); } diff --git a/src/main/java/io/netty/buffer/api/ComponentProcessor.java b/src/main/java/io/netty/buffer/api/ComponentProcessor.java new file mode 100644 index 0000000..e83b214 --- /dev/null +++ b/src/main/java/io/netty/buffer/api/ComponentProcessor.java @@ -0,0 +1,21 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.buffer.api; + +@FunctionalInterface +public interface ComponentProcessor { + boolean process(int index, Component component); +} diff --git a/src/main/java/io/netty/buffer/api/CompositeBuf.java b/src/main/java/io/netty/buffer/api/CompositeBuf.java index a4d9bca..6209cdc 100644 --- a/src/main/java/io/netty/buffer/api/CompositeBuf.java +++ b/src/main/java/io/netty/buffer/api/CompositeBuf.java @@ -19,7 +19,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.Objects; -import java.util.function.Consumer; final class CompositeBuf extends RcSupport implements Buf { /** @@ -49,7 +48,12 @@ final class CompositeBuf extends RcSupport implements Buf { private boolean readOnly; CompositeBuf(Allocator allocator, Buf[] bufs) { - this(allocator, true, bufs.clone(), COMPOSITE_DROP); // Clone prevents external modification of array. + this(allocator, true, filterExternalBufs(bufs), COMPOSITE_DROP); + } + + private static Buf[] filterExternalBufs(Buf[] bufs) { + // Allocating a new array unconditionally also prevents external modification of the array. + return Arrays.stream(bufs).filter(b -> b.capacity() > 0).toArray(Buf[]::new); } private CompositeBuf(Allocator allocator, boolean isSendable, Buf[] bufs, Drop drop) { @@ -617,7 +621,14 @@ final class CompositeBuf extends RcSupport implements Buf { (extension.readOnly()? "read-only." : "writable.")); } - long newSize = capacity() + (long) extension.capacity(); + long extensionCapacity = extension.capacity(); + if (extensionCapacity == 0) { + // Extending by a zero-sized buffer makes no difference. Especially since it's not allowed to change the + // capacity of buffers that are constiuents of composite buffers. + return; + } + + long newSize = capacity() + extensionCapacity; Allocator.checkSize(newSize); Buf[] restoreTemp = bufs; // We need this to restore our buffer array, in case offset computations fail. @@ -702,21 +713,63 @@ final class CompositeBuf extends RcSupport implements Buf { } @Override - public int componentCount() { + public int countComponents() { int sum = 0; for (Buf buf : bufs) { - sum += buf.componentCount(); + sum += buf.countComponents(); } return sum; } @Override - public int forEachReadable(Consumer consumer) { + public int countReadableComponents() { + int sum = 0; + for (Buf buf : bufs) { + sum += buf.countReadableComponents(); + } + return sum; + } + + @Override + public int countWritableComponents() { + int sum = 0; + for (Buf buf : bufs) { + sum += buf.countWritableComponents(); + } + return sum; + } + + @Override + public int forEachReadable(int initialIndex, ComponentProcessor processor) { checkReadBounds(readerOffset(), Math.max(1, readableBytes())); int visited = 0; for (Buf buf : bufs) { if (buf.readableBytes() > 0) { - visited += buf.forEachReadable(consumer); + int count = buf.forEachReadable(visited + initialIndex, processor); + if (count > 0) { + visited += count; + } else { + visited = -visited + count; + break; + } + } + } + return visited; + } + + @Override + public int forEachWritable(int initialIndex, ComponentProcessor processor) { + checkWriteBounds(writerOffset(), Math.max(1, writableBytes())); + int visited = 0; + for (Buf buf : bufs) { + if (buf.writableBytes() > 0) { + int count = buf.forEachWritable(visited + initialIndex, processor); + if (count > 0) { + visited += count; + } else { + visited = -visited + count; + break; + } } } return visited; diff --git a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java index 5285784..2c73d2b 100644 --- a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java +++ b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java @@ -20,6 +20,7 @@ import io.netty.buffer.api.AllocatorControl; import io.netty.buffer.api.Buf; import io.netty.buffer.api.ByteCursor; import io.netty.buffer.api.Component; +import io.netty.buffer.api.ComponentProcessor; import io.netty.buffer.api.Drop; import io.netty.buffer.api.Owned; import io.netty.buffer.api.RcSupport; @@ -27,7 +28,6 @@ import jdk.incubator.foreign.MemorySegment; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.function.Consumer; import static jdk.incubator.foreign.MemoryAccess.getByteAtOffset; import static jdk.incubator.foreign.MemoryAccess.getCharAtOffset; @@ -135,13 +135,18 @@ class MemSegBuf extends RcSupport implements Buf, Component { } @Override - public boolean hasCachedArray() { + public boolean hasArray() { return false; } @Override public byte[] array() { - return seg.toByteArray(); + return null; + } + + @Override + public int arrayOffset() { + return 0; } @Override @@ -154,19 +159,17 @@ class MemSegBuf extends RcSupport implements Buf, Component { } @Override - public ByteBuffer byteBuffer() { + public ByteBuffer buffer() { var buffer = seg.asByteBuffer(); int base = baseOffset; if (buffer.isDirect()) { - // TODO Remove this when JDK bug is fixed. + // TODO Remove this when the slicing of shared, native segments JDK bug is fixed. ByteBuffer tmp = ByteBuffer.allocateDirect(buffer.capacity()); tmp.put(buffer); buffer = tmp.position(0); base = 0; // TODO native memory segments do not have the buffer-of-slice bug. } - if (readOnly()) { - buffer = buffer.asReadOnlyBuffer(); - } + buffer = buffer.asReadOnlyBuffer(); // TODO avoid slicing and just set position+limit when JDK bug is fixed. return buffer.slice(base + readerOffset(), readableBytes()).order(order); } @@ -492,15 +495,75 @@ class MemSegBuf extends RcSupport implements Buf, Component { } @Override - public int componentCount() { + public int countComponents() { return 1; } @Override - public int forEachReadable(Consumer consumer) { + public int countReadableComponents() { + return readableBytes() > 0? 1 : 0; + } + + @Override + public int countWritableComponents() { + return writableBytes() > 0? 1 : 0; + } + + @Override + public int forEachReadable(int initialIndex, ComponentProcessor processor) { checkRead(readerOffset(), Math.max(1, readableBytes())); - consumer.accept(this); - return 1; + return processor.process(initialIndex, this)? 1 : -1; + } + + @Override + public int forEachWritable(int initialIndex, ComponentProcessor processor) { + checkWrite(writerOffset(), Math.max(1, writableBytes())); + + var buffer = wseg.asByteBuffer(); + + if (buffer.isDirect()) { + buffer = buffer.position(writerOffset()).limit(writerOffset() + writableBytes()); + } else { + // TODO avoid slicing and just set position when JDK bug is fixed. + buffer = buffer.slice(baseOffset + writerOffset(), writableBytes()); + } + buffer = buffer.order(order); + return processor.process(initialIndex, new WritableComponent(wseg, buffer))? 1 : -1; + } + + private static final class WritableComponent implements Component { + private final MemorySegment segment; + private final ByteBuffer buffer; + + private WritableComponent(MemorySegment segment, ByteBuffer buffer) { + this.segment = segment; + this.buffer = buffer; + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public byte[] array() { + return null; + } + + @Override + public int arrayOffset() { + return 0; + } + + @Override + public long nativeAddress() { + return buffer.isDirect()? segment.address().toRawLongValue() : 0; + } + + @Override + public ByteBuffer buffer() { + return buffer; + } } // diff --git a/src/test/java/io/netty/buffer/api/BufTest.java b/src/test/java/io/netty/buffer/api/BufTest.java index 23088d1..d241ba4 100644 --- a/src/test/java/io/netty/buffer/api/BufTest.java +++ b/src/test/java/io/netty/buffer/api/BufTest.java @@ -17,6 +17,7 @@ package io.netty.buffer.api; import io.netty.buffer.api.Fixture.Properties; import io.netty.buffer.api.memseg.NativeMemorySegmentManager; +import org.assertj.core.api.AtomicIntegerArrayAssert; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; @@ -40,6 +41,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.Stream.Builder; @@ -2588,7 +2590,29 @@ public class BufTest { public void componentCountOfNonCompositeBufferMustBeOne(Fixture fixture) { try (Allocator allocator = fixture.createAllocator(); Buf buf = allocator.allocate(8)) { - assertThat(buf.componentCount()).isOne(); + assertThat(buf.countComponents()).isOne(); + } + } + + @ParameterizedTest + @MethodSource("nonCompositeAllocators") + public void readableComponentCountMustBeOneIfThereAreReadableBytes(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThat(buf.countReadableComponents()).isZero(); + buf.writeByte((byte) 1); + assertThat(buf.countReadableComponents()).isOne(); + } + } + + @ParameterizedTest + @MethodSource("nonCompositeAllocators") + public void writableComponentCountMustBeOneIfThereAreWritableBytes(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertThat(buf.countWritableComponents()).isOne(); + buf.writeLong(1); + assertThat(buf.countWritableComponents()).isZero(); } } @@ -2602,7 +2626,27 @@ public class BufTest { Buf x = allocator.compose(b, c)) { buf = allocator.compose(a, x); } - assertThat(buf.componentCount()).isEqualTo(3); + assertThat(buf.countComponents()).isEqualTo(3); + assertThat(buf.countReadableComponents()).isZero(); + assertThat(buf.countWritableComponents()).isEqualTo(3); + buf.writeInt(1); + assertThat(buf.countReadableComponents()).isOne(); + assertThat(buf.countWritableComponents()).isEqualTo(3); + buf.writeInt(1); + assertThat(buf.countReadableComponents()).isOne(); + assertThat(buf.countWritableComponents()).isEqualTo(2); + buf.writeInt(1); + assertThat(buf.countReadableComponents()).isEqualTo(2); + assertThat(buf.countWritableComponents()).isEqualTo(2); + buf.writeInt(1); + assertThat(buf.countReadableComponents()).isEqualTo(2); + assertThat(buf.countWritableComponents()).isOne(); + buf.writeInt(1); + assertThat(buf.countReadableComponents()).isEqualTo(3); + assertThat(buf.countWritableComponents()).isOne(); + buf.writeInt(1); + assertThat(buf.countReadableComponents()).isEqualTo(3); + assertThat(buf.countWritableComponents()).isZero(); } } @@ -2614,16 +2658,16 @@ public class BufTest { Buf bufLERW = allocator.allocate(8).order(LITTLE_ENDIAN).writeLong(0x0102030405060708L); Buf bufBERO = allocator.allocate(8).order(BIG_ENDIAN).writeLong(0x0102030405060708L).readOnly(true); Buf bufLERO = allocator.allocate(8).order(LITTLE_ENDIAN).writeLong(0x0102030405060708L).readOnly(true)) { - verifyForEachReadableSignleComponent(fixture, bufBERW); - verifyForEachReadableSignleComponent(fixture, bufLERW); - verifyForEachReadableSignleComponent(fixture, bufBERO); - verifyForEachReadableSignleComponent(fixture, bufLERO); + verifyForEachReadableSingleComponent(fixture, bufBERW); + verifyForEachReadableSingleComponent(fixture, bufLERW); + verifyForEachReadableSingleComponent(fixture, bufBERO); + verifyForEachReadableSingleComponent(fixture, bufLERO); } } - private static void verifyForEachReadableSignleComponent(Fixture fixture, Buf buf) { - buf.forEachReadable(component -> { - var buffer = component.byteBuffer(); + private static void verifyForEachReadableSingleComponent(Fixture fixture, Buf buf) { + buf.forEachReadable(0, (index, component) -> { + var buffer = component.buffer(); assertThat(buffer.position()).isZero(); assertThat(buffer.limit()).isEqualTo(8); assertThat(buffer.capacity()).isEqualTo(8); @@ -2635,21 +2679,17 @@ public class BufTest { assertThat(component.nativeAddress()).isZero(); } - byte[] array = component.array(); - if (buffer.order() == BIG_ENDIAN) { - assertThat(array).containsExactly(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); - } else { - assertThat(array).containsExactly(0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01); + if (component.hasArray()) { + byte[] array = component.array(); + if (buffer.order() == BIG_ENDIAN) { + assertThat(array).containsExactly(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); + } else { + assertThat(array).containsExactly(0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01); + } } - if (buf.readOnly()) { - assertTrue(buffer.isReadOnly()); - assertThrows(ReadOnlyBufferException.class, () -> buffer.put(0, (byte) 0xFF)); - } else { - assertFalse(buffer.isReadOnly()); - buffer.put(0, (byte) 0xFF); - assertEquals((byte) 0xFF, buffer.get(0)); - } + assertThrows(ReadOnlyBufferException.class, () -> buffer.put(0, (byte) 0xFF)); + return true; }); } @@ -2666,9 +2706,56 @@ public class BufTest { composite = allocator.compose(a, b, c); } var list = new LinkedList(List.of(1, 2, 3)); - composite.forEachReadable(component -> { - assertEquals(list.pollFirst().intValue(), component.byteBuffer().getInt()); + int count = composite.forEachReadable(0, (index, component) -> { + var buffer = component.buffer(); + int bufferValue = buffer.getInt(); + assertEquals(list.pollFirst().intValue(), bufferValue); + assertEquals(bufferValue, index + 1); + assertThrows(ReadOnlyBufferException.class, () -> buffer.put(0, (byte) 0xFF)); + return true; }); + assertEquals(3, count); + assertThat(list).isEmpty(); + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void forEachReadableMustReturnNegativeCountWhenProcessorReturnsFalse(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + buf.writeLong(0x0102030405060708L); + int count = buf.forEachReadable(0, (index, component) -> false); + assertEquals(-1, count); + } + } + + @Test + public void forEachReadableMustStopIterationWhenProcessorReturnsFalse() { + try (Allocator allocator = Allocator.heap()) { + Buf composite; + try (Buf a = allocator.allocate(4); + Buf b = allocator.allocate(4); + Buf c = allocator.allocate(4)) { + a.writeInt(1); + b.writeInt(2); + c.writeInt(3); + composite = allocator.compose(a, b, c); + } + int readPos = composite.readerOffset(); + int writePos = composite.writerOffset(); + var list = new LinkedList(List.of(1, 2, 3)); + int count = composite.forEachReadable(0, (index, component) -> { + var buffer = component.buffer(); + int bufferValue = buffer.getInt(); + assertEquals(list.pollFirst().intValue(), bufferValue); + assertEquals(bufferValue, index + 1); + return false; + }); + assertEquals(-1, count); + assertThat(list).containsExactly(2, 3); + assertEquals(readPos, composite.readerOffset()); + assertEquals(writePos, composite.writerOffset()); } } @@ -2679,7 +2766,210 @@ public class BufTest { var buf = allocator.allocate(8); buf.writeLong(0); buf.close(); - assertThrows(IllegalStateException.class, () -> buf.forEachReadable(component -> { })); + assertThrows(IllegalStateException.class, () -> buf.forEachReadable(0, (component, index) -> true)); + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void forEachReadableMustAllowCollectingBuffersInArray(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator()) { + Buf buf; + try (Buf a = allocator.allocate(4); + Buf b = allocator.allocate(4); + Buf c = allocator.allocate(4)) { + buf = allocator.compose(a, b, c); + } + int i = 1; + while (buf.writableBytes() > 0) { + buf.writeByte((byte) i++); + } + ByteBuffer[] buffers = new ByteBuffer[buf.countReadableComponents()]; + buf.forEachReadable(0, (index, component) -> { + buffers[index] = component.buffer(); + return true; + }); + i = 1; + assertThat(buffers.length).isGreaterThanOrEqualTo(1); + for (ByteBuffer buffer : buffers) { + while (buffer.hasRemaining()) { + assertEquals((byte) i++, buffer.get()); + } + } + } + } + + @ParameterizedTest + @MethodSource("nonCompositeAllocators") + public void forEachWritableMustVisitBuffer(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf bufBERW = allocator.allocate(8).order(BIG_ENDIAN); + Buf bufLERW = allocator.allocate(8).order(LITTLE_ENDIAN)) { + verifyForEachWritableSingleComponent(fixture, bufBERW); + verifyForEachWritableSingleComponent(fixture, bufLERW); + } + } + + private static void verifyForEachWritableSingleComponent(Fixture fixture, Buf buf) { + buf.forEachWritable(0, (index, component) -> { + var buffer = component.buffer(); + assertThat(buffer.position()).isZero(); + assertThat(buffer.limit()).isEqualTo(8); + assertThat(buffer.capacity()).isEqualTo(8); + buffer.putLong(0x0102030405060708L); + buffer.flip(); + assertEquals(0x0102030405060708L, buffer.getLong()); + buf.writerOffset(8); + assertEquals(0x0102030405060708L, buf.getLong(0)); + + if (fixture.isDirect()) { + assertThat(component.nativeAddress()).isNotZero(); + } else { + assertThat(component.nativeAddress()).isZero(); + } + + if (component.hasArray()) { + byte[] array = component.array(); + if (buffer.order() == BIG_ENDIAN) { + assertThat(array).containsExactly(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); + } else { + assertThat(array).containsExactly(0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01); + } + } + + buffer.put(0, (byte) 0xFF); + assertEquals((byte) 0xFF, buffer.get(0)); + assertEquals((byte) 0xFF, buf.getByte(0)); + return true; + }); + } + + @Test + public void forEachWritableMustVisitAllWritableConstituentBuffersInOrder() { + try (Allocator allocator = Allocator.heap()) { + Buf buf; + try (Buf a = allocator.allocate(8); + Buf b = allocator.allocate(8); + Buf c = allocator.allocate(8)) { + buf = allocator.compose(a, b, c); + } + buf.order(BIG_ENDIAN); + buf.forEachWritable(0, (index, component) -> { + component.buffer().putLong(0x0102030405060708L + 0x1010101010101010L * index); + return true; + }); + buf.writerOffset(3 * 8); + assertEquals(0x0102030405060708L, buf.readLong()); + assertEquals(0x1112131415161718L, buf.readLong()); + assertEquals(0x2122232425262728L, buf.readLong()); + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void forEachWritableMustReturnNegativeCountWhenProcessorReturnsFalse(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + int count = buf.forEachWritable(0, (index, component) -> false); + assertEquals(-1, count); + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void forEachWritableMustStopIterationWhenProcessorRetursFalse(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + AtomicInteger counter = new AtomicInteger(); + buf.forEachWritable(0, (index, component) -> { + counter.incrementAndGet(); + return false; + }); + assertEquals(1, counter.get()); + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void forEachWritableChangesMadeToByteBufferComponentMustBeReflectedInBuffer(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(9).order(BIG_ENDIAN)) { + buf.writeByte((byte) 0xFF); + AtomicInteger writtenCounter = new AtomicInteger(); + buf.forEachWritable(0, (index, component) -> { + var buffer = component.buffer(); + while (buffer.hasRemaining()) { + buffer.put((byte) writtenCounter.incrementAndGet()); + } + return true; + }); + buf.writerOffset(9); + assertEquals((byte) 0xFF, buf.readByte()); + assertEquals(0x0102030405060708L, buf.readLong()); + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void changesMadeToByteBufferComponentsShouldBeReflectedInBuffer(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + AtomicInteger counter = new AtomicInteger(); + buf.forEachWritable(0, (index, component) -> { + var buffer = component.buffer(); + while (buffer.hasRemaining()) { + buffer.put((byte) counter.incrementAndGet()); + } + return true; + }); + buf.writerOffset(buf.capacity()); + for (int i = 0; i < 8; i++) { + assertEquals((byte) i + 1, buf.getByte(i)); + } + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void forEachWritableOnClosedBufferMustThrow(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator()) { + Buf buf = allocator.allocate(8); + buf.close(); + assertThrows(IllegalStateException.class, () -> buf.forEachWritable(0, (index, component) -> true)); + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void forEachWritableOnReadOnlyBufferMustThrow(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8).readOnly(true)) { + assertThrows(IllegalStateException.class, () -> buf.forEachWritable(0, (index, component) -> true)); + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void forEachWritableMustAllowCollectingBuffersInArray(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + ByteBuffer[] buffers = new ByteBuffer[buf.countWritableComponents()]; + buf.forEachWritable(0, (index, component) -> { + buffers[index] = component.buffer(); + return true; + }); + assertThat(buffers.length).isGreaterThanOrEqualTo(1); + int i = 1; + for (ByteBuffer buffer : buffers) { + while (buffer.hasRemaining()) { + buffer.put((byte) i++); + } + } + buf.writerOffset(buf.capacity()); + i = 1; + while (buf.readableBytes() > 0) { + assertEquals((byte) i++, buf.readByte()); + } } } From 1c10770e82cdfb8588e8f209e617c74fde07c59d Mon Sep 17 00:00:00 2001 From: Chris Vest Date: Fri, 15 Jan 2021 21:32:24 +0100 Subject: [PATCH 3/6] Implement segregated readable/writable component interfaces and processing --- src/main/java/io/netty/buffer/api/Buf.java | 75 +++++++-- .../java/io/netty/buffer/api/Component.java | 71 -------- .../netty/buffer/api/ComponentProcessor.java | 152 +++++++++++++++++- .../io/netty/buffer/api/CompositeBuf.java | 4 +- .../io/netty/buffer/api/memseg/MemSegBuf.java | 120 +++++++------- .../java/io/netty/buffer/api/BufTest.java | 35 ++-- 6 files changed, 285 insertions(+), 172 deletions(-) delete mode 100644 src/main/java/io/netty/buffer/api/Component.java diff --git a/src/main/java/io/netty/buffer/api/Buf.java b/src/main/java/io/netty/buffer/api/Buf.java index 86d8754..d85014f 100644 --- a/src/main/java/io/netty/buffer/api/Buf.java +++ b/src/main/java/io/netty/buffer/api/Buf.java @@ -15,6 +15,11 @@ */ package io.netty.buffer.api; +import io.netty.buffer.api.ComponentProcessor.OfReadable; +import io.netty.buffer.api.ComponentProcessor.OfWritable; +import io.netty.buffer.api.ComponentProcessor.ReadableComponent; +import io.netty.buffer.api.ComponentProcessor.WritableComponent; + import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -493,7 +498,7 @@ public interface Buf extends Rc, BufAccessors { /** * Get the number of "components" in this buffer, that are readable. These are the components that would be - * processed by {@link #forEachReadable(int, ComponentProcessor)}. For composite buffers, this is the number of + * processed by {@link #forEachReadable(int, OfReadable)}. For composite buffers, this is the number of * transitive constituent buffers that are readable, while non-composite buffers only have at most one readable * component. *

@@ -506,7 +511,7 @@ public interface Buf extends Rc, BufAccessors { /** * Get the number of "components" in this buffer, that are writable. These are the components that would be - * processed by {@link #forEachWritable(int, ComponentProcessor)}. For composite buffers, this is the number of + * processed by {@link #forEachWritable(int, OfWritable)}. For composite buffers, this is the number of * transitive constituent buffers that are writable, while non-composite buffers only have at most one writable * component. *

@@ -520,20 +525,20 @@ public interface Buf extends Rc, BufAccessors { /** * Process all readable components of this buffer, and return the number of components processed. *

- * The given {@linkplain ComponentProcessor processor} is called for each component in this buffer, and passed a - * component index, for the given component in the iteration, and a {@link Component} object for accessing the data - * within the given component. + * The given {@linkplain OfReadable processor} is called for each readable component in this buffer, + * and passed a component index, for the given component in the iteration, and a {@link ReadableComponent} object + * for accessing the data within the given component. *

- * The component index is specific to the particular invokation of this method, and may change. The first call to - * the consumer will be passed the given initial index, and the next call will be passed the initial index plus one, - * and so on. + * The component index is specific to the particular invokation of this method. The first call to the consumer will + * be passed the given initial index, and the next call will be passed the initial index plus one, and so on. *

- * The {@link ComponentProcessor} may stop the iteration at any time by returning {@code false}. This may cause the - * number of components processed to be returned as a negative number (to signal early return), and the number of - * components processed may then be less than the {@linkplain #countReadableComponents() readable component count}. + * The {@linkplain OfReadable component processor} may stop the iteration at any time by returning {@code false}. + * This will cause the number of components processed to be returned as a negative number (to signal early return), + * and the number of components processed may then be less than the + * {@linkplain #countReadableComponents() readable component count}. *

- * Note that the {@link Component} instance passed to the consumer could be reused for multiple - * calls, so the data must be extracted from the component in the context of the iteration. + * Note that the {@link ReadableComponent} instance passed to the consumer could be reused for + * multiple calls, so the data must be extracted from the component in the context of the iteration. *

* The {@link ByteBuffer} instances obtained from the component, share life time with that internal component. * This means they can be accessed as long as the internal memory store remain unchanged. Methods that may cause @@ -549,14 +554,50 @@ public interface Buf extends Rc, BufAccessors { * this buffer instance. * * @param initialIndex The initial index of the iteration, and the index that will be passed to the first call to - * the {@linkplain ComponentProcessor#process(int, Component) processor}. + * the {@linkplain OfReadable#process(int, ReadableComponent) processor}. * @param processor The processor that will be used to process the buffer components. * @return The number of readable components processed, as a positive number of all readable components were * processed, or as a negative number if the iteration was stopped because - * {@link ComponentProcessor#process(int, Component)} returned {@code false}. + * {@link OfReadable#process(int, ReadableComponent)} returned {@code false}. * In any case, the number of components processed may be less than {@link #countComponents()}. */ - int forEachReadable(int initialIndex, ComponentProcessor processor); + int forEachReadable(int initialIndex, OfReadable processor); - int forEachWritable(int initialIndex, ComponentProcessor processor); + /** + * Process all writable components of this buffer, and return the number of components processed. + *

+ * The given {@linkplain OfWritable processor} is called for each writable component in this buffer, + * and passed a component index, for the given component in the iteration, and a {@link WritableComponent} object + * for accessing the data within the given component. + *

+ * The component index is specific to the particular invokation of this method. The first call to the consumer will + * be passed the given initial index, and the next call will be passed the initial index plus one, and so on. + *

+ * The {@link OfWritable component processor} may stop the iteration at any time by returning {@code false}. + * This will cause the number of components processed to be returned as a negative number (to signal early return), + * and the number of components processed may then be less than the + * {@linkplain #countReadableComponents() readable component count}. + *

+ * Note that the {@link WritableComponent} instance passed to the consumer could be reused for + * multiple calls, so the data must be extracted from the component in the context of the iteration. + *

+ * The {@link ByteBuffer} instances obtained from the component, share life time with that internal component. + * This means they can be accessed as long as the internal memory store remain unchanged. Methods that may cause + * such changes, are any method that requires the buffer to be {@linkplain #isOwned() owned}. + *

+ * The best way to ensure this doesn't cause any trouble, is to use the buffers directly as part of the iteration, + * or immediately after the iteration. + *

+ * Changes to position and limit of the byte buffers exposed via the processed components, are not reflected back to + * this buffer instance. + * + * @param initialIndex The initial index of the iteration, and the index that will be passed to the first call to + * the {@linkplain OfWritable#process(int, WritableComponent) processor}. + * @param processor The processor that will be used to process the buffer components. + * @return The number of writable components processed, as a positive number of all writable components were + * processed, or as a negative number if the iteration was stopped because + * {@link OfWritable#process(int, WritableComponent)} returned {@code false}. + * In any case, the number of components processed may be less than {@link #countComponents()}. + */ + int forEachWritable(int initialIndex, OfWritable processor); } diff --git a/src/main/java/io/netty/buffer/api/Component.java b/src/main/java/io/netty/buffer/api/Component.java deleted file mode 100644 index fa72615..0000000 --- a/src/main/java/io/netty/buffer/api/Component.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package io.netty.buffer.api; - -import java.nio.ByteBuffer; - -/** - * A view onto the buffer component being processed in a given iteration of - * {@link Buf#forEachReadable(int, ComponentProcessor)}, or {@link Buf#forEachWritable(int, ComponentProcessor)}. - *

- * Instances of this interface are allowed to be mutable behind the scenes, and the data is only guaranteed to be - * consistent within the given iteration. - */ -public interface Component { - - /** - * Check if this component is backed by a cached byte array than can be accessed cheaply. - *

- * Note that regardless of what this method returns, the array should not be used to modify the - * contents of this buffer component. - * - * @return {@code true} if {@link #array()} is a cheap operation, otherwise {@code false}. - */ - boolean hasArray(); - - /** - * Get a byte array of the contents of this component. - *

- * Note that the array is meant to be read-only. It may either be a direct reference to the - * concrete array instance that is backing this component, or it is a fresh copy. - * Writing to the array may produce undefined behaviour. - * - * @return A byte array of the contents of this component. - */ - byte[] array(); - - int arrayOffset(); - - /** - * Give the native memory address backing this buffer, or return 0 if this buffer has no native memory address. - *

- * Note that the address should not be used for writing to the buffer memory, and doing so may - * produce undefined behaviour. - * - * @return The native memory address, if any, otherwise 0. - */ - long nativeAddress(); - - /** - * Get a {@link ByteBuffer} instance for this memory component. - *

- * Note that the {@link ByteBuffer} is read-only, to prevent write accesses to the memory, - * when the buffer component is obtained through {@link Buf#forEachReadable(int, ComponentProcessor)}. - * - * @return A new {@link ByteBuffer} for this memory component. - */ - ByteBuffer buffer(); -} diff --git a/src/main/java/io/netty/buffer/api/ComponentProcessor.java b/src/main/java/io/netty/buffer/api/ComponentProcessor.java index e83b214..7078584 100644 --- a/src/main/java/io/netty/buffer/api/ComponentProcessor.java +++ b/src/main/java/io/netty/buffer/api/ComponentProcessor.java @@ -15,7 +15,155 @@ */ package io.netty.buffer.api; -@FunctionalInterface +import java.nio.ByteBuffer; + +/** + * This interface contain a collection of APIs used in the {@link Buf#forEachReadable(int, OfReadable)} and + * {@link Buf#forEachWritable(int, OfWritable)} methods. + */ public interface ComponentProcessor { - boolean process(int index, Component component); + /** + * A processor of {@linkplain ReadableComponent readable components}. + */ + @FunctionalInterface + interface OfReadable extends ComponentProcessor { + /** + * Process the given component at the given index in the {@link Buf#forEachReadable(int, OfReadable) iteration}. + *

+ * The component object itself is only valid during this call, but the {@link ByteBuffer byte buffers}, arrays, + * and native address pointers obtained from it, will be valid until any + * {@link Buf#isOwned() ownership} requiring operation is performed on the buffer. + * + * @param index The current index of the given buffer component, based on the initial index passed to the + * {@link Buf#forEachReadable(int, OfReadable)} method. + * @param component The current buffer component being processed. + * @return {@code true} if the iteration should continue and more components should be processed, otherwise + * {@code false} to stop the iteration early. + */ + boolean process(int index, ReadableComponent component); + } + + /** + * A processor of {@linkplain WritableComponent writable components}. + */ + @FunctionalInterface + interface OfWritable extends ComponentProcessor { + /** + * Process the given component at the given index in the + * {@link Buf#forEachWritable(int, OfWritable)} iteration}. + *

+ * The component object itself is only valid during this call, but the {@link ByteBuffer byte buffers}, arrays, + * and native address pointers obtained from it, will be valid until any + * {@link Buf#isOwned() ownership} requiring operation is performed on the buffer. + * + * @param index The current index of the given buffer component, based on the initial index passed to the + * {@link Buf#forEachWritable(int, OfWritable)} method. + * @param component The current buffer component being processed. + * @return {@code true} if the iteration should continue and more components should be processed, otherwise + * {@code false} to stop the iteration early. + */ + boolean process(int index, WritableComponent component); + } + + /** + * A view onto the buffer component being processed in a given iteration of + * {@link Buf#forEachReadable(int, OfReadable)}. + */ + interface ReadableComponent { + + /** + * Check if this component is backed by a cached byte array than can be accessed cheaply. + *

+ * Note that regardless of what this method returns, the array should not be used to modify the + * contents of this buffer component. + * + * @return {@code true} if {@link #readableArray()} is a cheap operation, otherwise {@code false}. + */ + boolean hasReadableArray(); + + /** + * Get a byte array of the contents of this component. + *

+ * Note that the array is meant to be read-only. It may either be a direct reference to the + * concrete array instance that is backing this component, or it is a fresh copy. + * Writing to the array may produce undefined behaviour. + * + * @return A byte array of the contents of this component. + * @throws UnsupportedOperationException if {@link #hasReadableArray()} returns {@code false}. + */ + byte[] readableArray(); + + /** + * An offset into the {@link #readableArray()} where this component starts. + * + * @return An offset into {@link #readableArray()}. + * @throws UnsupportedOperationException if {@link #hasReadableArray()} returns {@code false}. + */ + int readableArrayOffset(); + + /** + * Give the native memory address backing this buffer, or return 0 if this buffer has no native memory address. + *

+ * Note that the address should not be used for writing to the buffer memory, and doing so may + * produce undefined behaviour. + * + * @return The native memory address, if any, otherwise 0. + */ + long readableNativeAddress(); + + /** + * Get a {@link ByteBuffer} instance for this memory component. + *

+ * Note that the {@link ByteBuffer} is read-only, to prevent write accesses to the memory, + * when the buffer component is obtained through {@link Buf#forEachReadable(int, OfReadable)}. + * + * @return A new {@link ByteBuffer} for this memory component. + */ + ByteBuffer readableBuffer(); + } + + /** + * A view onto the buffer component being processed in a given iteration of + * {@link Buf#forEachWritable(int, OfWritable)}. + */ + interface WritableComponent { + + /** + * Check if this component is backed by a cached byte array than can be accessed cheaply. + * + * @return {@code true} if {@link #writableArray()} is a cheap operation, otherwise {@code false}. + */ + boolean hasWritableArray(); + + /** + * Get a byte array of the contents of this component. + * + * @return A byte array of the contents of this component. + * @throws UnsupportedOperationException if {@link #hasWritableArray()} returns {@code false}. + */ + byte[] writableArray(); + + /** + * An offset into the {@link #writableArray()} where this component starts. + * + * @return An offset into {@link #writableArray()}. + * @throws UnsupportedOperationException if {@link #hasWritableArray()} returns {@code false}. + */ + int writableArrayOffset(); + + /** + * Give the native memory address backing this buffer, or return 0 if this buffer has no native memory address. + * + * @return The native memory address, if any, otherwise 0. + */ + long writableNativeAddress(); + + /** + * Get a {@link ByteBuffer} instance for this memory component, which can be used for modifying the buffer + * contents. + * + * @return A new {@link ByteBuffer} for this memory component. + */ + ByteBuffer writableBuffer(); + } } diff --git a/src/main/java/io/netty/buffer/api/CompositeBuf.java b/src/main/java/io/netty/buffer/api/CompositeBuf.java index 6209cdc..ea689db 100644 --- a/src/main/java/io/netty/buffer/api/CompositeBuf.java +++ b/src/main/java/io/netty/buffer/api/CompositeBuf.java @@ -740,7 +740,7 @@ final class CompositeBuf extends RcSupport implements Buf { } @Override - public int forEachReadable(int initialIndex, ComponentProcessor processor) { + public int forEachReadable(int initialIndex, ComponentProcessor.OfReadable processor) { checkReadBounds(readerOffset(), Math.max(1, readableBytes())); int visited = 0; for (Buf buf : bufs) { @@ -758,7 +758,7 @@ final class CompositeBuf extends RcSupport implements Buf { } @Override - public int forEachWritable(int initialIndex, ComponentProcessor processor) { + public int forEachWritable(int initialIndex, ComponentProcessor.OfWritable processor) { checkWriteBounds(writerOffset(), Math.max(1, writableBytes())); int visited = 0; for (Buf buf : bufs) { diff --git a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java index 2c73d2b..8be0ded 100644 --- a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java +++ b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java @@ -19,8 +19,9 @@ import io.netty.buffer.api.Allocator; import io.netty.buffer.api.AllocatorControl; import io.netty.buffer.api.Buf; import io.netty.buffer.api.ByteCursor; -import io.netty.buffer.api.Component; import io.netty.buffer.api.ComponentProcessor; +import io.netty.buffer.api.ComponentProcessor.ReadableComponent; +import io.netty.buffer.api.ComponentProcessor.WritableComponent; import io.netty.buffer.api.Drop; import io.netty.buffer.api.Owned; import io.netty.buffer.api.RcSupport; @@ -44,7 +45,7 @@ import static jdk.incubator.foreign.MemoryAccess.setIntAtOffset; import static jdk.incubator.foreign.MemoryAccess.setLongAtOffset; import static jdk.incubator.foreign.MemoryAccess.setShortAtOffset; -class MemSegBuf extends RcSupport implements Buf, Component { +class MemSegBuf extends RcSupport implements Buf, ReadableComponent, WritableComponent { private static final MemorySegment CLOSED_SEGMENT; static final Drop SEGMENT_CLOSE; @@ -134,32 +135,29 @@ class MemSegBuf extends RcSupport implements Buf, Component { return this; } + // @Override - public boolean hasArray() { + public boolean hasReadableArray() { return false; } @Override - public byte[] array() { - return null; + public byte[] readableArray() { + throw new UnsupportedOperationException("This component has no backing array."); } @Override - public int arrayOffset() { - return 0; + public int readableArrayOffset() { + throw new UnsupportedOperationException("This component has no backing array."); } @Override - public long nativeAddress() { - try { - return seg.address().toRawLongValue(); - } catch (UnsupportedOperationException e) { - return 0; // This is a heap segment. - } + public long readableNativeAddress() { + return nativeAddress(); } @Override - public ByteBuffer buffer() { + public ByteBuffer readableBuffer() { var buffer = seg.asByteBuffer(); int base = baseOffset; if (buffer.isDirect()) { @@ -174,6 +172,49 @@ class MemSegBuf extends RcSupport implements Buf, Component { return buffer.slice(base + readerOffset(), readableBytes()).order(order); } + @Override + public boolean hasWritableArray() { + return false; + } + + @Override + public byte[] writableArray() { + throw new UnsupportedOperationException("This component has no backing array."); + } + + @Override + public int writableArrayOffset() { + throw new UnsupportedOperationException("This component has no backing array."); + } + + @Override + public long writableNativeAddress() { + return nativeAddress(); + } + + @Override + public ByteBuffer writableBuffer() { + var buffer = wseg.asByteBuffer(); + + if (buffer.isDirect()) { + buffer = buffer.position(writerOffset()).limit(writerOffset() + writableBytes()); + } else { + // TODO avoid slicing and just set position when JDK bug is fixed. + buffer = buffer.slice(baseOffset + writerOffset(), writableBytes()); + } + return buffer.order(order); + } + // + + @Override + public long nativeAddress() { + try { + return seg.address().toRawLongValue(); + } catch (UnsupportedOperationException e) { + return 0; // This is a heap segment. + } + } + @Override public Buf readOnly(boolean readOnly) { wseg = readOnly? CLOSED_SEGMENT : seg; @@ -510,60 +551,15 @@ class MemSegBuf extends RcSupport implements Buf, Component { } @Override - public int forEachReadable(int initialIndex, ComponentProcessor processor) { + public int forEachReadable(int initialIndex, ComponentProcessor.OfReadable processor) { checkRead(readerOffset(), Math.max(1, readableBytes())); return processor.process(initialIndex, this)? 1 : -1; } @Override - public int forEachWritable(int initialIndex, ComponentProcessor processor) { + public int forEachWritable(int initialIndex, ComponentProcessor.OfWritable processor) { checkWrite(writerOffset(), Math.max(1, writableBytes())); - - var buffer = wseg.asByteBuffer(); - - if (buffer.isDirect()) { - buffer = buffer.position(writerOffset()).limit(writerOffset() + writableBytes()); - } else { - // TODO avoid slicing and just set position when JDK bug is fixed. - buffer = buffer.slice(baseOffset + writerOffset(), writableBytes()); - } - buffer = buffer.order(order); - return processor.process(initialIndex, new WritableComponent(wseg, buffer))? 1 : -1; - } - - private static final class WritableComponent implements Component { - private final MemorySegment segment; - private final ByteBuffer buffer; - - private WritableComponent(MemorySegment segment, ByteBuffer buffer) { - this.segment = segment; - this.buffer = buffer; - } - - @Override - public boolean hasArray() { - return false; - } - - @Override - public byte[] array() { - return null; - } - - @Override - public int arrayOffset() { - return 0; - } - - @Override - public long nativeAddress() { - return buffer.isDirect()? segment.address().toRawLongValue() : 0; - } - - @Override - public ByteBuffer buffer() { - return buffer; - } + return processor.process(initialIndex, this)? 1 : -1; } // diff --git a/src/test/java/io/netty/buffer/api/BufTest.java b/src/test/java/io/netty/buffer/api/BufTest.java index d241ba4..e5c4bf5 100644 --- a/src/test/java/io/netty/buffer/api/BufTest.java +++ b/src/test/java/io/netty/buffer/api/BufTest.java @@ -17,7 +17,6 @@ package io.netty.buffer.api; import io.netty.buffer.api.Fixture.Properties; import io.netty.buffer.api.memseg.NativeMemorySegmentManager; -import org.assertj.core.api.AtomicIntegerArrayAssert; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; @@ -2667,20 +2666,20 @@ public class BufTest { private static void verifyForEachReadableSingleComponent(Fixture fixture, Buf buf) { buf.forEachReadable(0, (index, component) -> { - var buffer = component.buffer(); + var buffer = component.readableBuffer(); assertThat(buffer.position()).isZero(); assertThat(buffer.limit()).isEqualTo(8); assertThat(buffer.capacity()).isEqualTo(8); assertEquals(0x0102030405060708L, buffer.getLong()); if (fixture.isDirect()) { - assertThat(component.nativeAddress()).isNotZero(); + assertThat(component.readableNativeAddress()).isNotZero(); } else { - assertThat(component.nativeAddress()).isZero(); + assertThat(component.readableNativeAddress()).isZero(); } - if (component.hasArray()) { - byte[] array = component.array(); + if (component.hasReadableArray()) { + byte[] array = component.readableArray(); if (buffer.order() == BIG_ENDIAN) { assertThat(array).containsExactly(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); } else { @@ -2707,7 +2706,7 @@ public class BufTest { } var list = new LinkedList(List.of(1, 2, 3)); int count = composite.forEachReadable(0, (index, component) -> { - var buffer = component.buffer(); + var buffer = component.readableBuffer(); int bufferValue = buffer.getInt(); assertEquals(list.pollFirst().intValue(), bufferValue); assertEquals(bufferValue, index + 1); @@ -2746,7 +2745,7 @@ public class BufTest { int writePos = composite.writerOffset(); var list = new LinkedList(List.of(1, 2, 3)); int count = composite.forEachReadable(0, (index, component) -> { - var buffer = component.buffer(); + var buffer = component.readableBuffer(); int bufferValue = buffer.getInt(); assertEquals(list.pollFirst().intValue(), bufferValue); assertEquals(bufferValue, index + 1); @@ -2786,7 +2785,7 @@ public class BufTest { } ByteBuffer[] buffers = new ByteBuffer[buf.countReadableComponents()]; buf.forEachReadable(0, (index, component) -> { - buffers[index] = component.buffer(); + buffers[index] = component.readableBuffer(); return true; }); i = 1; @@ -2812,7 +2811,7 @@ public class BufTest { private static void verifyForEachWritableSingleComponent(Fixture fixture, Buf buf) { buf.forEachWritable(0, (index, component) -> { - var buffer = component.buffer(); + var buffer = component.writableBuffer(); assertThat(buffer.position()).isZero(); assertThat(buffer.limit()).isEqualTo(8); assertThat(buffer.capacity()).isEqualTo(8); @@ -2823,13 +2822,13 @@ public class BufTest { assertEquals(0x0102030405060708L, buf.getLong(0)); if (fixture.isDirect()) { - assertThat(component.nativeAddress()).isNotZero(); + assertThat(component.writableNativeAddress()).isNotZero(); } else { - assertThat(component.nativeAddress()).isZero(); + assertThat(component.writableNativeAddress()).isZero(); } - if (component.hasArray()) { - byte[] array = component.array(); + if (component.hasWritableArray()) { + byte[] array = component.writableArray(); if (buffer.order() == BIG_ENDIAN) { assertThat(array).containsExactly(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); } else { @@ -2855,7 +2854,7 @@ public class BufTest { } buf.order(BIG_ENDIAN); buf.forEachWritable(0, (index, component) -> { - component.buffer().putLong(0x0102030405060708L + 0x1010101010101010L * index); + component.writableBuffer().putLong(0x0102030405060708L + 0x1010101010101010L * index); return true; }); buf.writerOffset(3 * 8); @@ -2897,7 +2896,7 @@ public class BufTest { buf.writeByte((byte) 0xFF); AtomicInteger writtenCounter = new AtomicInteger(); buf.forEachWritable(0, (index, component) -> { - var buffer = component.buffer(); + var buffer = component.writableBuffer(); while (buffer.hasRemaining()) { buffer.put((byte) writtenCounter.incrementAndGet()); } @@ -2916,7 +2915,7 @@ public class BufTest { Buf buf = allocator.allocate(8)) { AtomicInteger counter = new AtomicInteger(); buf.forEachWritable(0, (index, component) -> { - var buffer = component.buffer(); + var buffer = component.writableBuffer(); while (buffer.hasRemaining()) { buffer.put((byte) counter.incrementAndGet()); } @@ -2955,7 +2954,7 @@ public class BufTest { Buf buf = allocator.allocate(8)) { ByteBuffer[] buffers = new ByteBuffer[buf.countWritableComponents()]; buf.forEachWritable(0, (index, component) -> { - buffers[index] = component.buffer(); + buffers[index] = component.writableBuffer(); return true; }); assertThat(buffers.length).isGreaterThanOrEqualTo(1); From 1c1149395b8c966437eb1e1883d14b5b15c0b264 Mon Sep 17 00:00:00 2001 From: Chris Vest Date: Mon, 18 Jan 2021 10:56:33 +0100 Subject: [PATCH 4/6] Add comments about how component count overflow is prevented Also add TODOs for flattening composite buffers. --- src/main/java/io/netty/buffer/api/CompositeBuf.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/io/netty/buffer/api/CompositeBuf.java b/src/main/java/io/netty/buffer/api/CompositeBuf.java index ea689db..91c1a7f 100644 --- a/src/main/java/io/netty/buffer/api/CompositeBuf.java +++ b/src/main/java/io/netty/buffer/api/CompositeBuf.java @@ -52,7 +52,14 @@ final class CompositeBuf extends RcSupport implements Buf { } private static Buf[] filterExternalBufs(Buf[] bufs) { + // We filter out all zero-capacity buffers because they wouldn't contribute to the composite buffer anyway, + // and also, by ensuring that all constituent buffers contribute to the size of the composite buffer, + // we make sure that the number of composite buffers will never become greater than the number of bytes in + // the composite buffer. + // This restriction guarantees that methods like countComponents, forEachReadable and forEachWritable, + // will never overflow their component counts. // Allocating a new array unconditionally also prevents external modification of the array. + // TODO if any buffer is itself a composite buffer, then we should unwrap its sub-buffers return Arrays.stream(bufs).filter(b -> b.capacity() > 0).toArray(Buf[]::new); } @@ -625,8 +632,11 @@ final class CompositeBuf extends RcSupport implements Buf { if (extensionCapacity == 0) { // Extending by a zero-sized buffer makes no difference. Especially since it's not allowed to change the // capacity of buffers that are constiuents of composite buffers. + // This also ensures that methods like countComponents, and forEachReadable, do not have to worry about + // overflow in their component counters. return; } + // TODO if extension is itself a composite buffer, then we should extend ourselves by all of the sub-buffers long newSize = capacity() + extensionCapacity; Allocator.checkSize(newSize); From e22b57ddcdbc3d39894b72ae62b02242406c1bae Mon Sep 17 00:00:00 2001 From: Chris Vest Date: Mon, 18 Jan 2021 11:57:35 +0100 Subject: [PATCH 5/6] Address PR review comments --- src/main/java/io/netty/buffer/api/Buf.java | 42 ++++++++++--------- .../netty/buffer/api/ComponentProcessor.java | 27 ++++++------ .../io/netty/buffer/api/CompositeBuf.java | 7 +++- .../io/netty/buffer/api/memseg/MemSegBuf.java | 11 +++-- 4 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/main/java/io/netty/buffer/api/Buf.java b/src/main/java/io/netty/buffer/api/Buf.java index d85014f..979a8d1 100644 --- a/src/main/java/io/netty/buffer/api/Buf.java +++ b/src/main/java/io/netty/buffer/api/Buf.java @@ -15,8 +15,8 @@ */ package io.netty.buffer.api; -import io.netty.buffer.api.ComponentProcessor.OfReadable; -import io.netty.buffer.api.ComponentProcessor.OfWritable; +import io.netty.buffer.api.ComponentProcessor.ReadableComponentProcessor; +import io.netty.buffer.api.ComponentProcessor.WritableComponentProcessor; import io.netty.buffer.api.ComponentProcessor.ReadableComponent; import io.netty.buffer.api.ComponentProcessor.WritableComponent; @@ -498,9 +498,9 @@ public interface Buf extends Rc, BufAccessors { /** * Get the number of "components" in this buffer, that are readable. These are the components that would be - * processed by {@link #forEachReadable(int, OfReadable)}. For composite buffers, this is the number of - * transitive constituent buffers that are readable, while non-composite buffers only have at most one readable - * component. + * processed by {@link #forEachReadable(int, ReadableComponentProcessor)}. For composite buffers, this is the + * number of transitive constituent buffers that are readable, while non-composite buffers only have at most one + * readable component. *

* The number of readable components may be less than the {@link #countComponents() component count}, if not all of * them have readable data. @@ -511,9 +511,9 @@ public interface Buf extends Rc, BufAccessors { /** * Get the number of "components" in this buffer, that are writable. These are the components that would be - * processed by {@link #forEachWritable(int, OfWritable)}. For composite buffers, this is the number of - * transitive constituent buffers that are writable, while non-composite buffers only have at most one writable - * component. + * processed by {@link #forEachWritable(int, WritableComponentProcessor)}. For composite buffers, this is the + * number of transitive constituent buffers that are writable, while non-composite buffers only have at most one + * writable component. *

* The number of writable components may be less than the {@link #countComponents() component count}, if not all of * them have space for writing. @@ -525,14 +525,15 @@ public interface Buf extends Rc, BufAccessors { /** * Process all readable components of this buffer, and return the number of components processed. *

- * The given {@linkplain OfReadable processor} is called for each readable component in this buffer, + * The given {@linkplain ReadableComponentProcessor processor} is called for each readable component in this buffer, * and passed a component index, for the given component in the iteration, and a {@link ReadableComponent} object * for accessing the data within the given component. *

* The component index is specific to the particular invokation of this method. The first call to the consumer will * be passed the given initial index, and the next call will be passed the initial index plus one, and so on. *

- * The {@linkplain OfReadable component processor} may stop the iteration at any time by returning {@code false}. + * The {@linkplain ReadableComponentProcessor component processor} may stop the iteration at any time by returning + * {@code false}. * This will cause the number of components processed to be returned as a negative number (to signal early return), * and the number of components processed may then be less than the * {@linkplain #countReadableComponents() readable component count}. @@ -545,7 +546,7 @@ public interface Buf extends Rc, BufAccessors { * such changes, are any method that requires the buffer to be {@linkplain #isOwned() owned}. *

* The best way to ensure this doesn't cause any trouble, is to use the buffers directly as part of the iteration, - * or immediately after the iteration. + * or immediately after the iteration while we are still in the scope of the method that triggered the iteration. *

* Note that the arrays, memory addresses, and byte buffers exposed as components by this method, * should not be used for changing the buffer contents. Doing so may cause undefined behaviour. @@ -554,26 +555,27 @@ public interface Buf extends Rc, BufAccessors { * this buffer instance. * * @param initialIndex The initial index of the iteration, and the index that will be passed to the first call to - * the {@linkplain OfReadable#process(int, ReadableComponent) processor}. + * the {@linkplain ReadableComponentProcessor#process(int, ReadableComponent) processor}. * @param processor The processor that will be used to process the buffer components. * @return The number of readable components processed, as a positive number of all readable components were * processed, or as a negative number if the iteration was stopped because - * {@link OfReadable#process(int, ReadableComponent)} returned {@code false}. + * {@link ReadableComponentProcessor#process(int, ReadableComponent)} returned {@code false}. * In any case, the number of components processed may be less than {@link #countComponents()}. */ - int forEachReadable(int initialIndex, OfReadable processor); + int forEachReadable(int initialIndex, ReadableComponentProcessor processor); /** * Process all writable components of this buffer, and return the number of components processed. *

- * The given {@linkplain OfWritable processor} is called for each writable component in this buffer, + * The given {@linkplain WritableComponentProcessor processor} is called for each writable component in this buffer, * and passed a component index, for the given component in the iteration, and a {@link WritableComponent} object * for accessing the data within the given component. *

* The component index is specific to the particular invokation of this method. The first call to the consumer will * be passed the given initial index, and the next call will be passed the initial index plus one, and so on. *

- * The {@link OfWritable component processor} may stop the iteration at any time by returning {@code false}. + * The {@link WritableComponentProcessor component processor} may stop the iteration at any time by returning + * {@code false}. * This will cause the number of components processed to be returned as a negative number (to signal early return), * and the number of components processed may then be less than the * {@linkplain #countReadableComponents() readable component count}. @@ -586,18 +588,18 @@ public interface Buf extends Rc, BufAccessors { * such changes, are any method that requires the buffer to be {@linkplain #isOwned() owned}. *

* The best way to ensure this doesn't cause any trouble, is to use the buffers directly as part of the iteration, - * or immediately after the iteration. + * or immediately after the iteration while we are still in the scope of the method that triggered the iteration. *

* Changes to position and limit of the byte buffers exposed via the processed components, are not reflected back to * this buffer instance. * * @param initialIndex The initial index of the iteration, and the index that will be passed to the first call to - * the {@linkplain OfWritable#process(int, WritableComponent) processor}. + * the {@linkplain WritableComponentProcessor#process(int, WritableComponent) processor}. * @param processor The processor that will be used to process the buffer components. * @return The number of writable components processed, as a positive number of all writable components were * processed, or as a negative number if the iteration was stopped because - * {@link OfWritable#process(int, WritableComponent)} returned {@code false}. + * {@link WritableComponentProcessor#process(int, WritableComponent)} returned {@code false}. * In any case, the number of components processed may be less than {@link #countComponents()}. */ - int forEachWritable(int initialIndex, OfWritable processor); + int forEachWritable(int initialIndex, WritableComponentProcessor processor); } diff --git a/src/main/java/io/netty/buffer/api/ComponentProcessor.java b/src/main/java/io/netty/buffer/api/ComponentProcessor.java index 7078584..7e41314 100644 --- a/src/main/java/io/netty/buffer/api/ComponentProcessor.java +++ b/src/main/java/io/netty/buffer/api/ComponentProcessor.java @@ -18,24 +18,25 @@ package io.netty.buffer.api; import java.nio.ByteBuffer; /** - * This interface contain a collection of APIs used in the {@link Buf#forEachReadable(int, OfReadable)} and - * {@link Buf#forEachWritable(int, OfWritable)} methods. + * This interface contain a collection of APIs used in the {@link Buf#forEachReadable(int, ReadableComponentProcessor)} + * and {@link Buf#forEachWritable(int, WritableComponentProcessor)} methods. */ public interface ComponentProcessor { /** * A processor of {@linkplain ReadableComponent readable components}. */ @FunctionalInterface - interface OfReadable extends ComponentProcessor { + interface ReadableComponentProcessor extends ComponentProcessor { /** - * Process the given component at the given index in the {@link Buf#forEachReadable(int, OfReadable) iteration}. + * Process the given component at the given index in the + * {@link Buf#forEachReadable(int, ReadableComponentProcessor) iteration}. *

* The component object itself is only valid during this call, but the {@link ByteBuffer byte buffers}, arrays, * and native address pointers obtained from it, will be valid until any * {@link Buf#isOwned() ownership} requiring operation is performed on the buffer. * * @param index The current index of the given buffer component, based on the initial index passed to the - * {@link Buf#forEachReadable(int, OfReadable)} method. + * {@link Buf#forEachReadable(int, ReadableComponentProcessor)} method. * @param component The current buffer component being processed. * @return {@code true} if the iteration should continue and more components should be processed, otherwise * {@code false} to stop the iteration early. @@ -47,17 +48,17 @@ public interface ComponentProcessor { * A processor of {@linkplain WritableComponent writable components}. */ @FunctionalInterface - interface OfWritable extends ComponentProcessor { + interface WritableComponentProcessor extends ComponentProcessor { /** * Process the given component at the given index in the - * {@link Buf#forEachWritable(int, OfWritable)} iteration}. + * {@link Buf#forEachWritable(int, WritableComponentProcessor)} iteration}. *

* The component object itself is only valid during this call, but the {@link ByteBuffer byte buffers}, arrays, * and native address pointers obtained from it, will be valid until any * {@link Buf#isOwned() ownership} requiring operation is performed on the buffer. * * @param index The current index of the given buffer component, based on the initial index passed to the - * {@link Buf#forEachWritable(int, OfWritable)} method. + * {@link Buf#forEachWritable(int, WritableComponentProcessor)} method. * @param component The current buffer component being processed. * @return {@code true} if the iteration should continue and more components should be processed, otherwise * {@code false} to stop the iteration early. @@ -67,7 +68,7 @@ public interface ComponentProcessor { /** * A view onto the buffer component being processed in a given iteration of - * {@link Buf#forEachReadable(int, OfReadable)}. + * {@link Buf#forEachReadable(int, ReadableComponentProcessor)}. */ interface ReadableComponent { @@ -115,16 +116,16 @@ public interface ComponentProcessor { * Get a {@link ByteBuffer} instance for this memory component. *

* Note that the {@link ByteBuffer} is read-only, to prevent write accesses to the memory, - * when the buffer component is obtained through {@link Buf#forEachReadable(int, OfReadable)}. + * when the buffer component is obtained through {@link Buf#forEachReadable(int, ReadableComponentProcessor)}. * - * @return A new {@link ByteBuffer} for this memory component. + * @return A new {@link ByteBuffer}, with its own position and limit, for this memory component. */ ByteBuffer readableBuffer(); } /** * A view onto the buffer component being processed in a given iteration of - * {@link Buf#forEachWritable(int, OfWritable)}. + * {@link Buf#forEachWritable(int, WritableComponentProcessor)}. */ interface WritableComponent { @@ -162,7 +163,7 @@ public interface ComponentProcessor { * Get a {@link ByteBuffer} instance for this memory component, which can be used for modifying the buffer * contents. * - * @return A new {@link ByteBuffer} for this memory component. + * @return A new {@link ByteBuffer}, with its own position and limit, for this memory component. */ ByteBuffer writableBuffer(); } diff --git a/src/main/java/io/netty/buffer/api/CompositeBuf.java b/src/main/java/io/netty/buffer/api/CompositeBuf.java index 91c1a7f..b4011b8 100644 --- a/src/main/java/io/netty/buffer/api/CompositeBuf.java +++ b/src/main/java/io/netty/buffer/api/CompositeBuf.java @@ -15,6 +15,9 @@ */ package io.netty.buffer.api; +import io.netty.buffer.api.ComponentProcessor.ReadableComponentProcessor; +import io.netty.buffer.api.ComponentProcessor.WritableComponentProcessor; + import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; @@ -750,7 +753,7 @@ final class CompositeBuf extends RcSupport implements Buf { } @Override - public int forEachReadable(int initialIndex, ComponentProcessor.OfReadable processor) { + public int forEachReadable(int initialIndex, ReadableComponentProcessor processor) { checkReadBounds(readerOffset(), Math.max(1, readableBytes())); int visited = 0; for (Buf buf : bufs) { @@ -768,7 +771,7 @@ final class CompositeBuf extends RcSupport implements Buf { } @Override - public int forEachWritable(int initialIndex, ComponentProcessor.OfWritable processor) { + public int forEachWritable(int initialIndex, WritableComponentProcessor processor) { checkWriteBounds(writerOffset(), Math.max(1, writableBytes())); int visited = 0; for (Buf buf : bufs) { diff --git a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java index 8be0ded..72e5946 100644 --- a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java +++ b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java @@ -19,9 +19,10 @@ import io.netty.buffer.api.Allocator; import io.netty.buffer.api.AllocatorControl; import io.netty.buffer.api.Buf; import io.netty.buffer.api.ByteCursor; -import io.netty.buffer.api.ComponentProcessor; import io.netty.buffer.api.ComponentProcessor.ReadableComponent; +import io.netty.buffer.api.ComponentProcessor.ReadableComponentProcessor; import io.netty.buffer.api.ComponentProcessor.WritableComponent; +import io.netty.buffer.api.ComponentProcessor.WritableComponentProcessor; import io.netty.buffer.api.Drop; import io.netty.buffer.api.Owned; import io.netty.buffer.api.RcSupport; @@ -61,7 +62,9 @@ class MemSegBuf extends RcSupport implements Buf, ReadableCompon private final AllocatorControl alloc; private final boolean isSendable; - private final int baseOffset; // TODO remove this when JDK bug is fixed (slices of heap buffers) + // TODO remove baseOffset when JDK bug is fixed (slices of heap buffers) + // See https://mail.openjdk.java.net/pipermail/panama-dev/2021-January/011810.html + private final int baseOffset; private MemorySegment seg; private MemorySegment wseg; private ByteOrder order; @@ -551,13 +554,13 @@ class MemSegBuf extends RcSupport implements Buf, ReadableCompon } @Override - public int forEachReadable(int initialIndex, ComponentProcessor.OfReadable processor) { + public int forEachReadable(int initialIndex, ReadableComponentProcessor processor) { checkRead(readerOffset(), Math.max(1, readableBytes())); return processor.process(initialIndex, this)? 1 : -1; } @Override - public int forEachWritable(int initialIndex, ComponentProcessor.OfWritable processor) { + public int forEachWritable(int initialIndex, WritableComponentProcessor processor) { checkWrite(writerOffset(), Math.max(1, writableBytes())); return processor.process(initialIndex, this)? 1 : -1; } From 1ff8b4bf5a1efd54795bd7cfd112d3e25eded1a2 Mon Sep 17 00:00:00 2001 From: Chris Vest Date: Mon, 18 Jan 2021 12:11:03 +0100 Subject: [PATCH 6/6] Remove work-around for slice-of-heap-segment-based-buffer JDK bug that got fixed --- .../io/netty/buffer/api/memseg/MemSegBuf.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java index 72e5946..8ba7408 100644 --- a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java +++ b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java @@ -62,9 +62,6 @@ class MemSegBuf extends RcSupport implements Buf, ReadableCompon private final AllocatorControl alloc; private final boolean isSendable; - // TODO remove baseOffset when JDK bug is fixed (slices of heap buffers) - // See https://mail.openjdk.java.net/pipermail/panama-dev/2021-January/011810.html - private final int baseOffset; private MemorySegment seg; private MemorySegment wseg; private ByteOrder order; @@ -72,17 +69,15 @@ class MemSegBuf extends RcSupport implements Buf, ReadableCompon private int woff; MemSegBuf(MemorySegment segmet, Drop drop, AllocatorControl alloc) { - this(segmet, drop, alloc, true, 0); + this(segmet, drop, alloc, true); } - private MemSegBuf(MemorySegment segment, Drop drop, AllocatorControl alloc, boolean isSendable, - int baseOffset) { + private MemSegBuf(MemorySegment segment, Drop drop, AllocatorControl alloc, boolean isSendable) { super(drop); this.alloc = alloc; seg = segment; wseg = segment; this.isSendable = isSendable; - this.baseOffset = baseOffset; order = ByteOrder.nativeOrder(); } @@ -162,17 +157,16 @@ class MemSegBuf extends RcSupport implements Buf, ReadableCompon @Override public ByteBuffer readableBuffer() { var buffer = seg.asByteBuffer(); - int base = baseOffset; if (buffer.isDirect()) { // TODO Remove this when the slicing of shared, native segments JDK bug is fixed. + // See https://mail.openjdk.java.net/pipermail/panama-dev/2021-January/011810.html ByteBuffer tmp = ByteBuffer.allocateDirect(buffer.capacity()); tmp.put(buffer); buffer = tmp.position(0); - base = 0; // TODO native memory segments do not have the buffer-of-slice bug. } buffer = buffer.asReadOnlyBuffer(); // TODO avoid slicing and just set position+limit when JDK bug is fixed. - return buffer.slice(base + readerOffset(), readableBytes()).order(order); + return buffer.slice(readerOffset(), readableBytes()).order(order); } @Override @@ -203,7 +197,7 @@ class MemSegBuf extends RcSupport implements Buf, ReadableCompon buffer = buffer.position(writerOffset()).limit(writerOffset() + writableBytes()); } else { // TODO avoid slicing and just set position when JDK bug is fixed. - buffer = buffer.slice(baseOffset + writerOffset(), writableBytes()); + buffer = buffer.slice(writerOffset(), writableBytes()); } return buffer.order(order); } @@ -241,7 +235,7 @@ class MemSegBuf extends RcSupport implements Buf, ReadableCompon b.makeInaccessible(); }; var sendable = false; // Sending implies ownership change, which we can't do for slices. - return new MemSegBuf(slice, drop, alloc, sendable, baseOffset + offset) + return new MemSegBuf(slice, drop, alloc, sendable) .writerOffset(length) .order(order()) .readOnly(readOnly());