Skip to content
This repository was archived by the owner on Dec 12, 2022. It is now read-only.

Commit 617d9cc

Browse files
committed
Add support for read-only buffers
Motivation: There are cases where you want a buffer to be "constant." Buffers are inherently mutable, but it's possible to block off write access to the buffer contents. This doesn't make it completely safe to share the buffer across multiple threads, but it does catch most races that could occur. Modification: Add a method to Buf for toggling read-only mode. When a buffer is read-only, the write accessors throw exceptions when called. In the MemSegBuf, this is implemented by having separate read and write references to the underlying memory segment. In a read-only buffer, the write reference is redirected to point to a closed memory segment, thus preventing all writes to the memory backing the buffer. Result: It is now possible to make buffers read-only. Note, however, that it is also possible to toggle a read-only buffer back to writable. We need that in order for buffer pools to be able to fully reset the state of a buffer, regardless of the buffer implementation.
1 parent f862220 commit 617d9cc

File tree

6 files changed

+523
-89
lines changed

6 files changed

+523
-89
lines changed

src/main/java/io/netty/buffer/api/Allocator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ default Buf allocate(int size, ByteOrder order) {
7070
* <pre>{@code
7171
* try (Buf a = allocator.allocate(size);
7272
* Buf b = allocator.allocate(size)) {
73-
* return Buf.compose(a, b); // Reference counts for 'a' and 'b' incremented here.
73+
* return allocator.compose(a, b); // Reference counts for 'a' and 'b' incremented here.
7474
* } // Reference count for 'a' and 'b' decremented here; composite buffer now holds the last references.
7575
* }</pre>
7676
* <p>

src/main/java/io/netty/buffer/api/Buf.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ public interface Buf extends Rc<Buf>, BufAccessors {
154154
* @return This Buf.
155155
* @throws IndexOutOfBoundsException if the specified {@code offset} is less than the current
156156
* {@link #readerOffset()} or greater than {@link #capacity()}.
157+
* @throws IllegalStateException if this buffer is {@linkplain #readOnly() read-only}.
157158
*/
158159
Buf writerOffset(int offset);
159160

@@ -178,6 +179,7 @@ default int writableBytes() {
178179
*
179180
* @param value The byte value to write at every position in the buffer.
180181
* @return This Buf.
182+
* @throws IllegalStateException if this buffer is {@linkplain #readOnly() read-only}.
181183
*/
182184
Buf fill(byte value);
183185

@@ -187,6 +189,20 @@ default int writableBytes() {
187189
*/
188190
long getNativeAddress();
189191

192+
/**
193+
* Set the read-only state of this buffer.
194+
*
195+
* @return this buffer.
196+
*/
197+
Buf readOnly(boolean readOnly);
198+
199+
/**
200+
* Query if this buffer is read-only or not.
201+
*
202+
* @return {@code true} if this buffer is read-only, {@code false} otherwise.
203+
*/
204+
boolean readOnly();
205+
190206
/**
191207
* Returns a slice of this buffer's readable bytes.
192208
* Modifying the content of the returned buffer or this buffer affects each other's content,
@@ -371,8 +387,8 @@ default ByteCursor openReverseCursor() {
371387
* {@code false}.
372388
*
373389
* @param size The requested number of bytes of space that should be available for writing.
374-
* @throws IllegalStateException if this buffer is not in an owned state.
375-
* That is, if {@link #isOwned()} is {@code false}.
390+
* @throws IllegalStateException if this buffer is not in an {@linkplain #isOwned() owned} state,
391+
* or is {@linkplain #readOnly() read-only}.
376392
*/
377393
default void ensureWritable(int size) {
378394
ensureWritable(size, true);
@@ -410,8 +426,8 @@ default void ensureWritable(int size) {
410426
* @param allowCompaction {@code true} if the method is allowed to modify the
411427
* {@linkplain #readerOffset() reader offset} and
412428
* {@linkplain #writerOffset() writer offset}, otherwise {@code false}.
413-
* @throws IllegalStateException if this buffer is not in an owned state.
414-
* That is, if {@link #isOwned()} is {@code false}.
429+
* @throws IllegalStateException if this buffer is not in an {@linkplain #isOwned() owned} state,
430+
* * or is {@linkplain #readOnly() read-only}.
415431
*/
416432
void ensureWritable(int size, boolean allowCompaction);
417433

@@ -462,7 +478,8 @@ default void ensureWritable(int size) {
462478
/**
463479
* Discards the read bytes, and moves the buffer contents to the beginning of the buffer.
464480
*
465-
* The buffer must be {@linkplain #isOwned() owned}, or an exception will be thrown.
481+
* @throws IllegalStateException if this buffer is not in an {@linkplain #isOwned() owned} state,
482+
* or is {@linkplain #readOnly() read-only}.
466483
*/
467484
void compact();
468485
}

src/main/java/io/netty/buffer/api/CompositeBuf.java

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@
2020
import java.util.Arrays;
2121
import java.util.Objects;
2222

23-
import static jdk.incubator.foreign.MemoryAccess.setByteAtOffset;
24-
import static jdk.incubator.foreign.MemoryAccess.setLongAtOffset;
25-
2623
final class CompositeBuf extends RcSupport<Buf, CompositeBuf> implements Buf {
2724
/**
2825
* The max array size is JVM implementation dependant, but most seem to settle on {@code Integer.MAX_VALUE - 8}.
@@ -48,6 +45,7 @@ final class CompositeBuf extends RcSupport<Buf, CompositeBuf> implements Buf {
4845
private int subOffset; // The next offset *within* a consituent buffer to read from or write to.
4946
private ByteOrder order;
5047
private boolean closed;
48+
private boolean readOnly;
5149

5250
CompositeBuf(Allocator allocator, Buf[] bufs) {
5351
this(allocator, true, bufs.clone(), COMPOSITE_DROP); // Clone prevents external modification of array.
@@ -68,6 +66,14 @@ private CompositeBuf(Allocator allocator, boolean isSendable, Buf[] bufs, Drop<C
6866
}
6967
}
7068
order = bufs[0].order();
69+
70+
boolean targetReadOnly = bufs[0].readOnly();
71+
for (Buf buf : bufs) {
72+
if (buf.readOnly() != targetReadOnly) {
73+
throw new IllegalArgumentException("Constituent buffers have inconsistent read-only state.");
74+
}
75+
}
76+
readOnly = targetReadOnly;
7177
} else {
7278
order = ByteOrder.nativeOrder();
7379
}
@@ -201,6 +207,20 @@ public long getNativeAddress() {
201207
return 0;
202208
}
203209

210+
@Override
211+
public Buf readOnly(boolean readOnly) {
212+
for (Buf buf : bufs) {
213+
buf.readOnly(readOnly);
214+
}
215+
this.readOnly = readOnly;
216+
return this;
217+
}
218+
219+
@Override
220+
public boolean readOnly() {
221+
return readOnly;
222+
}
223+
204224
@Override
205225
public Buf slice(int offset, int length) {
206226
checkWriteBounds(offset, length);
@@ -236,7 +256,7 @@ public Buf slice(int offset, int length) {
236256
slices = new Buf[] { choice.slice(subOffset, 0) };
237257
}
238258

239-
return new CompositeBuf(allocator, false, slices, drop).writerOffset(length);
259+
return new CompositeBuf(allocator, false, slices, drop);
240260
} catch (Throwable throwable) {
241261
// We called acquire prior to the try-clause. We need to undo that if we're not creating a composite buffer:
242262
close();
@@ -267,10 +287,10 @@ private void copyInto(int srcPos, CopyInto dest, int destPos, int length) {
267287
throw new IndexOutOfBoundsException("Length cannot be negative: " + length + '.');
268288
}
269289
if (srcPos < 0) {
270-
throw indexOutOfBounds(srcPos);
290+
throw indexOutOfBounds(srcPos, false);
271291
}
272292
if (srcPos + length > capacity) {
273-
throw indexOutOfBounds(srcPos + length);
293+
throw indexOutOfBounds(srcPos + length, false);
274294
}
275295
while (length > 0) {
276296
var buf = (Buf) chooseBuffer(srcPos, 0);
@@ -293,10 +313,10 @@ public void copyInto(int srcPos, Buf dest, int destPos, int length) {
293313
throw new IndexOutOfBoundsException("Length cannot be negative: " + length + '.');
294314
}
295315
if (srcPos < 0) {
296-
throw indexOutOfBounds(srcPos);
316+
throw indexOutOfBounds(srcPos, false);
297317
}
298318
if (srcPos + length > capacity) {
299-
throw indexOutOfBounds(srcPos + length);
319+
throw indexOutOfBounds(srcPos + length, false);
300320
}
301321

302322
// Iterate in reverse to account for src and dest buffer overlap.
@@ -530,6 +550,9 @@ public void ensureWritable(int size, boolean allowCompaction) {
530550
if (size < 0) {
531551
throw new IllegalArgumentException("Cannot ensure writable for a negative size: " + size + '.');
532552
}
553+
if (readOnly) {
554+
throw bufferIsReadOnly();
555+
}
533556
if (writableBytes() >= size) {
534557
// We already have enough space.
535558
return;
@@ -586,15 +609,23 @@ void extendWith(Buf extension) {
586609
"This buffer uses " + order() + " byte order, and cannot be extended with " +
587610
"a buffer that uses " + extension.order() + " byte order.");
588611
}
589-
if (bufs.length == 0) {
590-
order = extension.order();
612+
if (bufs.length > 0 && extension.readOnly() != readOnly()) {
613+
throw new IllegalArgumentException(
614+
"This buffer is " + (readOnly? "read-only" : "writable") + ", " +
615+
"and cannot be extended with a buffer that is " +
616+
(extension.readOnly()? "read-only." : "writable."));
591617
}
618+
592619
long newSize = capacity() + (long) extension.capacity();
593620
Allocator.checkSize(newSize);
594621

595622
Buf[] restoreTemp = bufs; // We need this to restore our buffer array, in case offset computations fail.
596623
try {
597624
unsafeExtendWith(extension.acquire());
625+
if (restoreTemp.length == 0) {
626+
order = extension.order();
627+
readOnly = extension.readOnly();
628+
}
598629
} catch (Exception e) {
599630
bufs = restoreTemp;
600631
throw e;
@@ -642,6 +673,9 @@ public void compact() {
642673
if (!isOwned()) {
643674
throw new IllegalStateException("Buffer must be owned in order to compact.");
644675
}
676+
if (readOnly()) {
677+
throw new IllegalStateException("Buffer must be writable in order to compact, but was read-only.");
678+
}
645679
int distance = roff;
646680
if (distance == 0) {
647681
return;
@@ -962,6 +996,7 @@ public CompositeBuf transferOwnership(Drop<CompositeBuf> drop) {
962996
received[i] = sends[i].receive();
963997
}
964998
var composite = new CompositeBuf(allocator, true, received, drop);
999+
composite.readOnly = readOnly;
9651000
drop.attach(composite);
9661001
return composite;
9671002
}
@@ -1034,7 +1069,7 @@ private BufAccessors prepRead(int index, int size) {
10341069

10351070
private void checkReadBounds(int index, int size) {
10361071
if (index < 0 || woff < index + size) {
1037-
throw indexOutOfBounds(index);
1072+
throw indexOutOfBounds(index, false);
10381073
}
10391074
}
10401075

@@ -1051,19 +1086,30 @@ private BufAccessors prepWrite(int index, int size) {
10511086

10521087
private void checkWriteBounds(int index, int size) {
10531088
if (index < 0 || capacity < index + size) {
1054-
throw indexOutOfBounds(index);
1089+
throw indexOutOfBounds(index, true);
10551090
}
10561091
}
10571092

1058-
private RuntimeException indexOutOfBounds(int index) {
1093+
private RuntimeException indexOutOfBounds(int index, boolean write) {
10591094
if (closed) {
1060-
return new IllegalStateException("This buffer is closed.");
1095+
return bufferIsClosed();
1096+
}
1097+
if (write && readOnly) {
1098+
return bufferIsReadOnly();
10611099
}
10621100
return new IndexOutOfBoundsException(
10631101
"Index " + index + " is out of bounds: [read 0 to " + woff + ", write 0 to " +
10641102
(capacity - 1) + "].");
10651103
}
10661104

1105+
private static IllegalStateException bufferIsClosed() {
1106+
return new IllegalStateException("This buffer is closed.");
1107+
}
1108+
1109+
private static IllegalStateException bufferIsReadOnly() {
1110+
return new IllegalStateException("This buffer is read-only.");
1111+
}
1112+
10671113
private BufAccessors chooseBuffer(int index, int size) {
10681114
int i = searchOffsets(index);
10691115
if (i == bufs.length) {

src/main/java/io/netty/buffer/api/SizeClassedMemoryPool.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ public Buf allocate(int size) {
4343
var sizeClassPool = getSizeClassPool(size);
4444
Send<Buf> send = sizeClassPool.poll();
4545
if (send != null) {
46-
return send.receive().reset().fill((byte) 0).order(ByteOrder.nativeOrder());
46+
return send.receive()
47+
.reset()
48+
.readOnly(false)
49+
.fill((byte) 0)
50+
.order(ByteOrder.nativeOrder());
4751
}
4852
return createBuf(size, getDrop());
4953
}

0 commit comments

Comments
 (0)