This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-fury.git
The following commit(s) were added to refs/heads/main by this push:
new 71c5b76f feat(java): channel stream reader (#1483)
71c5b76f is described below
commit 71c5b76fa6affd7453be27709ae2dfcc90eae52d
Author: Nikita Ivchenko <[email protected]>
AuthorDate: Wed Apr 10 10:44:02 2024 +0300
feat(java): channel stream reader (#1483)
Co-authored-by: Shawn Yang <[email protected]>
---
.../src/main/java/org/apache/fury/BaseFury.java | 9 ++
.../src/main/java/org/apache/fury/Fury.java | 32 ++++++
.../main/java/org/apache/fury/ThreadLocalFury.java | 21 ++++
.../java/org/apache/fury/io/FuryInputStream.java | 1 -
.../org/apache/fury/io/FuryReadableChannel.java | 126 ++++++++++++++++++---
.../java/org/apache/fury/io/FuryStreamReader.java | 13 +++
.../java/org/apache/fury/memory/MemoryBuffer.java | 20 +++-
.../java/org/apache/fury/pool/ThreadPoolFury.java | 21 ++++
.../src/test/java/org/apache/fury/StreamTest.java | 51 +++++++++
9 files changed, 272 insertions(+), 22 deletions(-)
diff --git a/java/fury-core/src/main/java/org/apache/fury/BaseFury.java
b/java/fury-core/src/main/java/org/apache/fury/BaseFury.java
index 1bcb31ee..91a62cc4 100644
--- a/java/fury-core/src/main/java/org/apache/fury/BaseFury.java
+++ b/java/fury-core/src/main/java/org/apache/fury/BaseFury.java
@@ -21,6 +21,7 @@ package org.apache.fury;
import java.io.OutputStream;
import org.apache.fury.io.FuryInputStream;
+import org.apache.fury.io.FuryReadableChannel;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.serializer.BufferCallback;
import org.apache.fury.serializer.Serializer;
@@ -114,6 +115,10 @@ public interface BaseFury {
Object deserialize(FuryInputStream inputStream, Iterable<MemoryBuffer>
outOfBandBuffers);
+ Object deserialize(FuryReadableChannel channel);
+
+ Object deserialize(FuryReadableChannel channel, Iterable<MemoryBuffer>
outOfBandBuffers);
+
/**
* Serialize java object without class info, deserialization should use
{@link
* #deserializeJavaObject}.
@@ -142,6 +147,8 @@ public interface BaseFury {
<T> T deserializeJavaObject(FuryInputStream inputStream, Class<T> cls);
+ <T> T deserializeJavaObject(FuryReadableChannel channel, Class<T> cls);
+
byte[] serializeJavaObjectAndClass(Object obj);
void serializeJavaObjectAndClass(MemoryBuffer buffer, Object obj);
@@ -153,4 +160,6 @@ public interface BaseFury {
Object deserializeJavaObjectAndClass(MemoryBuffer buffer);
Object deserializeJavaObjectAndClass(FuryInputStream inputStream);
+
+ Object deserializeJavaObjectAndClass(FuryReadableChannel channel);
}
diff --git a/java/fury-core/src/main/java/org/apache/fury/Fury.java
b/java/fury-core/src/main/java/org/apache/fury/Fury.java
index 63421ada..e1a8bbd5 100644
--- a/java/fury-core/src/main/java/org/apache/fury/Fury.java
+++ b/java/fury-core/src/main/java/org/apache/fury/Fury.java
@@ -38,6 +38,7 @@ import org.apache.fury.config.Language;
import org.apache.fury.config.LongEncoding;
import org.apache.fury.exception.DeserializationException;
import org.apache.fury.io.FuryInputStream;
+import org.apache.fury.io.FuryReadableChannel;
import org.apache.fury.logging.Logger;
import org.apache.fury.logging.LoggerFactory;
import org.apache.fury.memory.MemoryBuffer;
@@ -771,6 +772,17 @@ public final class Fury implements BaseFury {
}
}
+ @Override
+ public Object deserialize(FuryReadableChannel channel) {
+ return deserialize(channel, null);
+ }
+
+ @Override
+ public Object deserialize(FuryReadableChannel channel,
Iterable<MemoryBuffer> outOfBandBuffers) {
+ MemoryBuffer buf = channel.getBuffer();
+ return deserialize(buf, outOfBandBuffers);
+ }
+
private RuntimeException handleReadFailed(Throwable t) {
if (refResolver instanceof MapRefResolver) {
ObjectArray readObjects = ((MapRefResolver)
refResolver).getReadObjects();
@@ -1092,6 +1104,16 @@ public final class Fury implements BaseFury {
}
}
+ /**
+ * Deserialize java object from binary channel by passing class info,
serialization should use
+ * {@link #serializeJavaObject}.
+ */
+ @Override
+ public <T> T deserializeJavaObject(FuryReadableChannel channel, Class<T>
cls) {
+ MemoryBuffer buf = channel.getBuffer();
+ return deserializeJavaObject(buf, cls);
+ }
+
/**
* Deserialize java object from binary by passing class info, serialization
should use {@link
* #deserializeJavaObjectAndClass}.
@@ -1181,6 +1203,16 @@ public final class Fury implements BaseFury {
}
}
+ /**
+ * Deserialize class info and java object from binary channel, serialization
should use {@link
+ * #serializeJavaObjectAndClass}.
+ */
+ @Override
+ public Object deserializeJavaObjectAndClass(FuryReadableChannel channel) {
+ MemoryBuffer buf = channel.getBuffer();
+ return deserializeJavaObjectAndClass(buf);
+ }
+
private void serializeToStream(OutputStream outputStream,
Consumer<MemoryBuffer> function) {
MemoryBuffer buf = getBuffer();
if (outputStream.getClass() == ByteArrayOutputStream.class) {
diff --git a/java/fury-core/src/main/java/org/apache/fury/ThreadLocalFury.java
b/java/fury-core/src/main/java/org/apache/fury/ThreadLocalFury.java
index cf6e45df..67928cae 100644
--- a/java/fury-core/src/main/java/org/apache/fury/ThreadLocalFury.java
+++ b/java/fury-core/src/main/java/org/apache/fury/ThreadLocalFury.java
@@ -26,6 +26,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.concurrent.ThreadSafe;
import org.apache.fury.io.FuryInputStream;
+import org.apache.fury.io.FuryReadableChannel;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.memory.MemoryUtils;
import org.apache.fury.resolver.ClassResolver;
@@ -164,6 +165,16 @@ public class ThreadLocalFury extends
AbstractThreadSafeFury {
return bindingThreadLocal.get().get().deserialize(inputStream,
outOfBandBuffers);
}
+ @Override
+ public Object deserialize(FuryReadableChannel channel) {
+ return bindingThreadLocal.get().get().deserialize(channel);
+ }
+
+ @Override
+ public Object deserialize(FuryReadableChannel channel,
Iterable<MemoryBuffer> outOfBandBuffers) {
+ return bindingThreadLocal.get().get().deserialize(channel,
outOfBandBuffers);
+ }
+
@Override
public byte[] serializeJavaObject(Object obj) {
return bindingThreadLocal.get().get().serializeJavaObject(obj);
@@ -194,6 +205,11 @@ public class ThreadLocalFury extends
AbstractThreadSafeFury {
return bindingThreadLocal.get().get().deserializeJavaObject(inputStream,
cls);
}
+ @Override
+ public <T> T deserializeJavaObject(FuryReadableChannel channel, Class<T>
cls) {
+ return bindingThreadLocal.get().get().deserializeJavaObject(channel, cls);
+ }
+
@Override
public byte[] serializeJavaObjectAndClass(Object obj) {
return bindingThreadLocal.get().get().serializeJavaObjectAndClass(obj);
@@ -224,6 +240,11 @@ public class ThreadLocalFury extends
AbstractThreadSafeFury {
return
bindingThreadLocal.get().get().deserializeJavaObjectAndClass(inputStream);
}
+ @Override
+ public Object deserializeJavaObjectAndClass(FuryReadableChannel channel) {
+ return
bindingThreadLocal.get().get().deserializeJavaObjectAndClass(channel);
+ }
+
@Override
public void setClassLoader(ClassLoader classLoader) {
setClassLoader(classLoader, StagingType.SOFT_STAGING);
diff --git
a/java/fury-core/src/main/java/org/apache/fury/io/FuryInputStream.java
b/java/fury-core/src/main/java/org/apache/fury/io/FuryInputStream.java
index 4fc32152..f5006b4b 100644
--- a/java/fury-core/src/main/java/org/apache/fury/io/FuryInputStream.java
+++ b/java/fury-core/src/main/java/org/apache/fury/io/FuryInputStream.java
@@ -33,7 +33,6 @@ import org.apache.fury.util.Platform;
*/
@NotThreadSafe
public class FuryInputStream extends InputStream implements FuryStreamReader {
- private static final int BUFFER_GROW_STEP_THRESHOLD = 100 * 1024 * 1024;
private final InputStream stream;
private final int bufferSize;
private final MemoryBuffer buffer;
diff --git
a/java/fury-core/src/main/java/org/apache/fury/io/FuryReadableChannel.java
b/java/fury-core/src/main/java/org/apache/fury/io/FuryReadableChannel.java
index 74cd6e03..0aea9e76 100644
--- a/java/fury-core/src/main/java/org/apache/fury/io/FuryReadableChannel.java
+++ b/java/fury-core/src/main/java/org/apache/fury/io/FuryReadableChannel.java
@@ -22,42 +22,136 @@ package org.apache.fury.io;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.apache.fury.exception.DeserializationException;
import org.apache.fury.memory.MemoryBuffer;
+import org.apache.fury.util.Platform;
+import org.apache.fury.util.Preconditions;
-// TODO support zero-copy channel reading.
-public class FuryReadableChannel extends AbstractStreamReader implements
ReadableByteChannel {
+@NotThreadSafe
+public class FuryReadableChannel implements FuryStreamReader,
ReadableByteChannel {
private final ReadableByteChannel channel;
- private final ByteBuffer byteBuffer;
- private final MemoryBuffer buffer;
+ private final MemoryBuffer memoryBuffer;
+ private ByteBuffer byteBuffer;
public FuryReadableChannel(ReadableByteChannel channel) {
- this(channel, ByteBuffer.allocate(4096));
+ this(channel, ByteBuffer.allocateDirect(4096));
}
- private FuryReadableChannel(ReadableByteChannel channel, ByteBuffer
directBuffer) {
+ public FuryReadableChannel(ReadableByteChannel channel, ByteBuffer
directBuffer) {
+ Preconditions.checkArgument(
+ directBuffer.isDirect(), "FuryReadableChannel support only direct
ByteBuffer.");
this.channel = channel;
this.byteBuffer = directBuffer;
- this.buffer = MemoryBuffer.fromByteBuffer(directBuffer);
+ this.memoryBuffer = MemoryBuffer.fromDirectByteBuffer(directBuffer, 0,
this);
+ }
+
+ @Override
+ public int fillBuffer(int minFillSize) {
+ try {
+ ByteBuffer byteBuf = byteBuffer;
+ MemoryBuffer memoryBuf = memoryBuffer;
+ int position = byteBuf.position();
+ int newLimit = position + minFillSize;
+ if (newLimit > byteBuf.capacity()) {
+ int newSize =
+ newLimit < BUFFER_GROW_STEP_THRESHOLD ? newLimit << 2 : (int)
(newLimit * 1.5);
+ ByteBuffer newByteBuf = ByteBuffer.allocateDirect(newSize);
+ byteBuf.position(0);
+ newByteBuf.put(byteBuf);
+ byteBuf = byteBuffer = newByteBuf;
+ memoryBuf.initDirectBuffer(Platform.getAddress(byteBuf), position,
byteBuf);
+ }
+ byteBuf.limit(newLimit);
+ int readCount = channel.read(byteBuf);
+ memoryBuf.increaseSize(readCount);
+ return readCount;
+ } catch (IOException e) {
+ throw new DeserializationException("Failed to read the provided byte
channel", e);
+ }
}
@Override
public int read(ByteBuffer dst) throws IOException {
- MemoryBuffer buf = buffer;
+ int length = dst.remaining();
+ MemoryBuffer buf = memoryBuffer;
+ int remaining = buf.remaining();
+ if (remaining >= length) {
+ buf.read(dst, length);
+ return length;
+ } else {
+ buf.read(dst, remaining);
+ return channel.read(dst) + remaining;
+ }
+ }
+
+ @Override
+ public void readTo(byte[] dst, int dstIndex, int length) {
+ MemoryBuffer buf = memoryBuffer;
int remaining = buf.remaining();
- int len = dst.remaining();
- if (remaining >= len) {
- buf.read(dst, len);
- return len;
+ if (remaining >= length) {
+ buf.readBytes(dst, dstIndex, length);
} else {
+ buf.readBytes(dst, dstIndex, remaining);
try {
- buf.read(dst, remaining);
- return channel.read(dst) + remaining;
+ ByteBuffer buffer = ByteBuffer.wrap(dst, dstIndex + remaining, length
- remaining);
+ channel.read(buffer);
} catch (IOException e) {
- throw new RuntimeException(e);
+ throw new DeserializationException("Failed to read the provided byte
channel", e);
}
}
}
+ @Override
+ public void readToUnsafe(Object target, long targetPointer, int numBytes) {
+ MemoryBuffer buf = memoryBuffer;
+ int remaining = buf.remaining();
+ if (remaining < numBytes) {
+ fillBuffer(numBytes - remaining);
+ }
+ long address = buf.getUnsafeReaderAddress();
+ Platform.copyMemory(null, address, target, targetPointer, numBytes);
+ buf.increaseReaderIndex(numBytes);
+ }
+
+ @Override
+ public void readToByteBuffer(ByteBuffer dst, int length) {
+ MemoryBuffer buf = memoryBuffer;
+ int remaining = buf.remaining();
+ if (remaining >= length) {
+ buf.read(dst, length);
+ } else {
+ buf.read(dst, remaining);
+ try {
+ int dstLimit = dst.limit();
+ int newLimit = dst.position() + length - remaining;
+ if (dstLimit > newLimit) {
+ dst.limit(newLimit);
+ channel.read(dst);
+ dst.limit(dstLimit);
+ } else {
+ channel.read(dst);
+ }
+ } catch (IOException e) {
+ throw new DeserializationException("Failed to read the provided byte
channel", e);
+ }
+ }
+ }
+
+ @Override
+ public int readToByteBuffer(ByteBuffer dst) {
+ MemoryBuffer buf = memoryBuffer;
+ int remaining = buf.remaining();
+ if (remaining > 0) {
+ buf.read(dst, remaining);
+ }
+ try {
+ return channel.read(dst) + remaining;
+ } catch (IOException e) {
+ throw new DeserializationException("Failed to read the provided byte
channel", e);
+ }
+ }
+
@Override
public boolean isOpen() {
return channel.isOpen();
@@ -70,6 +164,6 @@ public class FuryReadableChannel extends
AbstractStreamReader implements Readabl
@Override
public MemoryBuffer getBuffer() {
- return buffer;
+ return memoryBuffer;
}
}
diff --git
a/java/fury-core/src/main/java/org/apache/fury/io/FuryStreamReader.java
b/java/fury-core/src/main/java/org/apache/fury/io/FuryStreamReader.java
index a40a4b80..e02d4efb 100644
--- a/java/fury-core/src/main/java/org/apache/fury/io/FuryStreamReader.java
+++ b/java/fury-core/src/main/java/org/apache/fury/io/FuryStreamReader.java
@@ -21,10 +21,13 @@ package org.apache.fury.io;
import java.io.InputStream;
import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
import org.apache.fury.memory.MemoryBuffer;
/** A streaming reader to make {@link MemoryBuffer} to support streaming
reading. */
public interface FuryStreamReader {
+ int BUFFER_GROW_STEP_THRESHOLD = 100 * 1024 * 1024;
+
/**
* Read stream and fill the data to underlying {@link MemoryBuffer}, which
is also the buffer
* returned by {@link #getBuffer}.
@@ -63,4 +66,14 @@ public interface FuryStreamReader {
static FuryInputStream of(InputStream stream) {
return new FuryInputStream(stream);
}
+
+ /**
+ * Create a {@link FuryReadableChannel} from the provided {@link
SeekableByteChannel}. Note that
+ * the provided channel will be owned by the returned {@link
FuryReadableChannel}, <bold>do
+ * not</bold> read the provided {@link SeekableByteChannel} anymore, read
the returned stream
+ * instead.
+ */
+ static FuryReadableChannel of(SeekableByteChannel channel) {
+ return new FuryReadableChannel(channel);
+ }
}
diff --git
a/java/fury-core/src/main/java/org/apache/fury/memory/MemoryBuffer.java
b/java/fury-core/src/main/java/org/apache/fury/memory/MemoryBuffer.java
index cd4b10dc..0bf0882b 100644
--- a/java/fury-core/src/main/java/org/apache/fury/memory/MemoryBuffer.java
+++ b/java/fury-core/src/main/java/org/apache/fury/memory/MemoryBuffer.java
@@ -158,6 +158,15 @@ public final class MemoryBuffer {
*/
private MemoryBuffer(
long offHeapAddress, int size, ByteBuffer offHeapBuffer,
FuryStreamReader streamReader) {
+ initDirectBuffer(offHeapAddress, size, offHeapBuffer);
+ if (streamReader != null) {
+ this.streamReader = streamReader;
+ } else {
+ this.streamReader = new BoundChecker();
+ }
+ }
+
+ public void initDirectBuffer(long offHeapAddress, int size, ByteBuffer
offHeapBuffer) {
this.offHeapBuffer = offHeapBuffer;
if (offHeapAddress <= 0) {
throw new IllegalArgumentException("negative pointer or size");
@@ -175,11 +184,6 @@ public final class MemoryBuffer {
this.address = offHeapAddress;
this.addressLimit = this.address + size;
this.size = size;
- if (streamReader != null) {
- this.streamReader = streamReader;
- } else {
- this.streamReader = new BoundChecker();
- }
}
private class BoundChecker extends AbstractStreamReader {
@@ -2915,6 +2919,12 @@ public final class MemoryBuffer {
}
}
+ public static MemoryBuffer fromDirectByteBuffer(
+ ByteBuffer buffer, int size, FuryStreamReader streamReader) {
+ long offHeapAddress = Platform.getAddress(buffer) + buffer.position();
+ return new MemoryBuffer(offHeapAddress, size, buffer, streamReader);
+ }
+
/**
* Creates a new memory buffer that represents the provided native memory.
The buffer will change
* into a heap buffer automatically if not enough.
diff --git
a/java/fury-core/src/main/java/org/apache/fury/pool/ThreadPoolFury.java
b/java/fury-core/src/main/java/org/apache/fury/pool/ThreadPoolFury.java
index 3c207a6a..e9367dcf 100644
--- a/java/fury-core/src/main/java/org/apache/fury/pool/ThreadPoolFury.java
+++ b/java/fury-core/src/main/java/org/apache/fury/pool/ThreadPoolFury.java
@@ -28,6 +28,7 @@ import javax.annotation.concurrent.ThreadSafe;
import org.apache.fury.AbstractThreadSafeFury;
import org.apache.fury.Fury;
import org.apache.fury.io.FuryInputStream;
+import org.apache.fury.io.FuryReadableChannel;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.memory.MemoryUtils;
import org.apache.fury.serializer.BufferCallback;
@@ -153,6 +154,16 @@ public class ThreadPoolFury extends AbstractThreadSafeFury
{
return execute(fury -> fury.deserialize(inputStream, outOfBandBuffers));
}
+ @Override
+ public Object deserialize(FuryReadableChannel channel) {
+ return execute(fury -> fury.deserialize(channel));
+ }
+
+ @Override
+ public Object deserialize(FuryReadableChannel channel,
Iterable<MemoryBuffer> outOfBandBuffers) {
+ return execute(fury -> fury.deserialize(channel, outOfBandBuffers));
+ }
+
@Override
public byte[] serializeJavaObject(Object obj) {
return execute(fury -> fury.serializeJavaObject(obj));
@@ -191,6 +202,11 @@ public class ThreadPoolFury extends AbstractThreadSafeFury
{
return execute(fury -> fury.deserializeJavaObject(inputStream, cls));
}
+ @Override
+ public <T> T deserializeJavaObject(FuryReadableChannel channel, Class<T>
cls) {
+ return execute(fury -> fury.deserializeJavaObject(channel, cls));
+ }
+
@Override
public byte[] serializeJavaObjectAndClass(Object obj) {
return execute(fury -> fury.serializeJavaObjectAndClass(obj));
@@ -229,6 +245,11 @@ public class ThreadPoolFury extends AbstractThreadSafeFury
{
return execute(fury -> fury.deserializeJavaObjectAndClass(inputStream));
}
+ @Override
+ public Object deserializeJavaObjectAndClass(FuryReadableChannel channel) {
+ return execute(fury -> fury.deserializeJavaObjectAndClass(channel));
+ }
+
@Override
public void setClassLoader(ClassLoader classLoader) {
setClassLoader(classLoader, LoaderBinding.StagingType.SOFT_STAGING);
diff --git a/java/fury-core/src/test/java/org/apache/fury/StreamTest.java
b/java/fury-core/src/test/java/org/apache/fury/StreamTest.java
index 9d69bdc6..400e83a1 100644
--- a/java/fury-core/src/test/java/org/apache/fury/StreamTest.java
+++ b/java/fury-core/src/test/java/org/apache/fury/StreamTest.java
@@ -28,7 +28,10 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import org.apache.fury.io.FuryInputStream;
+import org.apache.fury.io.FuryReadableChannel;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.test.bean.BeanA;
import org.apache.fury.util.ReflectionUtils;
@@ -204,4 +207,52 @@ public class StreamTest {
assertEquals(newObj, beanA);
}
}
+
+ @Test
+ public void testReadableChannel() throws IOException {
+ Fury fury = Fury.builder().requireClassRegistration(false).build();
+ BeanA beanA = BeanA.createBeanA(2);
+ {
+ ByteArrayOutputStream bas = new ByteArrayOutputStream();
+ fury.serialize(bas, beanA);
+
+ Path tempFile = Files.createTempFile("readable_channel_test", "data_1");
+ Files.write(tempFile, bas.toByteArray());
+
+ try (FuryReadableChannel channel = of(Files.newByteChannel(tempFile))) {
+ Object newObj = fury.deserialize(channel);
+ assertEquals(newObj, beanA);
+ } finally {
+ Files.delete(tempFile);
+ }
+ }
+ {
+ ByteArrayOutputStream bas = new ByteArrayOutputStream();
+ fury.serializeJavaObject(bas, beanA);
+
+ Path tempFile = Files.createTempFile("readable_channel_test", "data_2");
+ Files.write(tempFile, bas.toByteArray());
+
+ try (FuryReadableChannel channel = of(Files.newByteChannel(tempFile))) {
+ Object newObj = fury.deserializeJavaObject(channel, BeanA.class);
+ assertEquals(newObj, beanA);
+ } finally {
+ Files.delete(tempFile);
+ }
+ }
+ {
+ ByteArrayOutputStream bas = new ByteArrayOutputStream();
+ fury.serializeJavaObjectAndClass(bas, beanA);
+
+ Path tempFile = Files.createTempFile("readable_channel_test", "data_3");
+ Files.write(tempFile, bas.toByteArray());
+
+ try (FuryReadableChannel channel = of(Files.newByteChannel(tempFile))) {
+ Object newObj = fury.deserializeJavaObjectAndClass(channel);
+ assertEquals(newObj, beanA);
+ } finally {
+ Files.delete(tempFile);
+ }
+ }
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]