diff --git a/pom.xml b/pom.xml index e5fb8c5..ae81aca 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,8 @@ -server -dsa -da -ea:io.netty... -XX:+HeapDumpOnOutOfMemoryError + -Dio.netty.tryReflectionSetAccessible=true + --add-opens java.base/java.nio=io.netty.common diff --git a/src/main/java/io/netty/buffer/api/MemoryManager.java b/src/main/java/io/netty/buffer/api/MemoryManager.java index 2486d5b..ee0333e 100644 --- a/src/main/java/io/netty/buffer/api/MemoryManager.java +++ b/src/main/java/io/netty/buffer/api/MemoryManager.java @@ -26,5 +26,6 @@ public interface MemoryManager { Drop drop(); Object unwrapRecoverableMemory(Buffer buf); int capacityOfRecoverableMemory(Object memory); + // todo should recoverMemory re-attach a cleaner? Buffer recoverMemory(AllocatorControl allocatorControl, Object recoverableMemory, Drop drop); } diff --git a/src/main/java/io/netty/buffer/api/bytebuffer/NioBuffer.java b/src/main/java/io/netty/buffer/api/bytebuffer/NioBuffer.java index 84d6b61..7fbb7c6 100644 --- a/src/main/java/io/netty/buffer/api/bytebuffer/NioBuffer.java +++ b/src/main/java/io/netty/buffer/api/bytebuffer/NioBuffer.java @@ -34,6 +34,9 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ReadOnlyBufferException; +import static io.netty.buffer.api.internal.Statics.bufferIsClosed; +import static io.netty.buffer.api.internal.Statics.bufferIsReadOnly; + class NioBuffer extends RcSupport implements Buffer, ReadableComponent, WritableComponent { private static final ByteBuffer CLOSED_BUFFER = ByteBuffer.allocate(0); @@ -417,9 +420,9 @@ class NioBuffer extends RcSupport implements Buffer, Readable } var drop = (ArcDrop) unsafeGetDrop(); unsafeSetDrop(new ArcDrop<>(drop)); - var bifurcatedSeg = rmem.slice(0, woff); + var bifurcatedBuffer = rmem.slice(0, woff); // TODO maybe incrementing the existing ArcDrop is enough; maybe we don't need to wrap it in another ArcDrop. - var bifurcatedBuf = new NioBuffer(base, bifurcatedSeg, control, new ArcDrop<>(drop.increment())); + var bifurcatedBuf = new NioBuffer(base, bifurcatedBuffer, control, new ArcDrop<>(drop.increment())); bifurcatedBuf.woff = woff; bifurcatedBuf.roff = roff; bifurcatedBuf.order(order()); @@ -893,7 +896,7 @@ class NioBuffer extends RcSupport implements Buffer, Readable wmem.putInt(woff, value); return this; } catch (IndexOutOfBoundsException e) { - throw checkWriteState(e, NioBuffer.this.woff); + throw checkWriteState(e, this.woff); } catch (ReadOnlyBufferException e) { throw bufferIsReadOnly(); } @@ -918,7 +921,7 @@ class NioBuffer extends RcSupport implements Buffer, Readable wmem.putInt(woff, (int) (value & 0xFFFFFFFFL)); return this; } catch (IndexOutOfBoundsException e) { - throw checkWriteState(e, NioBuffer.this.woff); + throw checkWriteState(e, this.woff); } catch (ReadOnlyBufferException e) { throw bufferIsReadOnly(); } @@ -1135,14 +1138,6 @@ class NioBuffer extends RcSupport implements Buffer, Readable return outOfBounds(index); } - private static IllegalStateException bufferIsClosed() { - return new IllegalStateException("This buffer is closed."); - } - - private static IllegalStateException bufferIsReadOnly() { - 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 " + diff --git a/src/main/java/io/netty/buffer/api/internal/Statics.java b/src/main/java/io/netty/buffer/api/internal/Statics.java index 61d9b16..c440a35 100644 --- a/src/main/java/io/netty/buffer/api/internal/Statics.java +++ b/src/main/java/io/netty/buffer/api/internal/Statics.java @@ -68,4 +68,12 @@ public interface Statics { dest.order(prevOrder); } } + + static IllegalStateException bufferIsClosed() { + return new IllegalStateException("This buffer is closed."); + } + + static IllegalStateException bufferIsReadOnly() { + return new IllegalStateException("This buffer is read-only."); + } } diff --git a/src/main/java/io/netty/buffer/api/memseg/MemSegBuffer.java b/src/main/java/io/netty/buffer/api/memseg/MemSegBuffer.java index 842e48a..75d2f99 100644 --- a/src/main/java/io/netty/buffer/api/memseg/MemSegBuffer.java +++ b/src/main/java/io/netty/buffer/api/memseg/MemSegBuffer.java @@ -38,6 +38,8 @@ import jdk.incubator.foreign.ResourceScope; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import static io.netty.buffer.api.internal.Statics.bufferIsClosed; +import static io.netty.buffer.api.internal.Statics.bufferIsReadOnly; import static jdk.incubator.foreign.MemoryAccess.getByteAtOffset; import static jdk.incubator.foreign.MemoryAccess.getCharAtOffset; import static jdk.incubator.foreign.MemoryAccess.getDoubleAtOffset; @@ -1168,14 +1170,6 @@ class MemSegBuffer extends RcSupport implements Buffer, Re return outOfBounds(index); } - private static IllegalStateException bufferIsClosed() { - return new IllegalStateException("This buffer is closed."); - } - - private static IllegalStateException bufferIsReadOnly() { - 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 " + diff --git a/src/main/java/io/netty/buffer/api/unsafe/CleanerDrop.java b/src/main/java/io/netty/buffer/api/unsafe/CleanerDrop.java new file mode 100644 index 0000000..d252936 --- /dev/null +++ b/src/main/java/io/netty/buffer/api/unsafe/CleanerDrop.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021 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.unsafe; + +import io.netty.buffer.api.Buffer; +import io.netty.buffer.api.Drop; +import io.netty.util.internal.PlatformDependent; + +import java.lang.ref.Cleaner; + +public class CleanerDrop implements Drop { + private final Drop drop; + + public CleanerDrop(UnsafeMemory memory, Drop drop, Cleaner cleaner) { + this.drop = drop; + long address = memory.address; + cleaner.register(memory, new FreeAddress(address)); + } + + @Override + public void drop(Buffer obj) { + drop.drop(obj); + } + + @Override + public void attach(Buffer obj) { + drop.attach(obj); + } + + private static class FreeAddress implements Runnable { + private final long address; + + FreeAddress(long address) { + this.address = address; + } + + @Override + public void run() { + PlatformDependent.freeMemory(address); + } + } +} diff --git a/src/main/java/io/netty/buffer/api/unsafe/UnsafeBuffer.java b/src/main/java/io/netty/buffer/api/unsafe/UnsafeBuffer.java new file mode 100644 index 0000000..1b5cf32 --- /dev/null +++ b/src/main/java/io/netty/buffer/api/unsafe/UnsafeBuffer.java @@ -0,0 +1,1557 @@ +/* + * Copyright 2021 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.unsafe; + +import io.netty.buffer.api.AllocatorControl; +import io.netty.buffer.api.Buffer; +import io.netty.buffer.api.BufferAllocator; +import io.netty.buffer.api.ByteCursor; +import io.netty.buffer.api.Drop; +import io.netty.buffer.api.Owned; +import io.netty.buffer.api.RcSupport; +import io.netty.buffer.api.ReadableComponent; +import io.netty.buffer.api.ReadableComponentProcessor; +import io.netty.buffer.api.WritableComponent; +import io.netty.buffer.api.WritableComponentProcessor; +import io.netty.buffer.api.internal.ArcDrop; +import io.netty.buffer.api.internal.Statics; +import io.netty.util.internal.PlatformDependent; + +import java.lang.ref.Reference; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static io.netty.buffer.api.internal.Statics.bufferIsClosed; +import static io.netty.buffer.api.internal.Statics.bufferIsReadOnly; +import static io.netty.util.internal.PlatformDependent.BIG_ENDIAN_NATIVE_ORDER; + +public class UnsafeBuffer extends RcSupport implements Buffer, ReadableComponent, + WritableComponent { + private static final int CLOSED_SIZE = -1; + private static final boolean ACCESS_UNALIGNED = PlatformDependent.isUnaligned(); + private UnsafeMemory memory; // The memory liveness; monitored by Cleaner. + private Object base; // On-heap address reference object, or null for off-heap. + private long baseOffset; // Offset of this buffer into the memory. + private long address; // Resolved address (baseOffset + memory.address). + private int rsize; + private int wsize; + private final AllocatorControl control; + private ByteOrder order; + private boolean flipBytes; + private boolean readOnly; + private int roff; + private int woff; + + public UnsafeBuffer(UnsafeMemory memory, long offset, int size, AllocatorControl allocatorControl, + Drop drop) { + super(new MakeInaccisbleOnDrop(ArcDrop.wrap(drop))); + this.memory = memory; + base = memory.base; + baseOffset = offset; + address = memory.address + offset; + rsize = size; + wsize = size; + control = allocatorControl; + order = ByteOrder.nativeOrder(); + } + + @Override + public String toString() { + return "Buffer[roff:" + roff + ", woff:" + woff + ", cap:" + rsize + ']'; + } + + @Override + public Buffer order(ByteOrder order) { + this.order = order; + flipBytes = order != ByteOrder.nativeOrder(); + return this; + } + + @Override + public ByteOrder order() { + return order; + } + + @Override + public int capacity() { + return rsize; + } + + @Override + public int readerOffset() { + return roff; + } + + @Override + public Buffer readerOffset(int offset) { + roff = offset; + return this; + } + + @Override + public int writerOffset() { + return woff; + } + + @Override + public Buffer writerOffset(int offset) { + woff = offset; + return this; + } + + @Override + public Buffer fill(byte value) { + checkSet(0, capacity()); + try { + PlatformDependent.setMemory(base, address, rsize, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public long nativeAddress() { + return base == null? address : 0; + } + + @Override + public Buffer readOnly(boolean readOnly) { + this.readOnly = readOnly; + wsize = readOnly? CLOSED_SIZE : rsize; + return this; + } + + @Override + public boolean readOnly() { + return readOnly; + } + + @Override + public Buffer slice(int offset, int length) { + if (length < 0) { + throw new IllegalArgumentException("Length cannot be negative: " + length + '.'); + } + if (!isAccessible()) { + throw new IllegalStateException("This buffer is closed: " + this + '.'); + } + ArcDrop drop = (ArcDrop) unsafeGetDrop(); + drop.increment(); + return new UnsafeBuffer(memory, baseOffset + offset, length, control, drop) + .writerOffset(length) + .order(order) + .readOnly(readOnly); + } + + @Override + public void copyInto(int srcPos, byte[] dest, int destPos, int length) { + checkCopyIntoArgs(srcPos, length, destPos, dest.length); + copyIntoArray(srcPos, dest, destPos, length); + } + + private void copyIntoArray(int srcPos, byte[] dest, int destPos, int length) { + long destOffset = PlatformDependent.byteArrayBaseOffset(); + try { + PlatformDependent.copyMemory(base, address + srcPos, dest, destOffset + destPos, length); + } finally { + Reference.reachabilityFence(memory); + Reference.reachabilityFence(dest); + } + } + + @Override + public void copyInto(int srcPos, ByteBuffer dest, int destPos, int length) { + checkCopyIntoArgs(srcPos, length, destPos, dest.capacity()); + if (dest.hasArray()) { + copyIntoArray(srcPos, dest.array(), dest.arrayOffset() + destPos, length); + } else { + assert dest.isDirect(); + long destAddr = PlatformDependent.directBufferAddress(dest); + try { + PlatformDependent.copyMemory(base, address + srcPos, null, destAddr + destPos, length); + } finally { + Reference.reachabilityFence(memory); + Reference.reachabilityFence(dest); + } + } + } + + private void checkCopyIntoArgs(int srcPos, int length, int destPos, int destLength) { + if (rsize == CLOSED_SIZE) { + throw bufferIsClosed(); + } + if (srcPos < 0) { + throw new IllegalArgumentException("The srcPos cannot be negative: " + srcPos + '.'); + } + if (length < 0) { + throw new IllegalArgumentException("The length cannot be negative: " + length + '.'); + } + if (rsize < srcPos + length) { + throw new IllegalArgumentException("The srcPos + length is beyond the end of the buffer: " + + "srcPos = " + srcPos + ", length = " + length + '.'); + } + if (destPos < 0) { + throw new IllegalArgumentException("The destPos cannot be negative: " + destPos + '.'); + } + if (destLength < destPos + length) { + throw new IllegalArgumentException("The destPos + length is beyond the end of the destination: " + + "destPos = " + destPos + ", length = " + length + '.'); + } + } + + @Override + public void copyInto(int srcPos, Buffer dest, int destPos, int length) { + checkCopyIntoArgs(srcPos, length, destPos, dest.capacity()); + long nativeAddress = dest.nativeAddress(); + try { + if (nativeAddress != 0) { + PlatformDependent.copyMemory(base, address + srcPos, null, nativeAddress, length); + } else if (dest instanceof UnsafeBuffer) { + UnsafeBuffer destUnsafe = (UnsafeBuffer) dest; + PlatformDependent.copyMemory( + base, address + srcPos, destUnsafe.base, destUnsafe.address + destPos, length); + } else { + Statics.copyToViaReverseCursor(this, srcPos, dest, destPos, length); + } + } finally { + Reference.reachabilityFence(memory); + Reference.reachabilityFence(dest); + } + } + + @Override + public ByteCursor openCursor() { + return openCursor(readerOffset(), readableBytes()); + } + + @Override + public ByteCursor openCursor(int fromOffset, int length) { + if (rsize == CLOSED_SIZE) { + throw bufferIsClosed(); + } + if (fromOffset < 0) { + throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.'); + } + if (length < 0) { + throw new IllegalArgumentException("The length cannot be negative: " + length + '.'); + } + if (capacity() < fromOffset + length) { + throw new IllegalArgumentException("The fromOffset + length is beyond the end of the buffer: " + + "fromOffset = " + fromOffset + ", length = " + length + '.'); + } + return new ByteCursor() { + final UnsafeMemory memory = UnsafeBuffer.this.memory; // Keep memory alive. + final Object baseObj = base; + final long baseAddress = address; + int index = fromOffset; + final int end = index + length; + long longValue = -1; + byte byteValue = -1; + + @Override + public boolean readLong() { + if (index + Long.BYTES <= end) { + try { + long value = PlatformDependent.getLong(baseObj, baseAddress + index); + longValue = BIG_ENDIAN_NATIVE_ORDER? value : Long.reverseBytes(value); + } finally { + Reference.reachabilityFence(memory); + } + index += Long.BYTES; + return true; + } + return false; + } + + @Override + public long getLong() { + return longValue; + } + + @Override + public boolean readByte() { + if (index < end) { + try { + byteValue = PlatformDependent.getByte(baseObj, baseAddress + index); + } finally { + Reference.reachabilityFence(memory); + } + index++; + return true; + } + return false; + } + + @Override + public byte getByte() { + return byteValue; + } + + @Override + public int currentOffset() { + return index; + } + + @Override + public int bytesLeft() { + return end - index; + } + }; + } + + @Override + public ByteCursor openReverseCursor(int fromOffset, int length) { + if (rsize == CLOSED_SIZE) { + throw bufferIsClosed(); + } + if (fromOffset < 0) { + throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.'); + } + if (length < 0) { + throw new IllegalArgumentException("The length cannot be negative: " + length + '.'); + } + if (capacity() <= fromOffset) { + throw new IllegalArgumentException("The fromOffset is beyond the end of the buffer: " + fromOffset + '.'); + } + if (fromOffset - length < -1) { + throw new IllegalArgumentException("The fromOffset - length would underflow the buffer: " + + "fromOffset = " + fromOffset + ", length = " + length + '.'); + } + return new ByteCursor() { + final UnsafeMemory memory = UnsafeBuffer.this.memory; // Keep memory alive. + final Object baseObj = base; + final long baseAddress = address; + int index = fromOffset; + final int end = index - length; + long longValue = -1; + byte byteValue = -1; + + @Override + public boolean readLong() { + if (index - Long.BYTES >= end) { + index -= 7; + try { + long value = PlatformDependent.getLong(baseObj, baseAddress + index); + longValue = BIG_ENDIAN_NATIVE_ORDER? value : Long.reverseBytes(value); + } finally { + Reference.reachabilityFence(memory); + } + index--; + return true; + } + return false; + } + + @Override + public long getLong() { + return longValue; + } + + @Override + public boolean readByte() { + if (index > end) { + try { + byteValue = PlatformDependent.getByte(baseObj, baseAddress + index); + } finally { + Reference.reachabilityFence(memory); + } + index--; + return true; + } + return false; + } + + @Override + public byte getByte() { + return byteValue; + } + + @Override + public int currentOffset() { + return index; + } + + @Override + public int bytesLeft() { + return index - end; + } + }; + } + + @Override + public void ensureWritable(int size, boolean allowCompaction) { + if (!isOwned()) { + throw attachTrace(new IllegalStateException( + "Buffer is not owned. Only owned buffers can call ensureWritable.")); + } + if (size < 0) { + throw new IllegalArgumentException("Cannot ensure writable for a negative size: " + size + '.'); + } + if (rsize != wsize) { + throw bufferIsReadOnly(); + } + if (writableBytes() >= size) { + // We already have enough space. + return; + } + + if (allowCompaction && writableBytes() + readerOffset() >= size) { + // We can solve this with compaction. + compact(); + return; + } + + // Allocate a bigger buffer. + long newSize = capacity() + size - (long) writableBytes(); + BufferAllocator.checkSize(newSize); + UnsafeMemory memory = (UnsafeMemory) control.allocateUntethered(this, (int) newSize); + + // Copy contents. + try { + PlatformDependent.copyMemory(base, address, memory.base, memory.address, rsize); + } finally { + Reference.reachabilityFence(this.memory); + Reference.reachabilityFence(memory); + } + + // Release old memory: + var drop = (Drop) unsafeGetDrop(); + int roff = this.roff; + int woff = this.woff; + drop.drop(this); + while (drop instanceof ArcDrop) { + drop = ((ArcDrop) drop).unwrap(); + } + unsafeSetDrop(new ArcDrop<>(drop)); + this.roff = roff; + this.woff = woff; + + this.memory = memory; + base = memory.base; + baseOffset = 0; + address = memory.address; + rsize = memory.size; + wsize = memory.size; + drop.attach(this); + } + + @Override + public Buffer bifurcate() { + if (!isOwned()) { + throw attachTrace(new IllegalStateException("Cannot bifurcate a buffer that is not owned.")); + } + var drop = (ArcDrop) unsafeGetDrop(); + unsafeSetDrop(new ArcDrop<>(drop)); + // TODO maybe incrementing the existing ArcDrop is enough; maybe we don't need to wrap it in another ArcDrop. + var bifurcatedBuf = new UnsafeBuffer(memory, baseOffset, woff, control, new ArcDrop<>(drop.increment())); + bifurcatedBuf.woff = woff; + bifurcatedBuf.roff = roff; + bifurcatedBuf.order(order()); + boolean readOnly = readOnly(); + bifurcatedBuf.readOnly(readOnly); + rsize -= woff; + baseOffset += woff; + address += woff; + if (!readOnly) { + wsize = rsize; + } + woff = 0; + roff = 0; + return bifurcatedBuf; + } + + @Override + public void compact() { + if (!isOwned()) { + throw attachTrace(new IllegalStateException("Buffer must be owned in order to compact.")); + } + if (readOnly()) { + throw new IllegalStateException("Buffer must be writable in order to compact, but was read-only."); + } + if (roff == 0) { + return; + } + try { + PlatformDependent.copyMemory(base, address + roff, base, address, woff - roff); + } finally { + Reference.reachabilityFence(memory); + } + woff -= roff; + roff = 0; + } + + @Override + public int countComponents() { + return 1; + } + + @Override + public int countReadableComponents() { + return readableBytes() > 0? 1 : 0; + } + + @Override + public int countWritableComponents() { + return writableBytes() > 0? 1 : 0; + } + + // + @Override + public boolean hasReadableArray() { + return base instanceof byte[]; + } + + @Override + public byte[] readableArray() { + checkHasReadableArray(); + return (byte[]) base; + } + + @Override + public int readableArrayOffset() { + checkHasReadableArray(); + return Math.toIntExact(address + roff - PlatformDependent.byteArrayBaseOffset()); + } + + private void checkHasReadableArray() { + if (!hasReadableArray()) { + throw new UnsupportedOperationException("No readable array available."); + } + } + + @Override + public int readableArrayLength() { + return woff - roff; + } + + @Override + public long readableNativeAddress() { + return nativeAddress(); + } + + @Override + public ByteBuffer readableBuffer() { + final ByteBuffer buf; + if (hasReadableArray()) { + buf = ByteBuffer.wrap(readableArray()).slice(readableArrayOffset(), readableArrayLength()); + } else { + buf = PlatformDependent.directBuffer(address + roff, readableBytes()); + } + return buf.asReadOnlyBuffer().order(order()); + } + + @Override + public boolean hasWritableArray() { + return hasReadableArray(); + } + + @Override + public byte[] writableArray() { + checkHasWritableArray(); + return (byte[]) base; + } + + @Override + public int writableArrayOffset() { + checkHasWritableArray(); + return Math.toIntExact(address + woff - PlatformDependent.byteArrayBaseOffset()); + } + + private void checkHasWritableArray() { + if (!hasReadableArray()) { + throw new UnsupportedOperationException("No writable array available."); + } + } + + @Override + public int writableArrayLength() { + return capacity() - woff; + } + + @Override + public long writableNativeAddress() { + return nativeAddress(); + } + + @Override + public ByteBuffer writableBuffer() { + final ByteBuffer buf; + if (hasWritableArray()) { + buf = ByteBuffer.wrap(writableArray()).slice(writableArrayOffset(), writableArrayLength()); + } else { + buf = PlatformDependent.directBuffer(address + woff, writableBytes()); + } + return buf.order(order()); + } + // + + @Override + public int forEachReadable(int initialIndex, ReadableComponentProcessor processor) + throws E { + checkRead(readerOffset(), Math.max(1, readableBytes())); + return processor.process(initialIndex, this)? 1 : -1; + } + + @Override + public int forEachWritable(int initialIndex, WritableComponentProcessor processor) + throws E { + checkWrite(writerOffset(), Math.max(1, writableBytes())); + return processor.process(initialIndex, this)? 1 : -1; + } + + @Override + public byte readByte() { + checkRead(roff, Byte.BYTES); + try { + var value = loadByte(address + roff); + roff += Byte.BYTES; + return value; + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public byte getByte(int roff) { + checkGet(roff, Byte.BYTES); + try { + return loadByte(address + roff); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public int readUnsignedByte() { + return readByte() & 0xFF; + } + + @Override + public int getUnsignedByte(int roff) { + return getByte(roff) & 0xFF; + } + + @Override + public Buffer writeByte(byte value) { + checkWrite(woff, Byte.BYTES); + long offset = address + woff; + woff += Byte.BYTES; + try { + storeByte(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer setByte(int woff, byte value) { + checkSet(woff, Byte.BYTES); + long offset = address + woff; + try { + storeByte(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer writeUnsignedByte(int value) { + checkWrite(woff, Byte.BYTES); + long offset = address + woff; + woff += Byte.BYTES; + try { + storeByte(offset, (byte) (value & 0xFF)); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer setUnsignedByte(int woff, int value) { + checkSet(woff, Byte.BYTES); + long offset = address + woff; + try { + storeByte(offset, (byte) (value & 0xFF)); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public char readChar() { + checkRead(roff, Character.BYTES); + try { + long offset = address + roff; + roff += Character.BYTES; + return loadChar(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public char getChar(int roff) { + checkGet(roff, Character.BYTES); + try { + long offset = address + roff; + return loadChar(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public Buffer writeChar(char value) { + checkWrite(woff, Character.BYTES); + long offset = address + woff; + woff += Character.BYTES; + try { + storeChar(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer setChar(int woff, char value) { + checkSet(woff, Character.BYTES); + long offset = address + woff; + try { + storeChar(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public short readShort() { + checkRead(roff, Short.BYTES); + try { + long offset = address + roff; + roff += Short.BYTES; + return loadShort(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public short getShort(int roff) { + checkGet(roff, Short.BYTES); + try { + long offset = address + roff; + return loadShort(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public int readUnsignedShort() { + return readShort() & 0xFFFF; + } + + @Override + public int getUnsignedShort(int roff) { + return getShort(roff) & 0xFFFF; + } + + @Override + public Buffer writeShort(short value) { + checkWrite(woff, Short.BYTES); + long offset = address + woff; + woff += Short.BYTES; + try { + storeShort(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer setShort(int woff, short value) { + checkSet(woff, Short.BYTES); + long offset = address + woff; + try { + storeShort(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer writeUnsignedShort(int value) { + checkWrite(woff, Short.BYTES); + long offset = address + woff; + woff += Short.BYTES; + try { + storeShort(offset, (short) (value & 0xFFFF)); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer setUnsignedShort(int woff, int value) { + checkSet(woff, Short.BYTES); + long offset = address + woff; + try { + storeShort(offset, (short) (value & 0xFFFF)); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public int readMedium() { + checkRead(roff, 3); + int value = order() == ByteOrder.BIG_ENDIAN ? + loadByte(roff) << 16 | + (loadByte(roff + 1) & 0xFF) << 8 | + loadByte(roff + 2) & 0xFF : + loadByte(roff) & 0xFF | + (loadByte(roff + 1) & 0xFF) << 8 | + loadByte(roff + 2) << 16; + roff += 3; + return value; + } + + @Override + public int getMedium(int roff) { + checkGet(roff, 3); + return order() == ByteOrder.BIG_ENDIAN? + loadByte(roff) << 16 | + (loadByte(roff + 1) & 0xFF) << 8 | + loadByte(roff + 2) & 0xFF : + loadByte(roff) & 0xFF | + (loadByte(roff + 1) & 0xFF) << 8 | + loadByte(roff + 2) << 16; + } + + @Override + public int readUnsignedMedium() { + checkRead(roff, 3); + int value = order() == ByteOrder.BIG_ENDIAN? + (loadByte(roff) << 16 | + (loadByte(roff + 1) & 0xFF) << 8 | + loadByte(roff + 2) & 0xFF) & 0xFFFFFF : + (loadByte(roff) & 0xFF | + (loadByte(roff + 1) & 0xFF) << 8 | + loadByte(roff + 2) << 16) & 0xFFFFFF; + roff += 3; + return value; + } + + @Override + public int getUnsignedMedium(int roff) { + checkGet(roff, 3); + return order() == ByteOrder.BIG_ENDIAN? + (loadByte(roff) << 16 | + (loadByte(roff + 1) & 0xFF) << 8 | + loadByte(roff + 2) & 0xFF) & 0xFFFFFF : + (loadByte(roff) & 0xFF | + (loadByte(roff + 1) & 0xFF) << 8 | + loadByte(roff + 2) << 16) & 0xFFFFFF; + } + + @Override + public Buffer writeMedium(int value) { + checkWrite(woff, 3); + if (order() == ByteOrder.BIG_ENDIAN) { + storeByte(woff, (byte) (value >> 16)); + storeByte(woff + 1, (byte) (value >> 8 & 0xFF)); + storeByte(woff + 2, (byte) (value & 0xFF)); + } else { + storeByte(woff, (byte) (value & 0xFF)); + storeByte(woff + 1, (byte) (value >> 8 & 0xFF)); + storeByte(woff + 2, (byte) (value >> 16 & 0xFF)); + } + woff += 3; + return this; + } + + @Override + public Buffer setMedium(int woff, int value) { + checkSet(woff, 3); + if (order() == ByteOrder.BIG_ENDIAN) { + storeByte(woff, (byte) (value >> 16)); + storeByte(woff + 1, (byte) (value >> 8 & 0xFF)); + storeByte(woff + 2, (byte) (value & 0xFF)); + } else { + storeByte(woff, (byte) (value & 0xFF)); + storeByte(woff + 1, (byte) (value >> 8 & 0xFF)); + storeByte(woff + 2, (byte) (value >> 16 & 0xFF)); + } + return this; + } + + @Override + public Buffer writeUnsignedMedium(int value) { + checkWrite(woff, 3); + if (order() == ByteOrder.BIG_ENDIAN) { + storeByte(woff, (byte) (value >> 16)); + storeByte(woff + 1, (byte) (value >> 8 & 0xFF)); + storeByte(woff + 2, (byte) (value & 0xFF)); + } else { + storeByte(woff, (byte) (value & 0xFF)); + storeByte(woff + 1, (byte) (value >> 8 & 0xFF)); + storeByte(woff + 2, (byte) (value >> 16 & 0xFF)); + } + woff += 3; + return this; + } + + @Override + public Buffer setUnsignedMedium(int woff, int value) { + checkSet(woff, 3); + if (order() == ByteOrder.BIG_ENDIAN) { + storeByte(woff, (byte) (value >> 16)); + storeByte(woff + 1, (byte) (value >> 8 & 0xFF)); + storeByte(woff + 2, (byte) (value & 0xFF)); + } else { + storeByte(woff, (byte) (value & 0xFF)); + storeByte(woff + 1, (byte) (value >> 8 & 0xFF)); + storeByte(woff + 2, (byte) (value >> 16 & 0xFF)); + } + return this; + } + + @Override + public int readInt() { + checkRead(roff, Integer.BYTES); + try { + long offset = address + roff; + roff += Integer.BYTES; + return loadInt(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public int getInt(int roff) { + checkGet(roff, Integer.BYTES); + try { + long offset = address + roff; + return loadInt(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public long readUnsignedInt() { + return readInt() & 0x0000_0000_FFFF_FFFFL; + } + + @Override + public long getUnsignedInt(int roff) { + return getInt(roff) & 0x0000_0000_FFFF_FFFFL; + } + + @Override + public Buffer writeInt(int value) { + checkWrite(woff, Integer.BYTES); + long offset = address + woff; + woff += Integer.BYTES; + try { + storeInt(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer setInt(int woff, int value) { + checkSet(woff, Integer.BYTES); + long offset = address + woff; + try { + storeInt(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer writeUnsignedInt(long value) { + checkWrite(woff, Integer.BYTES); + long offset = address + woff; + woff += Integer.BYTES; + try { + storeInt(offset, (int) (value & 0xFFFF_FFFFL)); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer setUnsignedInt(int woff, long value) { + checkSet(woff, Integer.BYTES); + long offset = address + woff; + try { + storeInt(offset, (int) (value & 0xFFFF_FFFFL)); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public float readFloat() { + checkRead(roff, Float.BYTES); + try { + long offset = address + roff; + roff += Float.BYTES; + return loadFloat(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public float getFloat(int roff) { + checkGet(roff, Float.BYTES); + try { + long offset = address + roff; + return loadFloat(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public Buffer writeFloat(float value) { + checkWrite(woff, Float.BYTES); + long offset = address + woff; + woff += Float.BYTES; + try { + storeFloat(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer setFloat(int woff, float value) { + checkSet(woff, Float.BYTES); + long offset = address + woff; + try { + storeFloat(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public long readLong() { + checkRead(roff, Long.BYTES); + try { + long offset = address + roff; + roff += Long.BYTES; + return loadLong(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public long getLong(int roff) { + checkGet(roff, Long.BYTES); + try { + long offset = address + roff; + return loadLong(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public Buffer writeLong(long value) { + checkWrite(woff, Long.BYTES); + long offset = address + woff; + woff += Long.BYTES; + try { + storeLong(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer setLong(int woff, long value) { + checkSet(woff, Long.BYTES); + long offset = address + woff; + try { + storeLong(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public double readDouble() { + checkRead(roff, Double.BYTES); + try { + long offset = address + roff; + roff += Double.BYTES; + return loadDouble(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public double getDouble(int roff) { + checkGet(roff, Double.BYTES); + try { + long offset = address + roff; + return loadDouble(offset); + } finally { + Reference.reachabilityFence(memory); + } + } + + @Override + public Buffer writeDouble(double value) { + checkWrite(woff, Double.BYTES); + long offset = address + woff; + woff += Double.BYTES; + try { + storeDouble(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + public Buffer setDouble(int woff, double value) { + checkSet(woff, Double.BYTES); + long offset = address + woff; + try { + storeDouble(offset, value); + } finally { + Reference.reachabilityFence(memory); + } + return this; + } + + @Override + protected Owned prepareSend() { + var order = order(); + var roff = this.roff; + var woff = this.woff; + var readOnly = readOnly(); + UnsafeMemory memory = this.memory; + AllocatorControl control = this.control; + long baseOffset = this.baseOffset; + int rsize = this.rsize; + makeInaccessible(); + return new Owned() { + @Override + public UnsafeBuffer transferOwnership(Drop drop) { + UnsafeBuffer copy = new UnsafeBuffer(memory, baseOffset, rsize, control, drop); + copy.order(order); + copy.roff = roff; + copy.woff = woff; + copy.readOnly(readOnly); + return copy; + } + }; + } + + @Override + protected Drop unsafeGetDrop() { + MakeInaccisbleOnDrop drop = (MakeInaccisbleOnDrop) super.unsafeGetDrop(); + return drop.delegate; + } + + @Override + protected void unsafeSetDrop(Drop replacement) { + super.unsafeSetDrop(new MakeInaccisbleOnDrop(replacement)); + } + + private static final class MakeInaccisbleOnDrop implements Drop { + final Drop delegate; + + private MakeInaccisbleOnDrop(Drop delegate) { + this.delegate = delegate; + } + + @Override + public void drop(UnsafeBuffer buf) { + try { + delegate.drop(buf); + } finally { + buf.makeInaccessible(); + } + } + + @Override + public void attach(UnsafeBuffer buf) { + delegate.attach(buf); + } + + @Override + public String toString() { + return "UnsafeDrop(" + delegate + ')'; + } + } + + void makeInaccessible() { + rsize = CLOSED_SIZE; + wsize = CLOSED_SIZE; + } + + @Override + public boolean isOwned() { + return super.isOwned() && ((ArcDrop) unsafeGetDrop()).isOwned(); + } + + @Override + public int countBorrows() { + return super.countBorrows() + ((ArcDrop) unsafeGetDrop()).countBorrows(); + } + + private void checkRead(int index, int size) { + if (index < 0 || woff < index + size) { + throw readAccessCheckException(index); + } + } + + private void checkGet(int index, int size) { + if (index < 0 || rsize < index + size) { + throw readAccessCheckException(index); + } + } + + private void checkWrite(int index, int size) { + if (index < roff || wsize < index + size) { + throw writeAccessCheckException(index); + } + } + + private void checkSet(int index, int size) { + if (index < 0 || wsize < index + size) { + throw writeAccessCheckException(index); + } + } + + private RuntimeException checkWriteState(IndexOutOfBoundsException ioobe, int offset) { + if (rsize == CLOSED_SIZE) { + return bufferIsClosed(); + } + if (wsize != rsize) { + return bufferIsReadOnly(); + } + + IndexOutOfBoundsException exception = outOfBounds(offset); + exception.addSuppressed(ioobe); + return exception; + } + + private RuntimeException readAccessCheckException(int index) { + if (rsize == CLOSED_SIZE) { + throw bufferIsClosed(); + } + return outOfBounds(index); + } + + private RuntimeException writeAccessCheckException(int index) { + if (rsize == CLOSED_SIZE) { + throw bufferIsClosed(); + } + if (wsize != rsize) { + return bufferIsReadOnly(); + } + return outOfBounds(index); + } + + private IndexOutOfBoundsException outOfBounds(int index) { + return new IndexOutOfBoundsException( + "Index " + index + " is out of bounds: [read 0 to " + woff + ", write 0 to " + + rsize + "]."); + } + + private byte loadByte(long off) { + return PlatformDependent.getByte(base, off); + } + + private char loadChar(long offset) { + if (ACCESS_UNALIGNED) { + var value = PlatformDependent.getChar(base, offset); + return flipBytes? Character.reverseBytes(value) : value; + } + return loadCharUnaligned(offset); + } + + private char loadCharUnaligned(long offset) { + final char value; + Object b = base; + if ((offset & 1) == 0) { + value = PlatformDependent.getChar(b, offset); + } else { + value = (char) (PlatformDependent.getByte(b, offset) << 8 | + PlatformDependent.getByte(b, offset + 1)); + } + return flipBytes? Character.reverseBytes(value) : value; + } + + private short loadShort(long offset) { + if (ACCESS_UNALIGNED) { + var value = PlatformDependent.getShort(base, offset); + return flipBytes? Short.reverseBytes(value) : value; + } + return loadShortUnaligned(offset); + } + + private short loadShortUnaligned(long offset) { + final short value; + Object b = base; + if ((offset & 1) == 0) { + value = PlatformDependent.getShort(b, offset); + } else { + value = (short) (PlatformDependent.getByte(b, offset) << 8 | + PlatformDependent.getByte(b, offset + 1)); + } + return flipBytes? Short.reverseBytes(value) : value; + } + + private int loadInt(long offset) { + if (ACCESS_UNALIGNED) { + var value = PlatformDependent.getInt(base, offset); + return flipBytes? Integer.reverseBytes(value) : value; + } + return loadIntUnaligned(offset); + } + + private int loadIntUnaligned(long offset) { + final int value; + Object b = base; + if ((offset & 3) == 0) { + value = PlatformDependent.getInt(b, offset); + } else if ((offset & 1) == 0) { + value = PlatformDependent.getShort(b, offset) << 16 | + PlatformDependent.getShort(b, offset + 2); + } else { + value = PlatformDependent.getByte(b, offset) << 24 | + PlatformDependent.getByte(b, offset + 1) << 16 | + PlatformDependent.getByte(b, offset + 2) << 8 | + PlatformDependent.getByte(b, offset + 3); + } + return flipBytes? Integer.reverseBytes(value) : value; + } + + private float loadFloat(long offset) { + if (ACCESS_UNALIGNED) { + if (flipBytes) { + var value = PlatformDependent.getInt(base, offset); + return Float.intBitsToFloat(Integer.reverseBytes(value)); + } + return PlatformDependent.getFloat(base, offset); + } + return loadFloatUnaligned(offset); + } + + private float loadFloatUnaligned(long offset) { + return Float.intBitsToFloat(loadIntUnaligned(offset)); + } + + private long loadLong(long offset) { + if (ACCESS_UNALIGNED) { + var value = PlatformDependent.getLong(base, offset); + return flipBytes? Long.reverseBytes(value) : value; + } + return loadLongUnaligned(offset); + } + + private long loadLongUnaligned(long offset) { + final long value; + Object b = base; + if ((offset & 7) == 0) { + value = PlatformDependent.getLong(b, offset); + } else if ((offset & 3) == 0) { + value = (long) PlatformDependent.getInt(b, offset) << 32 | + PlatformDependent.getInt(b, offset + 4); + } else if ((offset & 1) == 0) { + value = (long) PlatformDependent.getShort(b, offset) << 48 | + (long) PlatformDependent.getShort(b, offset + 2) << 32 | + (long) PlatformDependent.getShort(b, offset + 4) << 16 | + PlatformDependent.getShort(b, offset + 6); + } else { + value = (long) PlatformDependent.getByte(b, offset) << 54 | + (long) PlatformDependent.getByte(b, offset + 1) << 48 | + (long) PlatformDependent.getByte(b, offset + 2) << 40 | + (long) PlatformDependent.getByte(b, offset + 3) << 32 | + (long) PlatformDependent.getByte(b, offset + 4) << 24 | + (long) PlatformDependent.getByte(b, offset + 5) << 16 | + (long) PlatformDependent.getByte(b, offset + 6) << 8 | + PlatformDependent.getByte(b, offset + 7); + } + return flipBytes? Long.reverseBytes(value) : value; + } + + private double loadDouble(long offset) { + if (ACCESS_UNALIGNED) { + if (flipBytes) { + var value = PlatformDependent.getLong(base, offset); + return Double.longBitsToDouble(Long.reverseBytes(value)); + } + return PlatformDependent.getDouble(base, offset); + } + return loadDoubleUnaligned(offset); + } + + private double loadDoubleUnaligned(long offset) { + return Double.longBitsToDouble(loadLongUnaligned(offset)); + } + + private void storeByte(long offset, byte value) { + PlatformDependent.putByte(base, offset, value); + } + + private void storeChar(long offset, char value) { + if (flipBytes) { + value = Character.reverseBytes(value); + } + if (ACCESS_UNALIGNED) { + PlatformDependent.putChar(base, offset, value); + } else { + storeCharUnaligned(offset, value); + } + } + + private void storeCharUnaligned(long offset, char value) { + Object b = base; + if ((offset & 1) == 0) { + PlatformDependent.putChar(b, offset, value); + } else { + PlatformDependent.putByte(b, offset, (byte) (value >> 8)); + PlatformDependent.putByte(b, offset + 1, (byte) value); + } + } + + private void storeShort(long offset, short value) { + if (flipBytes) { + value = Short.reverseBytes(value); + } + if (ACCESS_UNALIGNED) { + PlatformDependent.putShort(base, offset, value); + } else { + storeShortUnaligned(offset, value); + } + } + + private void storeShortUnaligned(long offset, short value) { + Object b = base; + if ((offset & 1) == 0) { + PlatformDependent.putShort(b, offset, value); + } else { + PlatformDependent.putByte(b, offset, (byte) (value >> 8)); + PlatformDependent.putByte(b, offset + 1, (byte) value); + } + } + + private void storeInt(long offset, int value) { + if (flipBytes) { + value = Integer.reverseBytes(value); + } + if (ACCESS_UNALIGNED) { + PlatformDependent.putInt(base, offset, value); + } else { + storeIntUnaligned(offset, value); + } + } + + private void storeIntUnaligned(long offset, int value) { + Object b = base; + if ((offset & 3) == 0) { + PlatformDependent.putInt(b, offset, value); + } else if ((offset & 1) == 0) { + PlatformDependent.putShort(b, offset, (short) (value >> 16)); + PlatformDependent.putShort(b, offset + 2, (short) value); + } else { + PlatformDependent.putByte(b, offset, (byte) (value >> 24)); + PlatformDependent.putByte(b, offset + 1, (byte) (value >> 16)); + PlatformDependent.putByte(b, offset + 2, (byte) (value >> 8)); + PlatformDependent.putByte(b, offset + 3, (byte) value); + } + } + + private void storeFloat(long offset, float value) { + storeInt(offset, Float.floatToRawIntBits(value)); + } + + private void storeLong(long offset, long value) { + if (flipBytes) { + value = Long.reverseBytes(value); + } + if (ACCESS_UNALIGNED) { + PlatformDependent.putLong(base, offset, value); + } else { + storeLongUnaligned(offset, value); + } + } + + private void storeLongUnaligned(long offset, long value) { + Object b = base; + if ((offset & 7) == 0) { + PlatformDependent.putLong(b, offset, value); + } else if ((offset & 3) == 0) { + PlatformDependent.putInt(b, offset, (int) (value >> 32)); + PlatformDependent.putInt(b, offset + 4, (int) value); + } else if ((offset & 1) == 0) { + PlatformDependent.putShort(b, offset, (short) (value >> 48)); + PlatformDependent.putShort(b, offset + 16, (short) (value >> 32)); + PlatformDependent.putShort(b, offset + 32, (short) (value >> 16)); + PlatformDependent.putShort(b, offset + 48, (short) value); + } else { + PlatformDependent.putByte(b, offset, (byte) (value >> 56)); + PlatformDependent.putByte(b, offset + 1, (byte) (value >> 48)); + PlatformDependent.putByte(b, offset + 2, (byte) (value >> 40)); + PlatformDependent.putByte(b, offset + 3, (byte) (value >> 32)); + PlatformDependent.putByte(b, offset + 4, (byte) (value >> 24)); + PlatformDependent.putByte(b, offset + 5, (byte) (value >> 16)); + PlatformDependent.putByte(b, offset + 6, (byte) (value >> 8)); + PlatformDependent.putByte(b, offset + 7, (byte) value); + } + } + + private void storeDouble(long offset, double value) { + storeLong(offset, Double.doubleToRawLongBits(value)); + } + + Object recover() { + return memory; + } +} diff --git a/src/main/java/io/netty/buffer/api/unsafe/UnsafeMemory.java b/src/main/java/io/netty/buffer/api/unsafe/UnsafeMemory.java new file mode 100644 index 0000000..d4721d9 --- /dev/null +++ b/src/main/java/io/netty/buffer/api/unsafe/UnsafeMemory.java @@ -0,0 +1,28 @@ +/* + * Copyright 2021 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.unsafe; + +class UnsafeMemory { + final Object base; + final long address; + final int size; + + UnsafeMemory(Object base, long address, int size) { + this.base = base; + this.address = address; + this.size = size; + } +} diff --git a/src/main/java/io/netty/buffer/api/unsafe/UnsafeMemoryManager.java b/src/main/java/io/netty/buffer/api/unsafe/UnsafeMemoryManager.java new file mode 100644 index 0000000..62f594c --- /dev/null +++ b/src/main/java/io/netty/buffer/api/unsafe/UnsafeMemoryManager.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021 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.unsafe; + +import io.netty.buffer.api.AllocatorControl; +import io.netty.buffer.api.Buffer; +import io.netty.buffer.api.Drop; +import io.netty.buffer.api.MemoryManager; +import io.netty.buffer.api.internal.Statics; +import io.netty.util.internal.PlatformDependent; + +import java.lang.ref.Cleaner; + +import static io.netty.buffer.api.internal.Statics.convert; + +public class UnsafeMemoryManager implements MemoryManager { + private final boolean offheap; + + public UnsafeMemoryManager(boolean offheap) { + this.offheap = offheap; + } + + @Override + public boolean isNative() { + return offheap; + } + + @Override + public Buffer allocateConfined(AllocatorControl allocatorControl, long size, Drop drop, Cleaner cleaner) { + return allocateShared(allocatorControl, size, drop, cleaner); + } + + @Override + public Buffer allocateShared(AllocatorControl allocatorControl, long size, Drop drop, Cleaner cleaner) { + final Object base; + final long address; + final UnsafeMemory memory; + final int size32 = Math.toIntExact(size); + if (cleaner == null) { + cleaner = Statics.CLEANER; + } + if (offheap) { + base = null; + address = PlatformDependent.allocateMemory(size); + PlatformDependent.setMemory(address, size, (byte) 0); + memory = new UnsafeMemory(base, address, size32); + drop = new CleanerDrop(memory, drop, cleaner); + } else { + base = new byte[size32]; + address = PlatformDependent.byteArrayBaseOffset(); + memory = new UnsafeMemory(base, address, size32); + } + return new UnsafeBuffer(memory, 0, size32, allocatorControl, convert(drop)); + } + + @Override + public Drop drop() { + // We cannot reliably drop unsafe memory. We have to rely on the cleaner to do that. + return Statics.NO_OP_DROP; + } + + @Override + public Object unwrapRecoverableMemory(Buffer buf) { + return ((UnsafeBuffer) buf).recover(); + } + + @Override + public int capacityOfRecoverableMemory(Object memory) { + return ((UnsafeMemory) memory).size; + } + + @Override + public Buffer recoverMemory(AllocatorControl allocatorControl, Object recoverableMemory, Drop drop) { + UnsafeMemory memory = (UnsafeMemory) recoverableMemory; + return new UnsafeBuffer(memory, 0, memory.size, allocatorControl, convert(drop)); + } +} diff --git a/src/main/java/io/netty/buffer/api/unsafe/UnsafeMemoryManagers.java b/src/main/java/io/netty/buffer/api/unsafe/UnsafeMemoryManagers.java new file mode 100644 index 0000000..e1f0ed3 --- /dev/null +++ b/src/main/java/io/netty/buffer/api/unsafe/UnsafeMemoryManagers.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021 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.unsafe; + +import io.netty.buffer.api.MemoryManager; +import io.netty.buffer.api.MemoryManagers; +import io.netty.util.internal.PlatformDependent; + +public class UnsafeMemoryManagers implements MemoryManagers { + public UnsafeMemoryManagers() { + if (!PlatformDependent.hasUnsafe()) { + throw new UnsupportedOperationException("Unsafe is not available."); + } + if (!PlatformDependent.hasDirectBufferNoCleanerConstructor()) { + throw new UnsupportedOperationException("DirectByteBuffer internal constructor is not available."); + } + } + + @Override + public MemoryManager getHeapMemoryManager() { + return new UnsafeMemoryManager(false); + } + + @Override + public MemoryManager getNativeMemoryManager() { + return new UnsafeMemoryManager(true); + } + + @Override + public String toString() { + return "US"; + } +} diff --git a/src/main/java/io/netty/buffer/api/unsafe/package-info.java b/src/main/java/io/netty/buffer/api/unsafe/package-info.java new file mode 100644 index 0000000..3fd081a --- /dev/null +++ b/src/main/java/io/netty/buffer/api/unsafe/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021 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. + */ + +/** + * A {@link io.netty.buffer.api.Buffer} implementation that is based on {@code sun.misc.Unsafe}. + */ +package io.netty.buffer.api.unsafe; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 4063afe..1b4d5a4 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -32,5 +32,6 @@ module netty.incubator.buffer { provides io.netty.buffer.api.MemoryManagers with io.netty.buffer.api.memseg.SegmentMemoryManagers, - io.netty.buffer.api.bytebuffer.ByteBufferMemoryManagers; + io.netty.buffer.api.bytebuffer.ByteBufferMemoryManagers, + io.netty.buffer.api.unsafe.UnsafeMemoryManagers; } \ No newline at end of file