Use ArrowBuf instead of byte[] for Flight metadata

This commit is contained in:
David Li committed 2019-06-26 09:25:14 -04:00
1 parent ccfef2d1e7
commit 1225b67af3
16 files changed
+312 -222

No files matched your search

@@ -53,7 +53,6 @@ import com.google.protobuf.WireFormat;
import io.grpc.Drainable;
import io.grpc.MethodDescriptor;
import io.grpc.MethodDescriptor.Marshaller;
import io.grpc.internal.ReadableBuffer;
import io.grpc.protobuf.ProtoUtils;
import io.netty.buffer.ArrowBuf;
@@ -80,8 +79,8 @@ class ArrowMessage implements AutoCloseable {
private static Marshaller<FlightData> NO_BODY_MARSHALLER = ProtoUtils.marshaller(FlightData.getDefaultInstance());
/** Get the application-specific metadata in this message. */
public byte[] getApplicationMetadata() {
/** Get the application-specific metadata in this message. The ArrowMessage retains ownership of the buffer. */
public ArrowBuf getApplicationMetadata() {
return appMetadata;
}
@@ -122,7 +121,7 @@ class ArrowMessage implements AutoCloseable {
private final FlightDescriptor descriptor;
private final Message message;
private final byte[] appMetadata;
private final ArrowBuf appMetadata;
private final List<ArrowBuf> bufs;
public ArrowMessage(FlightDescriptor descriptor, Schema schema) {
@@ -136,7 +135,12 @@ class ArrowMessage implements AutoCloseable {
this.appMetadata = null;
}
public ArrowMessage(ArrowRecordBatch batch, byte[] appMetadata) {
/**
* Create an ArrowMessage from a record batch and app metadata.
* @param batch The record batch.
* @param appMetadata The app metadata. May be null. Takes ownership of the buffer otherwise.
*/
public ArrowMessage(ArrowRecordBatch batch, ArrowBuf appMetadata) {
FlatBufferBuilder builder = new FlatBufferBuilder();
int batchOffset = batch.writeTo(builder);
ByteBuffer serializedMessage = MessageSerializer.serializeMessage(builder, MessageHeader.RecordBatch, batchOffset,
@@ -163,7 +167,7 @@ class ArrowMessage implements AutoCloseable {
this.appMetadata = null;
}
private ArrowMessage(FlightDescriptor descriptor, Message message, byte[] appMetadata, ArrowBuf buf) {
private ArrowMessage(FlightDescriptor descriptor, Message message, ArrowBuf appMetadata, ArrowBuf buf) {
this.message = message;
this.descriptor = descriptor;
this.appMetadata = appMetadata;
@@ -196,6 +200,7 @@ class ArrowMessage implements AutoCloseable {
RecordBatch recordBatch = new RecordBatch();
message.header(recordBatch);
ArrowBuf underlying = bufs.get(0);
underlying.getReferenceManager().retain();
ArrowRecordBatch batch = MessageSerializer.deserializeRecordBatch(recordBatch, underlying);
return batch;
}
@@ -217,7 +222,7 @@ class ArrowMessage implements AutoCloseable {
FlightDescriptor descriptor = null;
Message header = null;
ArrowBuf body = null;
byte[] appMetadata = null;
ArrowBuf appMetadata = null;
while (stream.available() > 0) {
int tag = readRawVarint32(stream);
switch (tag) {
@@ -238,8 +243,8 @@ class ArrowMessage implements AutoCloseable {
}
case APP_METADATA_TAG: {
int size = readRawVarint32(stream);
appMetadata = new byte[size];
ByteStreams.readFully(stream, appMetadata);
appMetadata = allocator.buffer(size);
GetReadableBuffer.readIntoBuffer(stream, appMetadata, size, FAST_PATH);
break;
}
case BODY_TAG:
@@ -250,15 +255,7 @@ class ArrowMessage implements AutoCloseable {
}
int size = readRawVarint32(stream);
body = allocator.buffer(size);
ReadableBuffer readableBuffer = FAST_PATH ? GetReadableBuffer.getReadableBuffer(stream) : null;
if (readableBuffer != null) {
readableBuffer.readBytes(body.nioBuffer(0, size));
} else {
byte[] heapBytes = new byte[size];
ByteStreams.readFully(stream, heapBytes);
body.writeBytes(heapBytes);
}
body.writerIndex(size);
GetReadableBuffer.readIntoBuffer(stream, body, size, FAST_PATH);
break;
default:
@@ -309,8 +306,10 @@ class ArrowMessage implements AutoCloseable {
CodedOutputStream cos = CodedOutputStream.newInstance(baos);
cos.writeBytes(FlightData.DATA_HEADER_FIELD_NUMBER, bytes);
if (appMetadata != null && appMetadata.length > 0) {
cos.writeByteArray(FlightData.APP_METADATA_FIELD_NUMBER, appMetadata);
if (appMetadata != null && appMetadata.capacity() > 0) {
cos.writeByteBuffer(FlightData.APP_METADATA_FIELD_NUMBER, appMetadata.asNettyBuffer().nioBuffer());
// This is weird, but implicitly, writing an ArrowMessage frees any references it has
appMetadata.getReferenceManager().release();
}
cos.writeTag(FlightData.DATA_BODY_FIELD_NUMBER, WireFormat.WIRETYPE_LENGTH_DELIMITED);
@@ -335,6 +334,7 @@ class ArrowMessage implements AutoCloseable {
initialBuf.writeBytes(baos.toByteArray());
final CompositeByteBuf bb = new CompositeByteBuf(allocator.getAsByteBufAllocator(), true, bufs.size() + 1,
ImmutableList.<ByteBuf>builder().add(initialBuf.asNettyBuffer()).addAll(allBufs).build());
// Implicitly, transfer ownership of our buffers to the input stream (which will decrement the refcount when done)
final ByteBufInputStream is = new DrainableByteBufInputStream(bb);
return is;
} catch (Exception ex) {
@@ -364,7 +364,7 @@ class ArrowMessage implements AutoCloseable {
}
@Override
public void close() throws IOException {
public void close() {
buf.release();
}
@@ -399,5 +399,8 @@ class ArrowMessage implements AutoCloseable {
@Override
public void close() throws Exception {
AutoCloseables.close(bufs);
if (appMetadata != null) {
appMetadata.close();
}
}
}
@@ -19,12 +19,7 @@ package org.apache.arrow.flight;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -42,17 +37,11 @@ import org.apache.arrow.flight.impl.FlightServiceGrpc;
import org.apache.arrow.flight.impl.FlightServiceGrpc.FlightServiceBlockingStub;
import org.apache.arrow.flight.impl.FlightServiceGrpc.FlightServiceStub;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.vector.FieldVector;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.VectorUnloader;
import org.apache.arrow.vector.dictionary.Dictionary;
import org.apache.arrow.vector.dictionary.DictionaryProvider;
import org.apache.arrow.vector.dictionary.DictionaryProvider.MapDictionaryProvider;
import org.apache.arrow.vector.ipc.message.ArrowDictionaryBatch;
import org.apache.arrow.vector.ipc.message.ArrowRecordBatch;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.Schema;
import org.apache.arrow.vector.util.DictionaryUtility;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
@@ -69,6 +58,7 @@ import io.grpc.stub.ClientCalls;
import io.grpc.stub.ClientResponseObserver;
import io.grpc.stub.StreamObserver;
import io.netty.buffer.ArrowBuf;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.handler.ssl.SslContextBuilder;
@@ -169,9 +159,11 @@ public class FlightClient implements AutoCloseable {
/**
* Create or append a descriptor with another stream.
*
* @param descriptor FlightDescriptor the descriptor for the data
* @param root VectorSchemaRoot the root containing data
* @param metadataListener A handler for metadata messages from the server.
* @param metadataListener A handler for metadata messages from the server. This will be passed buffers that will be
* freed after {@link StreamListener#onNext(Object)} is called!
* @param options RPC-layer hints for this call.
* @return ClientStreamListener an interface to control uploading data
*/
@@ -193,7 +185,7 @@ public class FlightClient implements AutoCloseable {
Preconditions.checkNotNull(descriptor);
Preconditions.checkNotNull(root);
SetStreamObserver resultObserver = new SetStreamObserver(metadataListener);
SetStreamObserver resultObserver = new SetStreamObserver(allocator, metadataListener);
final io.grpc.CallOptions callOptions = CallOptions.wrapStub(asyncStub, options).getCallOptions();
ClientCallStreamObserver<ArrowMessage> observer = (ClientCallStreamObserver<ArrowMessage>)
ClientCalls.asyncBidiStreamingCall(
@@ -268,33 +260,32 @@ public class FlightClient implements AutoCloseable {
private static class SetStreamObserver implements StreamObserver<Flight.PutResult> {
private final CompletableFuture<Void> result;
private final BufferAllocator allocator;
private final StreamListener<PutResult> listener;
SetStreamObserver(FlightProducer.StreamListener<PutResult> listener) {
this.listener = listener;
SetStreamObserver(BufferAllocator allocator,
StreamListener<PutResult> listener) {
this.allocator = allocator;
this.listener = listener == null ? NoOpStreamListener.getInstance() : listener;
result = new CompletableFuture<>();
}
@Override
public void onNext(Flight.PutResult value) {
if (listener != null) {
listener.onNext(PutResult.fromProtocol(value));
try (final PutResult message = PutResult.fromProtocol(allocator, value)) {
listener.onNext(message);
}
}
@Override
public void onError(Throwable t) {
result.completeExceptionally(t);
if (listener != null) {
listener.onError(t);
}
listener.onError(t);
}
@Override
public void onCompleted() {
if (listener != null) {
listener.onCompleted();
}
listener.onCompleted();
result.complete(null);
}
@@ -322,12 +313,13 @@ public class FlightClient implements AutoCloseable {
}
@Override
public void putNext(byte[] appMetadata) {
public void putNext(ArrowBuf appMetadata) {
ArrowRecordBatch batch = unloader.getRecordBatch();
// Check the futureResult in case server sent an exception
while (!observer.isReady() && !futureResult.isDone()) {
/* busy wait */
}
// Takes ownership of appMetadata
observer.onNext(new ArrowMessage(batch, appMetadata));
}
@@ -361,7 +353,11 @@ public class FlightClient implements AutoCloseable {
*/
void putNext();
void putNext(byte[] appMetadata);
/**
* Send the current data in the corresponding {@link VectorSchemaRoot} to the server, along with
* application-specific metadata. This takes ownership of the buffer.
*/
void putNext(ArrowBuf appMetadata);
/**
* Indicate an error to the server. Terminates the stream; do not call {@link #completed()}.
@@ -376,7 +372,6 @@ public class FlightClient implements AutoCloseable {
* happened during the upload.
*/
void getResult();
}
/**
@@ -20,6 +20,8 @@ package org.apache.arrow.flight;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.dictionary.DictionaryProvider;
import io.netty.buffer.ArrowBuf;
/**
* API to Implement an Arrow Flight producer.
*/
@@ -59,7 +61,7 @@ public interface FlightProducer {
* @param context Per-call context.
* @param flightStream The data stream being uploaded.
*/
public Runnable acceptPut(CallContext context,
Runnable acceptPut(CallContext context,
FlightStream flightStream, StreamListener<PutResult> ackStream);
/**
@@ -82,7 +84,7 @@ public interface FlightProducer {
/**
* An interface for sending Arrow data back to a client.
*/
public interface ServerStreamListener {
interface ServerStreamListener {
/**
* Check whether the call has been cancelled. If so, stop sending data.
@@ -115,8 +117,9 @@ public interface FlightProducer {
/**
* Send the current contents of the associated {@link VectorSchemaRoot} alongside application-defined metadata.
* @param metadata The metadata to send. Ownership of the buffer is transferred to the Flight implementation.
*/
void putNext(byte[] metadata);
void putNext(ArrowBuf metadata);
/**
* Indicate an error to the client. Terminates the stream; do not call {@link #completed()} afterwards.
@@ -17,11 +17,6 @@
package org.apache.arrow.flight;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BooleanSupplier;
@@ -37,17 +32,10 @@ import org.apache.arrow.flight.impl.Flight.HandshakeRequest;
import org.apache.arrow.flight.impl.Flight.HandshakeResponse;
import org.apache.arrow.flight.impl.FlightServiceGrpc.FlightServiceImplBase;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.vector.FieldVector;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.VectorUnloader;
import org.apache.arrow.vector.dictionary.Dictionary;
import org.apache.arrow.vector.dictionary.DictionaryProvider;
import org.apache.arrow.vector.dictionary.DictionaryProvider.MapDictionaryProvider;
import org.apache.arrow.vector.ipc.message.ArrowDictionaryBatch;
import org.apache.arrow.vector.ipc.message.ArrowRecordBatch;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.Schema;
import org.apache.arrow.vector.util.DictionaryUtility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -55,6 +43,7 @@ import com.google.common.base.Preconditions;
import io.grpc.stub.ServerCallStreamObserver;
import io.grpc.stub.StreamObserver;
import io.netty.buffer.ArrowBuf;
/**
* GRPC service implementation for a flight server.
@@ -167,7 +156,7 @@ class FlightService extends FlightServiceImplBase {
}
@Override
public void putNext(byte[] metadata) {
public void putNext(ArrowBuf metadata) {
Preconditions.checkNotNull(unloader);
responseObserver.onNext(new ArrowMessage(unloader.getRecordBatch(), metadata));
}
@@ -46,6 +46,7 @@ import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.SettableFuture;
import io.grpc.stub.StreamObserver;
import io.netty.buffer.ArrowBuf;
/**
* An adaptor between protobuf streams and flight data streams.
@@ -72,7 +73,7 @@ public class FlightStream implements AutoCloseable {
private volatile Throwable ex;
private volatile FlightDescriptor descriptor;
private volatile Schema schema;
private volatile byte[] applicationMetadata = null;
private volatile ArrowBuf applicationMetadata = null;
/**
* Constructs a new instance.
@@ -117,6 +118,9 @@ public class FlightStream implements AutoCloseable {
.collect(Collectors.toList());
AutoCloseables.close(Iterables.concat(closeables, ImmutableList.of(root.get())));
if (applicationMetadata != null) {
applicationMetadata.close();
}
}
/**
@@ -149,31 +153,38 @@ public class FlightStream implements AutoCloseable {
throw new Exception(ex);
}
} else {
ArrowMessage msg = ((ArrowMessage) data);
if (msg.getMessageType() == HeaderType.RECORD_BATCH) {
try (ArrowRecordBatch arb = msg.asRecordBatch()) {
loader.load(arb);
}
this.applicationMetadata = msg.getApplicationMetadata();
} else if (msg.getMessageType() == HeaderType.DICTIONARY_BATCH) {
try (ArrowDictionaryBatch arb = msg.asDictionaryBatch()) {
final long id = arb.getDictionaryId();
final Dictionary dictionary = dictionaries.lookup(id);
if (dictionary == null) {
throw new IllegalArgumentException("Dictionary not defined in schema: ID " + id);
try (ArrowMessage msg = ((ArrowMessage) data)) {
if (msg.getMessageType() == HeaderType.RECORD_BATCH) {
try (ArrowRecordBatch arb = msg.asRecordBatch()) {
loader.load(arb);
}
if (this.applicationMetadata != null) {
this.applicationMetadata.close();
}
this.applicationMetadata = msg.getApplicationMetadata();
if (this.applicationMetadata != null) {
this.applicationMetadata.getReferenceManager().retain();
}
} else if (msg.getMessageType() == HeaderType.DICTIONARY_BATCH) {
try (ArrowDictionaryBatch arb = msg.asDictionaryBatch()) {
final long id = arb.getDictionaryId();
final Dictionary dictionary = dictionaries.lookup(id);
if (dictionary == null) {
throw new IllegalArgumentException("Dictionary not defined in schema: ID " + id);
}
final FieldVector vector = dictionary.getVector();
final VectorSchemaRoot dictionaryRoot = new VectorSchemaRoot(Collections.singletonList(vector.getField()),
Collections.singletonList(vector), 0);
final VectorLoader dictionaryLoader = new VectorLoader(dictionaryRoot);
dictionaryLoader.load(arb.getDictionary());
final FieldVector vector = dictionary.getVector();
final VectorSchemaRoot dictionaryRoot = new VectorSchemaRoot(Collections.singletonList(vector.getField()),
Collections.singletonList(vector), 0);
final VectorLoader dictionaryLoader = new VectorLoader(dictionaryRoot);
dictionaryLoader.load(arb.getDictionary());
}
return next();
} else {
throw new UnsupportedOperationException("Message type is unsupported: " + msg.getMessageType());
}
return next();
} else {
throw new UnsupportedOperationException("Message type is unsupported: " + msg.getMessageType());
return true;
}
return true;
}
} catch (Exception e) {
throw new RuntimeException(e);
@@ -191,11 +202,12 @@ public class FlightStream implements AutoCloseable {
/**
* Get the most recent metadata sent from the server. This may be cleared by calls to {@link #next()} if the server
* sends a message without metadata.
* sends a message without metadata. This does NOT take ownership of the buffer - call retain() to create a reference
* if you need the buffer after a call to {@link #next()}.
*
* @return the application metadata. May be null.
*/
public byte[] getLatestMetadata() {
public ArrowBuf getLatestMetadata() {
return applicationMetadata;
}
@@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.arrow.flight;
import org.apache.arrow.flight.FlightProducer.StreamListener;
/**
* A {@link StreamListener} that does nothing for all callbacks.
* @param <T> The type of the callback object.
*/
public class NoOpStreamListener<T> implements StreamListener<T> {
private static NoOpStreamListener INSTANCE = new NoOpStreamListener();
/** Ignores the value received. */
@Override
public void onNext(T val) {
}
/** Ignores the error received. */
@Override
public void onError(Throwable t) {
}
/** Ignores the stream completion event. */
@Override
public void onCompleted() {
}
@SuppressWarnings("unchecked")
public static <T> StreamListener<T> getInstance() {
// Safe because we never use T
return (StreamListener<T>) INSTANCE;
}
}
@@ -17,29 +17,32 @@
package org.apache.arrow.flight;
import java.nio.ByteBuffer;
import org.apache.arrow.flight.impl.Flight;
import org.apache.arrow.memory.BufferAllocator;
import com.google.protobuf.ByteString;
import io.netty.buffer.ArrowBuf;
/**
* A message from the server during a DoPut operation.
*
* <p>This object owns an {@link ArrowBuf} and should be closed when you are done with it.
*/
public class PutResult {
public class PutResult implements AutoCloseable {
private ByteBuffer applicationMetadata;
private ArrowBuf applicationMetadata;
private PutResult(ByteBuffer metadata) {
private PutResult(ArrowBuf metadata) {
applicationMetadata = metadata;
}
/** Create a PutResult with application-specific metadata. */
public static PutResult metadata(byte[] metadata) {
public static PutResult metadata(ArrowBuf metadata) {
if (metadata == null) {
return empty();
}
return new PutResult(ByteBuffer.wrap(metadata));
return new PutResult(metadata);
}
/** Create an empty PutResult. */
@@ -48,7 +51,7 @@ public class PutResult {
}
/** Get the metadata in this message. May be null. */
public ByteBuffer getApplicationMetadata() {
public ArrowBuf getApplicationMetadata() {
return applicationMetadata;
}
@@ -56,10 +59,29 @@ public class PutResult {
if (applicationMetadata == null) {
return Flight.PutResult.getDefaultInstance();
}
return Flight.PutResult.newBuilder().setAppMetadata(ByteString.copyFrom(applicationMetadata)).build();
return Flight.PutResult.newBuilder().setAppMetadata(ByteString.copyFrom(applicationMetadata.nioBuffer())).build();
}
static PutResult fromProtocol(Flight.PutResult message) {
return new PutResult(message.getAppMetadata().asReadOnlyByteBuffer());
/**
* Construct a PutResult from a Protobuf message.
*
* @param allocator The allocator to use for allocating application metadata memory. The result object owns the
* allocated buffer, if any.
* @param message The gRPC/Protobuf message.
*/
static PutResult fromProtocol(BufferAllocator allocator, Flight.PutResult message) {
final ArrowBuf buf = allocator.buffer(message.getAppMetadata().size());
message.getAppMetadata().asReadOnlyByteBufferList().forEach(bb -> {
buf.setBytes(buf.writerIndex(), bb);
buf.writerIndex(buf.writerIndex() + bb.limit());
});
return new PutResult(buf);
}
@Override
public void close() {
if (applicationMetadata != null) {
applicationMetadata.close();
}
}
}
@@ -36,6 +36,8 @@ import org.apache.arrow.vector.types.pojo.Schema;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import io.netty.buffer.ArrowBuf;
/**
* A collection of Arrow record batches.
*/
@@ -91,8 +93,12 @@ public class Stream implements AutoCloseable, Iterable<ArrowRecordBatch> {
final VectorLoader loader = new VectorLoader(root);
int counter = 0;
for (ArrowRecordBatch batch : batches) {
final byte[] rawMetadata = Integer.toString(counter).getBytes(StandardCharsets.UTF_8);
final ArrowBuf metadata = allocator.buffer(rawMetadata.length);
metadata.writeBytes(rawMetadata);
loader.load(batch);
listener.putNext(Integer.toString(counter).getBytes(StandardCharsets.UTF_8));
// Transfers ownership of the buffer - do not free buffer ourselves
listener.putNext(metadata);
counter++;
}
listener.completed();
@@ -22,6 +22,10 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.arrow.flight.FlightClient;
import org.apache.arrow.flight.FlightDescriptor;
@@ -44,6 +48,8 @@ import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import io.netty.buffer.ArrowBuf;
/**
* An Example Flight Server that provides access to the InMemoryStore.
*/
@@ -92,17 +98,19 @@ class IntegrationTestClient {
FlightDescriptor descriptor = FlightDescriptor.path(inputPath);
VectorSchemaRoot jsonRoot;
try (JsonFileReader reader = new JsonFileReader(new File(inputPath), allocator);
VectorSchemaRoot root = VectorSchemaRoot.create(reader.start(), allocator)) {
VectorSchemaRoot root = VectorSchemaRoot.create(reader.start(), allocator)) {
jsonRoot = VectorSchemaRoot.create(root.getSchema(), allocator);
VectorUnloader unloader = new VectorUnloader(root);
VectorLoader jsonLoader = new VectorLoader(jsonRoot);
final CompletableFuture<Void> completed = new CompletableFuture<>();
FlightClient.ClientStreamListener stream = client.startPut(descriptor, root, reader,
new StreamListener<PutResult>() {
int counter = 0;
@Override
public void onNext(PutResult val) {
final String metadata = StandardCharsets.UTF_8.decode(val.getApplicationMetadata()).toString();
final String metadata = StandardCharsets.UTF_8.decode(val.getApplicationMetadata().nioBuffer())
.toString();
if (!Integer.toString(counter).equals(metadata)) {
throw new RuntimeException(
String.format("Invalid ACK from server. Expected '%d' but got '%s'.", counter, metadata));
@@ -112,15 +120,20 @@ class IntegrationTestClient {
@Override
public void onError(Throwable t) {
completed.completeExceptionally(t);
}
@Override
public void onCompleted() {
completed.complete(null);
}
});
int counter = 0;
while (reader.read(root)) {
stream.putNext(Integer.toString(counter).getBytes(StandardCharsets.UTF_8));
final ArrowBuf metadata = allocator.buffer(0);
metadata.writeBytes(Integer.toString(counter).getBytes(StandardCharsets.UTF_8));
// Transfers ownership of the buffer, so do not release it ourselves
stream.putNext(metadata);
jsonLoader.load(unloader.getRecordBatch());
root.clear();
counter++;
@@ -128,6 +141,9 @@ class IntegrationTestClient {
stream.completed();
// Need to call this, or exceptions from the server get swallowed
stream.getResult();
completed.get(30, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new RuntimeException(e);
}
// 2. Get the ticket for the data.
@@ -17,12 +17,15 @@
package org.apache.arrow.flight.grpc;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import com.google.common.base.Throwables;
import com.google.common.io.ByteStreams;
import io.grpc.internal.ReadableBuffer;
import io.netty.buffer.ArrowBuf;
/**
* Enable access to ReadableBuffer directly to copy data from an BufferInputStream into a target
@@ -72,4 +75,24 @@ public class GetReadableBuffer {
}
}
/**
* Helper method to read a gRPC-provided InputStream into an ArrowBuf.
* @param stream The stream to read from. Should be an instance of {@link #BUFFER_INPUT_STREAM}.
* @param buf The buffer to read into.
* @param size The number of bytes to read.
* @param fastPath Whether to enable the fast path (i.e. detect whether the stream is a {@link #BUFFER_INPUT_STREAM}).
* @throws IOException if there is an error reading form the stream
*/
public static void readIntoBuffer(final InputStream stream, final ArrowBuf buf, final int size,
final boolean fastPath) throws IOException {
ReadableBuffer readableBuffer = fastPath ? getReadableBuffer(stream) : null;
if (readableBuffer != null) {
readableBuffer.readBytes(buf.nioBuffer(0, size));
} else {
byte[] heapBytes = new byte[size];
ByteStreams.readFully(stream, heapBytes);
buf.writeBytes(heapBytes);
}
buf.writerIndex(size);
}
}
@@ -17,11 +17,9 @@
package org.apache.arrow.flight;
import java.util.Arrays;
import java.util.Collections;
import org.apache.arrow.flight.FlightProducer.StreamListener;
import org.apache.arrow.flight.auth.ServerAuthHandler;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.IntVector;
@@ -34,6 +32,9 @@ import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import io.grpc.Status;
import io.netty.buffer.ArrowBuf;
/**
* Tests for application-specific metadata support in Flight.
*/
@@ -57,7 +58,7 @@ public class TestApplicationMetadata {
final IntVector vector = (IntVector) stream.getRoot().getVector("a");
Assert.assertEquals(1, vector.getValueCount());
Assert.assertEquals(10, vector.get(0));
Assert.assertArrayEquals(new byte[]{i}, stream.getLatestMetadata());
Assert.assertEquals(i, stream.getLatestMetadata().getByte(0));
i++;
}
} catch (Exception e) {
@@ -85,7 +86,7 @@ public class TestApplicationMetadata {
@Override
public void onNext(PutResult val) {
Assert.assertNotNull(val);
Assert.assertEquals(counter, val.getApplicationMetadata().get(0));
Assert.assertEquals(counter, val.getApplicationMetadata().getByte(0));
counter++;
}
@@ -104,10 +105,12 @@ public class TestApplicationMetadata {
root.allocateNew();
for (byte i = 0; i < 10; i++) {
final IntVector vector = (IntVector) root.getVector("a");
final ArrowBuf metadata = a.buffer(1);
metadata.writeByte(i);
vector.set(0, 10);
vector.setValueCount(1);
root.setRowCount(1);
writer.putNext(new byte[]{i});
writer.putNext(metadata);
}
writer.completed();
// Must attempt to retrieve the result to get any server-side errors.
@@ -139,7 +142,9 @@ public class TestApplicationMetadata {
vector.set(0, 10);
vector.setValueCount(1);
root.setRowCount(1);
listener.putNext(new byte[]{i});
final ArrowBuf metadata = allocator.buffer(1);
metadata.writeByte(i);
listener.putNext(metadata);
}
listener.completed();
}
@@ -151,11 +156,14 @@ public class TestApplicationMetadata {
try (FlightStream flightStream = stream) {
byte current = 0;
while (flightStream.next()) {
byte[] metadata = flightStream.getLatestMetadata();
if (!Arrays.equals(new byte[]{current}, metadata)) {
throw new IllegalArgumentException("Metadata does not match expected value.");
final ArrowBuf metadata = flightStream.getLatestMetadata();
if (current != metadata.getByte(0)) {
ackStream.onError(Status.INVALID_ARGUMENT.withDescription(String
.format("Metadata does not match expected value; got %d but expected %d.", metadata.getByte(0),
current)).asRuntimeException());
return;
}
ackStream.onNext(PutResult.metadata(new byte[]{current}));
ackStream.onNext(PutResult.metadata(metadata));
current++;
}
if (current != 10) {
@@ -40,7 +40,6 @@ public class TestBackPressure {
/**
* Make sure that failing to consume one stream doesn't block other streams.
*/
@Ignore
@Test
public void ensureIndependentSteams() throws Exception {
try (
@@ -49,26 +48,24 @@ public class TestBackPressure {
(location) -> (new PerformanceTestServer(a, location)));
final FlightClient client = FlightClient.builder(a, server.getLocation()).build()
) {
FlightStream fs1 = client.getStream(client.getInfo(
try (FlightStream fs1 = client.getStream(client.getInfo(
TestPerf.getPerfFlightDescriptor(110L * BATCH_SIZE, BATCH_SIZE, 1))
.getEndpoints().get(0).getTicket());
consume(fs1, 10);
.getEndpoints().get(0).getTicket())) {
consume(fs1, 10);
// stop consuming fs1 but make sure we can consume a large amount of fs2.
FlightStream fs2 = client.getStream(client.getInfo(
TestPerf.getPerfFlightDescriptor(200L * BATCH_SIZE, BATCH_SIZE, 1))
.getEndpoints().get(0).getTicket());
consume(fs2, 100);
// stop consuming fs1 but make sure we can consume a large amount of fs2.
try (FlightStream fs2 = client.getStream(client.getInfo(
TestPerf.getPerfFlightDescriptor(200L * BATCH_SIZE, BATCH_SIZE, 1))
.getEndpoints().get(0).getTicket())) {
consume(fs2, 100);
consume(fs1, 100);
consume(fs2, 100);
consume(fs1);
consume(fs2);
fs1.close();
fs2.close();
consume(fs1, 100);
consume(fs2, 100);
consume(fs1);
consume(fs2);
}
}
}
}
@@ -92,27 +89,28 @@ public class TestBackPressure {
ServerStreamListener listener) {
int batches = 0;
final Schema pojoSchema = new Schema(ImmutableList.of(Field.nullable("a", MinorType.BIGINT.getType())));
VectorSchemaRoot root = VectorSchemaRoot.create(pojoSchema, allocator);
listener.start(root);
while (true) {
while (!listener.isReady()) {
try {
Thread.sleep(1);
sleepTime.addAndGet(1L);
} catch (InterruptedException ignore) {
try (VectorSchemaRoot root = VectorSchemaRoot.create(pojoSchema, allocator)) {
listener.start(root);
while (true) {
while (!listener.isReady()) {
try {
Thread.sleep(1);
sleepTime.addAndGet(1L);
} catch (InterruptedException ignore) {
}
}
}
if (batches > 100) {
root.clear();
listener.completed();
return;
}
if (batches > 100) {
root.clear();
listener.completed();
return;
}
root.allocateNew();
root.setRowCount(4095);
listener.putNext();
batches++;
root.allocateNew();
root.setRowCount(4095);
listener.putNext();
batches++;
}
}
}
};
@@ -127,9 +125,9 @@ public class TestBackPressure {
FlightClient client =
FlightClient
.builder(clientAllocator, server.getLocation())
.build()
.build();
FlightStream stream = client.getStream(new Ticket(new byte[1]))
) {
FlightStream stream = client.getStream(new Ticket(new byte[1]));
VectorSchemaRoot root = stream.getRoot();
root.clear();
Thread.sleep(wait);
@@ -97,19 +97,8 @@ public class TestBasicOperation {
IntVector iv = new IntVector("c1", a);
VectorSchemaRoot root = VectorSchemaRoot.of(iv);
ClientStreamListener listener = c.startPut(FlightDescriptor.path("hello"), root, new StreamListener<PutResult>() {
@Override
public void onNext(PutResult val) {
}
@Override
public void onError(Throwable t) {
}
@Override
public void onCompleted() {
}
});
ClientStreamListener listener = c
.startPut(FlightDescriptor.path("hello"), root, NoOpStreamListener.getInstance());
//batch 1
root.allocateNew();
@@ -21,7 +21,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.apache.arrow.flight.FlightProducer.StreamListener;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.IntVector;
@@ -78,22 +77,7 @@ public class TestLargeMessage {
BufferAllocator testAllocator = a.newChildAllocator("testcase", 0, Long.MAX_VALUE);
VectorSchemaRoot root = generateData(testAllocator)) {
final FlightClient.ClientStreamListener listener = client.startPut(FlightDescriptor.path("hello"), root,
new StreamListener<PutResult>() {
@Override
public void onNext(PutResult val) {
}
@Override
public void onError(Throwable t) {
}
@Override
public void onCompleted() {
}
});
NoOpStreamListener.getInstance());
listener.putNext();
listener.completed();
listener.getResult();
@@ -27,6 +27,7 @@ import org.apache.arrow.flight.FlightProducer.StreamListener;
import org.apache.arrow.flight.FlightStream;
import org.apache.arrow.flight.FlightTestUtil;
import org.apache.arrow.flight.Location;
import org.apache.arrow.flight.NoOpStreamListener;
import org.apache.arrow.flight.PutResult;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
@@ -40,7 +41,6 @@ import org.junit.Test;
/**
* Ensure that example server supports get and put.
*/
@org.junit.Ignore
public class TestExampleServer {
private BufferAllocator allocator;
@@ -78,19 +78,7 @@ public class TestExampleServer {
VectorSchemaRoot root = VectorSchemaRoot.of(iv);
ClientStreamListener listener = client.startPut(FlightDescriptor.path("hello"), root,
new StreamListener<PutResult>() {
@Override
public void onNext(PutResult val) {
}
@Override
public void onError(Throwable t) {
}
@Override
public void onCompleted() {
}
});
NoOpStreamListener.getInstance());
//batch 1
root.allocateNew();
@@ -117,10 +105,13 @@ public class TestExampleServer {
listener.getResult();
FlightInfo info = client.getInfo(FlightDescriptor.path("hello"));
FlightStream stream = client.getStream(info.getEndpoints().get(0).getTicket());
VectorSchemaRoot newRoot = stream.getRoot();
while (stream.next()) {
newRoot.clear();
try (final FlightStream stream = client.getStream(info.getEndpoints().get(0).getTicket())) {
VectorSchemaRoot newRoot = stream.getRoot();
while (stream.next()) {
newRoot.clear();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@@ -28,7 +28,6 @@ import org.apache.arrow.flight.FlightDescriptor;
import org.apache.arrow.flight.FlightInfo;
import org.apache.arrow.flight.FlightStream;
import org.apache.arrow.flight.FlightTestUtil;
import org.apache.arrow.flight.Location;
import org.apache.arrow.flight.Ticket;
import org.apache.arrow.flight.perf.impl.PerfOuterClass.Perf;
import org.apache.arrow.memory.BufferAllocator;
@@ -92,11 +91,13 @@ public class TestPerf {
.map(t -> pool.submit(t))
.collect(Collectors.toList());
Futures.whenAllSucceed(results);
Result r = new Result();
for (ListenableFuture<Result> f : results) {
r.add(f.get());
}
final Result r = Futures.whenAllSucceed(results).call(() -> {
Result res = new Result();
for (ListenableFuture<Result> f : results) {
res.add(f.get());
}
return res;
}).get();
double seconds = r.nanos * 1.0d / 1000 / 1000 / 1000;
System.out.println(String.format(
@@ -126,28 +127,29 @@ public class TestPerf {
public Result call() throws Exception {
final Result r = new Result();
Stopwatch watch = Stopwatch.createStarted();
FlightStream stream = client.getStream(ticket);
final VectorSchemaRoot root = stream.getRoot();
try {
BigIntVector a = (BigIntVector) root.getVector("a");
while (stream.next()) {
int rows = root.getRowCount();
long aSum = r.aSum;
for (int i = 0; i < rows; i++) {
if (VALIDATE) {
aSum += a.get(i);
try (final FlightStream stream = client.getStream(ticket)) {
final VectorSchemaRoot root = stream.getRoot();
try {
BigIntVector a = (BigIntVector) root.getVector("a");
while (stream.next()) {
int rows = root.getRowCount();
long aSum = r.aSum;
for (int i = 0; i < rows; i++) {
if (VALIDATE) {
aSum += a.get(i);
}
}
r.bytes += rows * 32;
r.rows += rows;
r.aSum = aSum;
r.batches++;
}
r.bytes += rows * 32;
r.rows += rows;
r.aSum = aSum;
r.batches++;
}
r.nanos = watch.elapsed(TimeUnit.NANOSECONDS);
return r;
} finally {
root.clear();
r.nanos = watch.elapsed(TimeUnit.NANOSECONDS);
return r;
} finally {
root.clear();
}
}
}