From e7449b1ef361c55457ed21d44d6ed8387ec1fa45 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Fri, 5 Aug 2016 20:17:24 +0200 Subject: [PATCH] [#5645] Allow to create ByteBuf from existing memory address. Motivation: Sometimes it is useful to be able to wrap an existing memory address (a.k.a pointer) and create a ByteBuf from it. This way its easier to interopt with other libraries. Modifications: Add a new Unpooled.wrappedBuffer(....) method that takes a memory address. Result: Be able to wrap an existing memory address into a ByteBuf. --- .../main/java/io/netty/buffer/Unpooled.java | 8 ++ .../buffer/UnpooledUnsafeDirectByteBuf.java | 8 +- .../WrappedUnpooledUnsafeDirectByteBuf.java | 32 +++++ .../WrappedUnpooledUnsafeByteBufTest.java | 126 ++++++++++++++++++ .../io/netty/util/internal/ObjectUtil.java | 11 ++ .../util/internal/PlatformDependent.java | 8 ++ .../util/internal/PlatformDependent0.java | 5 +- 7 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 buffer/src/main/java/io/netty/buffer/WrappedUnpooledUnsafeDirectByteBuf.java create mode 100644 buffer/src/test/java/io/netty/buffer/WrappedUnpooledUnsafeByteBufTest.java diff --git a/buffer/src/main/java/io/netty/buffer/Unpooled.java b/buffer/src/main/java/io/netty/buffer/Unpooled.java index 420b7fe625..42d571a61f 100644 --- a/buffer/src/main/java/io/netty/buffer/Unpooled.java +++ b/buffer/src/main/java/io/netty/buffer/Unpooled.java @@ -207,6 +207,14 @@ public final class Unpooled { } } + /** + * Creates a new buffer which wraps the specified memory address. If {@code doFree} is true the + * memoryAddress will automatically be freed once the reference count of the {@link ByteBuf} reaches {@code 0}. + */ + public static ByteBuf wrappedBuffer(long memoryAddress, int size, boolean doFree) { + return new WrappedUnpooledUnsafeDirectByteBuf(ALLOC, memoryAddress, size, doFree); + } + /** * Creates a new buffer which wraps the specified buffer's readable bytes. * A modification on the specified buffer's content will be visible to the diff --git a/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeDirectByteBuf.java b/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeDirectByteBuf.java index a54e3af64f..6f27818034 100644 --- a/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeDirectByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeDirectByteBuf.java @@ -36,11 +36,11 @@ public class UnpooledUnsafeDirectByteBuf extends AbstractReferenceCountedByteBuf private final ByteBufAllocator alloc; - private long memoryAddress; private ByteBuffer tmpNioBuf; private int capacity; private boolean doNotFree; ByteBuffer buffer; + long memoryAddress; /** * Creates a new direct buffer. @@ -74,6 +74,10 @@ public class UnpooledUnsafeDirectByteBuf extends AbstractReferenceCountedByteBuf * @param maxCapacity the maximum capacity of the underlying direct buffer */ protected UnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer, int maxCapacity) { + this(alloc, initialBuffer, maxCapacity, true); + } + + UnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer, int maxCapacity, boolean doFree) { super(maxCapacity); if (alloc == null) { throw new NullPointerException("alloc"); @@ -95,7 +99,7 @@ public class UnpooledUnsafeDirectByteBuf extends AbstractReferenceCountedByteBuf } this.alloc = alloc; - doNotFree = true; + doNotFree = !doFree; setByteBuffer(initialBuffer.slice().order(ByteOrder.BIG_ENDIAN), false); writerIndex(initialCapacity); } diff --git a/buffer/src/main/java/io/netty/buffer/WrappedUnpooledUnsafeDirectByteBuf.java b/buffer/src/main/java/io/netty/buffer/WrappedUnpooledUnsafeDirectByteBuf.java new file mode 100644 index 0000000000..91db4ded19 --- /dev/null +++ b/buffer/src/main/java/io/netty/buffer/WrappedUnpooledUnsafeDirectByteBuf.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 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: + * + * http://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; + +import io.netty.util.internal.PlatformDependent; + +import java.nio.ByteBuffer; + +final class WrappedUnpooledUnsafeDirectByteBuf extends UnpooledUnsafeDirectByteBuf { + + WrappedUnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, long memoryAddress, int size, boolean doFree) { + super(alloc, PlatformDependent.directBuffer(memoryAddress, size), size, doFree); + } + + @Override + protected void freeDirect(ByteBuffer buffer) { + PlatformDependent.freeMemory(memoryAddress); + } +} diff --git a/buffer/src/test/java/io/netty/buffer/WrappedUnpooledUnsafeByteBufTest.java b/buffer/src/test/java/io/netty/buffer/WrappedUnpooledUnsafeByteBufTest.java new file mode 100644 index 0000000000..645074b384 --- /dev/null +++ b/buffer/src/test/java/io/netty/buffer/WrappedUnpooledUnsafeByteBufTest.java @@ -0,0 +1,126 @@ +/* + * Copyright 2016 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: + * + * http://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; + +import io.netty.util.internal.PlatformDependent; +import org.junit.Test; + +import java.io.IOException; + +public class WrappedUnpooledUnsafeByteBufTest extends BigEndianUnsafeDirectByteBufTest { + + @Override + protected ByteBuf newBuffer(int length) { + return new WrappedUnpooledUnsafeDirectByteBuf(UnpooledByteBufAllocator.DEFAULT, + PlatformDependent.allocateMemory(length), length, true); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testInternalNioBuffer() { + super.testInternalNioBuffer(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testDuplicateReadGatheringByteChannelMultipleThreads() throws Exception { + super.testDuplicateReadGatheringByteChannelMultipleThreads(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testSliceReadGatheringByteChannelMultipleThreads() throws Exception { + super.testSliceReadGatheringByteChannelMultipleThreads(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testDuplicateReadOutputStreamMultipleThreads() throws Exception { + super.testDuplicateReadOutputStreamMultipleThreads(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testSliceReadOutputStreamMultipleThreads() throws Exception { + super.testSliceReadOutputStreamMultipleThreads(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testDuplicateBytesInArrayMultipleThreads() throws Exception { + super.testDuplicateBytesInArrayMultipleThreads(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testSliceBytesInArrayMultipleThreads() throws Exception { + super.testSliceBytesInArrayMultipleThreads(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testNioBufferExposeOnlyRegion() { + super.testNioBufferExposeOnlyRegion(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testEnsureWritableAfterRelease() { + super.testEnsureWritableAfterRelease(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testWriteZeroAfterRelease() throws IOException { + super.testWriteZeroAfterRelease(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testGetReadOnlyDirectDst() { + super.testGetReadOnlyDirectDst(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testGetReadOnlyHeapDst() { + super.testGetReadOnlyHeapDst(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testReadBytes() { + super.testReadBytes(); + } + + @Test(expected = IllegalArgumentException.class) + @Override + public void testDuplicateCapacityChange() { + super.testDuplicateCapacityChange(); + } + + @Test(expected = IllegalArgumentException.class) + @Override + public void testRetainedDuplicateCapacityChange() { + super.testRetainedDuplicateCapacityChange(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testLittleEndianWithExpand() { + super.testLittleEndianWithExpand(); + } +} diff --git a/common/src/main/java/io/netty/util/internal/ObjectUtil.java b/common/src/main/java/io/netty/util/internal/ObjectUtil.java index 98e5691cb9..50212c7232 100644 --- a/common/src/main/java/io/netty/util/internal/ObjectUtil.java +++ b/common/src/main/java/io/netty/util/internal/ObjectUtil.java @@ -66,6 +66,17 @@ public final class ObjectUtil { return i; } + /** + * Checks that the given argument is positive or zero. If it is, throws {@link IllegalArgumentException}. + * Otherwise, returns the argument. + */ + public static long checkPositiveOrZero(long i, String name) { + if (i < 0) { + throw new IllegalArgumentException(name + ": " + i + " (expected: >= 0)"); + } + return i; + } + /** * Checks that the given argument is neither null nor empty. * If it is, throws {@link NullPointerException} or {@link IllegalArgumentException}. diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent.java b/common/src/main/java/io/netty/util/internal/PlatformDependent.java index b16b21d42d..c1a94889df 100644 --- a/common/src/main/java/io/netty/util/internal/PlatformDependent.java +++ b/common/src/main/java/io/netty/util/internal/PlatformDependent.java @@ -365,6 +365,14 @@ public final class PlatformDependent { return PlatformDependent0.directBufferAddress(buffer); } + public static ByteBuffer directBuffer(long memoryAddress, int size) { + if (PlatformDependent0.hasDirectBufferNoCleanerConstructor()) { + return PlatformDependent0.newDirectBuffer(memoryAddress, size); + } + throw new UnsupportedOperationException( + "sun.misc.Unsafe or java.nio.DirectByteBuffer.(long, int) not available"); + } + public static Object getObject(Object object, long fieldOffset) { return PlatformDependent0.getObject(object, fieldOffset); } diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java index 31e374b0c9..3c6012601f 100644 --- a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java +++ b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java @@ -287,7 +287,10 @@ final class PlatformDependent0 { return newDirectBuffer(UNSAFE.allocateMemory(capacity), capacity); } - private static ByteBuffer newDirectBuffer(long address, int capacity) { + static ByteBuffer newDirectBuffer(long address, int capacity) { + ObjectUtil.checkPositiveOrZero(address, "address"); + ObjectUtil.checkPositiveOrZero(capacity, "capacity"); + try { return (ByteBuffer) DIRECT_BUFFER_CONSTRUCTOR.newInstance(address, capacity); } catch (Throwable cause) {