From 34e353fc8ade6070aae2f5949477ebea22598ffe Mon Sep 17 00:00:00 2001 From: Nate Bauernfeind Date: Fri, 10 Mar 2023 14:05:47 -0700 Subject: [PATCH] Move rpcWrapper to gRPC Interceptor; Open Session's ExecutionContext (#3511) --- .../barrage/util/ArrowToTableConverter.java | 5 +- .../extensions/barrage/util/BarrageUtil.java | 11 +- .../extensions/barrage/util/GrpcUtil.java | 67 +-- .../server/appmode/ApplicationFactory.java | 4 +- .../appmode/ApplicationServiceGrpcImpl.java | 32 +- .../appmode/ApplicationTicketResolver.java | 29 +- .../server/arrow/ArrowFlightUtil.java | 264 +++++----- .../server/arrow/FlightServiceGrpcImpl.java | 150 +++--- .../browserstreaming/BrowserStream.java | 9 +- .../BrowserStreamInterceptor.java | 4 +- .../server/config/ConfigServiceGrpcImpl.java | 39 +- .../console/ConsoleServiceGrpcImpl.java | 300 ++++++----- .../server/console/ScopeTicketResolver.java | 18 +- .../completer/JavaAutoCompleteObserver.java | 7 +- .../completer/PythonAutoCompleteObserver.java | 8 +- .../java/io/deephaven/server/grpc/Common.java | 7 +- .../server/grpc/GrpcErrorHelper.java | 12 +- .../HierarchicalTableServiceGrpcImpl.java | 474 +++++++++--------- .../HierarchicalTableViewSubscription.java | 9 +- .../FilesystemStorageServiceGrpcImpl.java | 301 +++++------ .../server/object/ObjectServiceGrpcImpl.java | 53 +- .../PartitionedTableServiceGrpcImpl.java | 212 ++++---- .../server/session/ExportTicketResolver.java | 10 +- .../server/session/SessionModule.java | 2 +- .../session/SessionServiceGrpcImpl.java | 324 ++++++++---- .../server/session/SessionState.java | 36 +- .../server/session/TicketRouter.java | 12 +- .../table/ExportedTableUpdateListener.java | 4 +- .../InputTableServiceGrpcImpl.java | 188 +++---- .../server/table/ops/AggSpecAdapter.java | 20 +- .../server/table/ops/AggregationAdapter.java | 6 +- .../table/ops/ComboAggregateGrpcImpl.java | 23 +- .../table/ops/CreateInputTableGrpcImpl.java | 7 +- .../server/table/ops/EmptyTableGrpcImpl.java | 4 +- .../table/ops/HeadOrTailByGrpcImpl.java | 5 +- .../server/table/ops/HeadOrTailGrpcImpl.java | 5 +- .../server/table/ops/JoinTablesGrpcImpl.java | 11 +- .../server/table/ops/MergeTablesGrpcImpl.java | 4 +- .../table/ops/SelectDistinctGrpcImpl.java | 4 +- .../table/ops/SnapshotWhenTableGrpcImpl.java | 4 +- .../server/table/ops/SortTableGrpcImpl.java | 4 +- .../table/ops/TableServiceGrpcImpl.java | 466 +++++++++-------- .../server/table/ops/TimeTableGrpcImpl.java | 4 +- .../server/table/ops/UpdateByGrpcImpl.java | 4 +- .../util/GrpcServiceOverrideBuilder.java | 77 ++- 45 files changed, 1664 insertions(+), 1575 deletions(-) diff --git a/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/ArrowToTableConverter.java b/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/ArrowToTableConverter.java index a5c7b2c6b49..23c31351432 100644 --- a/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/ArrowToTableConverter.java +++ b/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/ArrowToTableConverter.java @@ -19,6 +19,7 @@ import io.deephaven.extensions.barrage.chunk.ChunkInputStreamGenerator; import io.deephaven.extensions.barrage.table.BarrageTable; import io.deephaven.io.streams.ByteBufferInputStream; +import io.deephaven.proto.util.Exceptions; import io.deephaven.util.datastructures.LongSizedDataStructure; import org.apache.arrow.flatbuf.Message; import org.apache.arrow.flatbuf.MessageHeader; @@ -157,7 +158,7 @@ private void signalCompletion(final Condition completedCondition) { protected void parseSchema(final Schema header) { if (resultTable != null) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Schema evolution not supported"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Schema evolution not supported"); } final BarrageUtil.ConvertedArrowSchema result = BarrageUtil.convertArrowSchema(header); @@ -218,7 +219,7 @@ protected BarrageMessage createBarrageMessage(BarrageProtoUtil.MessageInfo mi, i } if (acd.data.get(0).size() != numRowsAdded) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Inconsistent num records per column: " + numRowsAdded + " != " + acd.data.get(0).size()); } acd.type = columnTypes[ci]; diff --git a/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java b/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java index 44a81bd8883..1c77d069826 100755 --- a/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java +++ b/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java @@ -32,6 +32,7 @@ import io.deephaven.io.logger.Logger; import io.deephaven.proto.flight.util.MessageHelper; import io.deephaven.proto.flight.util.SchemaHelper; +import io.deephaven.proto.util.Exceptions; import io.deephaven.time.DateTime; import io.deephaven.api.util.NameValidator; import io.deephaven.engine.util.ColumnFormatting; @@ -293,7 +294,7 @@ private static Class getDefaultType(final ArrowType arrowType, final Converte return long.class; } } - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, exMsg + + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, exMsg + " of intType(signed=" + intType.getIsSigned() + ", bitWidth=" + intType.getBitWidth() + ")"); case Bool: return boolean.class; @@ -303,7 +304,7 @@ private static Class getDefaultType(final ArrowType arrowType, final Converte if (maybeConvertForTimeUnit(durationUnit, result, i)) { return long.class; } - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, exMsg + + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, exMsg + " of durationType(unit=" + durationUnit.toString() + ")"); case Timestamp: final ArrowType.Timestamp timestampType = (ArrowType.Timestamp) arrowType; @@ -314,7 +315,7 @@ private static Class getDefaultType(final ArrowType arrowType, final Converte return DateTime.class; } } - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, exMsg + + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, exMsg + " of timestampType(Timezone=" + tz + ", Unit=" + timestampUnit.toString() + ")"); case FloatingPoint: @@ -326,13 +327,13 @@ private static Class getDefaultType(final ArrowType arrowType, final Converte return double.class; case HALF: default: - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, exMsg + + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, exMsg + " of floatingPointType(Precision=" + floatingPointType.getPrecision().toString() + ")"); } case Utf8: return java.lang.String.class; default: - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, exMsg + + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, exMsg + " of type " + arrowType.getTypeID().toString()); } } diff --git a/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/GrpcUtil.java b/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/GrpcUtil.java index 3031cfae6db..f73a64cf3ce 100644 --- a/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/GrpcUtil.java +++ b/extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/GrpcUtil.java @@ -5,76 +5,17 @@ import io.deephaven.io.logger.Logger; import com.google.rpc.Code; -import io.deephaven.engine.liveness.LivenessScopeStack; import io.deephaven.proto.util.Exceptions; import io.deephaven.util.FunctionalInterfaces; -import io.deephaven.util.SafeCloseable; import io.deephaven.internal.log.LoggerFactory; -import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; -import java.io.IOException; import java.util.UUID; -import java.util.concurrent.Callable; public class GrpcUtil { private static final Logger log = LoggerFactory.getLogger(GrpcUtil.class); - /** - * Utility to avoid errors escaping to the stream, to make sure the server log and client both see the message if - * there is an error, and if the error was not meant to propagate to a gRPC client, obfuscates it. - * - * @param log the current class's logger - * @param response the responseStream used to send messages to the client - * @param lambda the code to safely execute - * @param some IOException type, so that we can handle IO errors as well as runtime exceptions. - */ - public static void rpcWrapper(final Logger log, final StreamObserver response, - final FunctionalInterfaces.ThrowingRunnable lambda) { - try (final SafeCloseable ignored = LivenessScopeStack.open()) { - lambda.run(); - } catch (final StatusRuntimeException err) { - if (err.getStatus().equals(Status.UNAUTHENTICATED)) { - log.info().append("ignoring unauthenticated request").endl(); - } else { - log.error().append(err).endl(); - } - safelyError(response, err); - } catch (final RuntimeException | IOException err) { - safelyError(response, securelyWrapError(log, err)); - } - } - - /** - * Utility to avoid errors escaping to the stream, to make sure the server log and client both see the message if - * there is an error, and if the error was not meant to propagate to a gRPC client, obfuscates it. - * - * @param log the current class's logger - * @param response the responseStream used to send messages to the client - * @param lambda the code to safely execute - * @param the type of the value to be returned - * @return the result of the lambda - */ - public static T rpcWrapper(final Logger log, final StreamObserver response, final Callable lambda) { - try (final SafeCloseable ignored = LivenessScopeStack.open()) { - return lambda.call(); - } catch (final StatusRuntimeException err) { - if (err.getStatus().equals(Status.UNAUTHENTICATED)) { - log.info().append("ignoring unauthenticated request").endl(); - } else { - log.error().append(err).endl(); - } - safelyError(response, err); - } catch (final InterruptedException err) { - Thread.currentThread().interrupt(); - safelyError(response, securelyWrapError(log, err, Code.UNAVAILABLE)); - } catch (final Throwable err) { - safelyError(response, securelyWrapError(log, err)); - } - return null; - } - public static StatusRuntimeException securelyWrapError(final Logger log, final Throwable err) { return securelyWrapError(log, err, Code.INVALID_ARGUMENT); } @@ -87,11 +28,7 @@ public static StatusRuntimeException securelyWrapError(final Logger log, final T final UUID errorId = UUID.randomUUID(); log.error().append("Internal Error '").append(errorId.toString()).append("' ").append(err).endl(); - return statusRuntimeException(statusCode, "Details Logged w/ID '" + errorId + "'"); - } - - public static StatusRuntimeException statusRuntimeException(final Code statusCode, final String details) { - return Exceptions.statusRuntimeException(statusCode, details); + return Exceptions.statusRuntimeException(statusCode, "Details Logged w/ID '" + errorId + "'"); } /** @@ -155,7 +92,7 @@ public static void safelyComplete(StreamObserver observer) { * stream. */ public static void safelyError(final StreamObserver observer, final Code statusCode, final String msg) { - safelyError(observer, statusRuntimeException(statusCode, msg)); + safelyError(observer, Exceptions.statusRuntimeException(statusCode, msg)); } /** diff --git a/server/src/main/java/io/deephaven/server/appmode/ApplicationFactory.java b/server/src/main/java/io/deephaven/server/appmode/ApplicationFactory.java index bb7fa2b1d98..c420cec990a 100644 --- a/server/src/main/java/io/deephaven/server/appmode/ApplicationFactory.java +++ b/server/src/main/java/io/deephaven/server/appmode/ApplicationFactory.java @@ -13,8 +13,8 @@ import io.deephaven.appmode.StaticClassApplication; import io.deephaven.engine.util.GroovyDeephavenSession; import io.deephaven.engine.util.ScriptSession; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.integrations.python.PythonDeephavenSession; +import io.deephaven.proto.util.Exceptions; import java.nio.file.Path; import java.util.List; @@ -77,7 +77,7 @@ public void visit(DynamicApplication advanced) { @Override public void visit(QSTApplication qst) { - throw GrpcUtil.statusRuntimeException(Code.UNIMPLEMENTED, "See deephaven-core#1080; support qst application"); + throw Exceptions.statusRuntimeException(Code.UNIMPLEMENTED, "See deephaven-core#1080; support qst application"); } @Override diff --git a/server/src/main/java/io/deephaven/server/appmode/ApplicationServiceGrpcImpl.java b/server/src/main/java/io/deephaven/server/appmode/ApplicationServiceGrpcImpl.java index f766656abaa..d31b26070c4 100644 --- a/server/src/main/java/io/deephaven/server/appmode/ApplicationServiceGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/appmode/ApplicationServiceGrpcImpl.java @@ -22,6 +22,7 @@ import io.deephaven.server.util.Scheduler; import io.grpc.stub.ServerCallStreamObserver; import io.grpc.stub.StreamObserver; +import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import javax.inject.Singleton; @@ -125,22 +126,21 @@ private synchronized void propagateUpdates() { } @Override - public synchronized void listFields(ListFieldsRequest request, - StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - final Subscription subscription = new Subscription(session, responseObserver); - - final FieldsChangeUpdate.Builder responseBuilder = FieldsChangeUpdate.newBuilder(); - for (FieldInfo fieldInfo : known.values()) { - responseBuilder.addCreated(fieldInfo); - } - if (subscription.send(responseBuilder.build())) { - subscriptions.add(subscription); - } else { - subscription.onCancel(); - } - }); + public synchronized void listFields( + @NotNull final ListFieldsRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + final Subscription subscription = new Subscription(session, responseObserver); + + final FieldsChangeUpdate.Builder responseBuilder = FieldsChangeUpdate.newBuilder(); + for (FieldInfo fieldInfo : known.values()) { + responseBuilder.addCreated(fieldInfo); + } + if (subscription.send(responseBuilder.build())) { + subscriptions.add(subscription); + } else { + subscription.onCancel(); + } } synchronized void remove(Subscription sub) { diff --git a/server/src/main/java/io/deephaven/server/appmode/ApplicationTicketResolver.java b/server/src/main/java/io/deephaven/server/appmode/ApplicationTicketResolver.java index 116a5774cab..7e87dc8fa4c 100644 --- a/server/src/main/java/io/deephaven/server/appmode/ApplicationTicketResolver.java +++ b/server/src/main/java/io/deephaven/server/appmode/ApplicationTicketResolver.java @@ -9,7 +9,6 @@ import io.deephaven.appmode.Field; import io.deephaven.base.string.EncodingInfo; import io.deephaven.engine.table.Table; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.Ticket; import io.deephaven.proto.flight.util.TicketRouterHelper; import io.deephaven.proto.util.ApplicationTicketHelper; @@ -74,13 +73,13 @@ public SessionState.ExportObject resolve( private SessionState.ExportObject resolve(final AppFieldId id, final String logId) { if (id.app == null) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': field '" + getLogNameFor(id) + "' does not belong to an application"); } final Field field = id.app.getField(id.fieldName); if (field == null) { - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, "Could not resolve '" + logId + "': field '" + getLogNameFor(id) + "' not found"); } Object value = authTransformation.transform(field.value()); @@ -93,7 +92,7 @@ public SessionState.ExportObject flightInfoFor( final @Nullable SessionState session, final Flight.FlightDescriptor descriptor, final String logId) { final AppFieldId id = appFieldIdFor(descriptor, logId); if (id.app == null) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': field does not belong to an application"); } @@ -101,7 +100,7 @@ public SessionState.ExportObject flightInfoFor( synchronized (id.app) { Field field = id.app.getField(id.fieldName); if (field == null) { - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, "Could not resolve '" + logId + "': field '" + getLogNameFor(id) + "' not found"); } Object value = field.value(); @@ -109,7 +108,7 @@ public SessionState.ExportObject flightInfoFor( value = authTransformation.transform(value); info = TicketRouter.getFlightInfo((Table) value, descriptor, flightTicketForName(id.app, id.fieldName)); } else { - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, "Could not resolve '" + logId + "': field '" + getLogNameFor(id) + "' is not a flight"); } } @@ -120,14 +119,14 @@ public SessionState.ExportObject flightInfoFor( @Override public SessionState.ExportBuilder publish( SessionState session, ByteBuffer ticket, final String logId) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not publish '" + logId + "': application tickets cannot be published to"); } @Override public SessionState.ExportBuilder publish( final SessionState session, final Flight.FlightDescriptor descriptor, final String logId) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not publish '" + logId + "': application flight descriptors cannot be published to"); } @@ -210,7 +209,7 @@ private AppFieldId appFieldIdFor(final ByteBuffer ticket, final String logId) { try { ticketAsString = decoder.decode(ticket).toString(); } catch (CharacterCodingException e) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': failed to decode: " + e.getMessage()); } finally { ticket.position(initialPosition); @@ -221,7 +220,7 @@ private AppFieldId appFieldIdFor(final ByteBuffer ticket, final String logId) { final int endOfAppId = ticketAsString.indexOf('/', endOfRoute + 1); final int endOfFieldSegment = ticketAsString.indexOf('/', endOfAppId + 1); if (endOfAppId == -1 || endOfFieldSegment == -1) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': ticket does conform to expected format"); } final String appId = ticketAsString.substring(endOfRoute + 1, endOfAppId); @@ -229,7 +228,7 @@ private AppFieldId appFieldIdFor(final ByteBuffer ticket, final String logId) { final ApplicationState app = applicationMap.get(appId); if (app == null) { - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, "Could not resolve '" + logId + "': no application exists with the identifier: " + appId); } @@ -243,13 +242,13 @@ private AppFieldId appFieldIdFor(final Flight.FlightDescriptor descriptor, final } if (descriptor.getType() != Flight.FlightDescriptor.DescriptorType.PATH) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': only flight paths are supported"); } // current structure: a/app_id/f/field_name if (descriptor.getPathCount() != 4) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': unexpected path length (found: " + TicketRouterHelper.getLogNameFor(descriptor) + ", expected: 4)"); } @@ -257,12 +256,12 @@ private AppFieldId appFieldIdFor(final Flight.FlightDescriptor descriptor, final final String appId = descriptor.getPath(1); final ApplicationState app = applicationMap.get(appId); if (app == null) { - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, "Could not resolve '" + logId + "': no application exists with the identifier: " + appId); } if (!descriptor.getPath(2).equals(ApplicationTicketHelper.FIELD_PATH_SEGMENT)) { - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, "Could not resolve '" + logId + "': path is not an application field"); } diff --git a/server/src/main/java/io/deephaven/server/arrow/ArrowFlightUtil.java b/server/src/main/java/io/deephaven/server/arrow/ArrowFlightUtil.java index 4bd8a9353a4..24f2ca38d63 100644 --- a/server/src/main/java/io/deephaven/server/arrow/ArrowFlightUtil.java +++ b/server/src/main/java/io/deephaven/server/arrow/ArrowFlightUtil.java @@ -31,6 +31,7 @@ import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.internal.log.LoggerFactory; import io.deephaven.io.logger.Logger; +import io.deephaven.proto.util.Exceptions; import io.deephaven.proto.util.ExportTicketHelper; import io.deephaven.server.barrage.BarrageMessageProducer; import io.deephaven.extensions.barrage.BarrageStreamGeneratorImpl; @@ -129,53 +130,57 @@ public DoPutObserver( // this is the entry point for client-streams @Override public void onNext(final InputStream request) { - GrpcUtil.rpcWrapper(log, observer, () -> { - final MessageInfo mi = BarrageProtoUtil.parseProtoMessage(request); - if (mi.descriptor != null) { - if (flightDescriptor != null) { - if (!flightDescriptor.equals(mi.descriptor)) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "additional flight descriptor sent does not match original descriptor"); - } - } else { - flightDescriptor = mi.descriptor; - resultExportBuilder = ticketRouter - .publish(session, mi.descriptor, "Flight.Descriptor") - .onError(observer); + final MessageInfo mi; + try { + mi = BarrageProtoUtil.parseProtoMessage(request); + } catch (final IOException err) { + throw GrpcUtil.securelyWrapError(log, err); + } + + if (mi.descriptor != null) { + if (flightDescriptor != null) { + if (!flightDescriptor.equals(mi.descriptor)) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "additional flight descriptor sent does not match original descriptor"); } + } else { + flightDescriptor = mi.descriptor; + resultExportBuilder = ticketRouter + .
publish(session, mi.descriptor, "Flight.Descriptor") + .onError(observer); } + } - if (mi.app_metadata != null - && mi.app_metadata.msgType() == BarrageMessageType.BarrageSerializationOptions) { - options = BarrageSubscriptionOptions.of(BarrageSubscriptionRequest - .getRootAsBarrageSubscriptionRequest(mi.app_metadata.msgPayloadAsByteBuffer())); - } + if (mi.app_metadata != null + && mi.app_metadata.msgType() == BarrageMessageType.BarrageSerializationOptions) { + options = BarrageSubscriptionOptions.of(BarrageSubscriptionRequest + .getRootAsBarrageSubscriptionRequest(mi.app_metadata.msgPayloadAsByteBuffer())); + } - if (mi.header == null) { - return; // nothing to do! - } + if (mi.header == null) { + return; // nothing to do! + } - if (mi.header.headerType() == MessageHeader.Schema) { - parseSchema((Schema) mi.header.header(new Schema())); - return; - } + if (mi.header.headerType() == MessageHeader.Schema) { + parseSchema((Schema) mi.header.header(new Schema())); + return; + } - if (mi.header.headerType() != MessageHeader.RecordBatch) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Only schema/record-batch messages supported"); - } + if (mi.header.headerType() != MessageHeader.RecordBatch) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Only schema/record-batch messages supported"); + } - final int numColumns = resultTable.getColumnSources().size(); - final BarrageMessage msg = createBarrageMessage(mi, numColumns); - msg.rowsAdded = RowSetFactory.fromRange(totalRowsRead, totalRowsRead + msg.length - 1); - msg.rowsIncluded = msg.rowsAdded.copy(); - msg.modColumnData = BarrageMessage.ZERO_MOD_COLUMNS; - totalRowsRead += msg.length; - resultTable.handleBarrageMessage(msg); + final int numColumns = resultTable.getColumnSources().size(); + final BarrageMessage msg = createBarrageMessage(mi, numColumns); + msg.rowsAdded = RowSetFactory.fromRange(totalRowsRead, totalRowsRead + msg.length - 1); + msg.rowsIncluded = msg.rowsAdded.copy(); + msg.modColumnData = BarrageMessage.ZERO_MOD_COLUMNS; + totalRowsRead += msg.length; + resultTable.handleBarrageMessage(msg); - // no app_metadata to report; but ack the processing - GrpcUtil.safelyOnNext(observer, Flight.PutResult.getDefaultInstance()); - }); + // no app_metadata to report; but ack the processing + GrpcUtil.safelyOnNext(observer, Flight.PutResult.getDefaultInstance()); } private void onCancel() { @@ -186,7 +191,7 @@ private void onCancel() { if (resultExportBuilder != null) { // this thrown error propagates to observer resultExportBuilder.submit(() -> { - throw GrpcUtil.statusRuntimeException(Code.CANCELLED, "cancelled"); + throw Exceptions.statusRuntimeException(Code.CANCELLED, "cancelled"); }); resultExportBuilder = null; } @@ -214,40 +219,39 @@ public void onError(Throwable t) { @Override public void onCompleted() { - GrpcUtil.rpcWrapper(log, observer, () -> { - if (resultExportBuilder == null) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Result flight descriptor never provided"); - } - if (resultTable == null) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Result flight schema never provided"); - } + if (resultExportBuilder == null) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Result flight descriptor never provided"); + } + if (resultTable == null) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Result flight schema never provided"); + } - final BarrageTable localResultTable = resultTable; - resultTable = null; - final SessionState.ExportBuilder
localExportBuilder = resultExportBuilder; - resultExportBuilder = null; + final BarrageTable localResultTable = resultTable; + resultTable = null; + final SessionState.ExportBuilder
localExportBuilder = resultExportBuilder; + resultExportBuilder = null; - // gRPC is about to remove its hard reference to this observer. We must keep the result table hard - // referenced until the export is complete, so that the export can properly be satisfied. ExportObject's - // LivenessManager enforces strong reachability. - if (!localExportBuilder.getExport().tryManage(localResultTable)) { - GrpcUtil.safelyError(observer, Code.DATA_LOSS, "Result export already destroyed"); - localResultTable.dropReference(); - session.removeOnCloseCallback(this); - return; - } + // gRPC is about to remove its hard reference to this observer. We must keep the result table hard + // referenced until the export is complete, so that the export can properly be satisfied. ExportObject's + // LivenessManager enforces strong reachability. + if (!localExportBuilder.getExport().tryManage(localResultTable)) { + GrpcUtil.safelyError(observer, Code.DATA_LOSS, "Result export already destroyed"); localResultTable.dropReference(); - - // no more changes allowed; this is officially static content - localResultTable.sealTable(() -> localExportBuilder.submit(() -> { - GrpcUtil.safelyComplete(observer); - session.removeOnCloseCallback(this); - return localResultTable; - }), () -> { - GrpcUtil.safelyError(observer, Code.DATA_LOSS, "Do put could not be sealed"); - session.removeOnCloseCallback(this); - }); + session.removeOnCloseCallback(this); + return; + } + localResultTable.dropReference(); + + // no more changes allowed; this is officially static content + localResultTable.sealTable(() -> localExportBuilder.submit(() -> { + GrpcUtil.safelyComplete(observer); + session.removeOnCloseCallback(this); + return localResultTable; + }), () -> { + GrpcUtil.safelyError(observer, Code.DATA_LOSS, "Do put could not be sealed"); + session.removeOnCloseCallback(this); }); } @@ -328,67 +332,70 @@ public DoExchangeMarshaller( // this entry is used for client-streaming requests @Override public void onNext(final InputStream request) { - GrpcUtil.rpcWrapper(log, listener, () -> { - MessageInfo message = BarrageProtoUtil.parseProtoMessage(request); - synchronized (this) { - - // `FlightData` messages from Barrage clients will provide app_metadata describing the request but - // official Flight implementations may force a NULL metadata field in the first message. In that - // case, identify a valid Barrage connection by verifying the `FlightDescriptor.CMD` field contains - // the `Barrage` magic bytes - - if (requestHandler != null) { - // rely on the handler to verify message type - requestHandler.handleMessage(message); - return; - } + MessageInfo message; + try { + message = BarrageProtoUtil.parseProtoMessage(request); + } catch (final IOException err) { + throw GrpcUtil.securelyWrapError(log, err); + } + synchronized (this) { - if (message.app_metadata != null) { - // handle the different message types that can come over DoExchange - switch (message.app_metadata.msgType()) { - case BarrageMessageType.BarrageSubscriptionRequest: - requestHandler = new SubscriptionRequestHandler(); - break; - case BarrageMessageType.BarrageSnapshotRequest: - requestHandler = new SnapshotRequestHandler(); - break; - default: - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - myPrefix + "received a message with unhandled BarrageMessageType"); - } - requestHandler.handleMessage(message); - return; - } + // `FlightData` messages from Barrage clients will provide app_metadata describing the request but + // official Flight implementations may force a NULL metadata field in the first message. In that + // case, identify a valid Barrage connection by verifying the `FlightDescriptor.CMD` field contains + // the `Barrage` magic bytes - // handle the possible error cases - if (!isFirstMsg) { - // only the first messages is allowed to have null metadata - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - myPrefix + "failed to receive Barrage request metadata"); + if (requestHandler != null) { + // rely on the handler to verify message type + requestHandler.handleMessage(message); + return; + } + + if (message.app_metadata != null) { + // handle the different message types that can come over DoExchange + switch (message.app_metadata.msgType()) { + case BarrageMessageType.BarrageSubscriptionRequest: + requestHandler = new SubscriptionRequestHandler(); + break; + case BarrageMessageType.BarrageSnapshotRequest: + requestHandler = new SnapshotRequestHandler(); + break; + default: + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + myPrefix + "received a message with unhandled BarrageMessageType"); } + requestHandler.handleMessage(message); + return; + } - isFirstMsg = false; + // handle the possible error cases + if (!isFirstMsg) { + // only the first messages is allowed to have null metadata + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + myPrefix + "failed to receive Barrage request metadata"); + } + + isFirstMsg = false; - // The magic value is '0x6E687064'. It is the numerical representation of the ASCII "dphn". - int size = message.descriptor.getCmd().size(); - if (size == 4) { - ByteBuffer bb = message.descriptor.getCmd().asReadOnlyByteBuffer(); + // The magic value is '0x6E687064'. It is the numerical representation of the ASCII "dphn". + int size = message.descriptor.getCmd().size(); + if (size == 4) { + ByteBuffer bb = message.descriptor.getCmd().asReadOnlyByteBuffer(); - // set the order to little-endian (FlatBuffers default) - bb.order(ByteOrder.LITTLE_ENDIAN); + // set the order to little-endian (FlatBuffers default) + bb.order(ByteOrder.LITTLE_ENDIAN); - // read and compare the value to the "magic" bytes - long value = (long) bb.getInt(0) & 0xFFFFFFFFL; - if (value != BarrageUtil.FLATBUFFER_MAGIC) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - myPrefix + "expected BarrageMessageWrapper magic bytes in FlightDescriptor.cmd"); - } - } else { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + // read and compare the value to the "magic" bytes + long value = (long) bb.getInt(0) & 0xFFFFFFFFL; + if (value != BarrageUtil.FLATBUFFER_MAGIC) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, myPrefix + "expected BarrageMessageWrapper magic bytes in FlightDescriptor.cmd"); } + } else { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + myPrefix + "expected BarrageMessageWrapper magic bytes in FlightDescriptor.cmd"); } - }); + } } public void onCancel() { @@ -422,8 +429,8 @@ public void close() { if (requestHandler != null) { requestHandler.close(); } - } catch (IOException ioException) { - throw new UncheckedDeephavenException("IOException closing handler", ioException); + } catch (final IOException err) { + throw GrpcUtil.securelyWrapError(log, err); } release(); @@ -447,7 +454,7 @@ public SnapshotRequestHandler() {} public void handleMessage(@NotNull final BarrageProtoUtil.MessageInfo message) { // verify this is the correct type of message for this handler if (message.app_metadata.msgType() != BarrageMessageType.BarrageSnapshotRequest) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Request type cannot be changed after initialization, expected BarrageSnapshotRequest metadata"); } @@ -522,13 +529,12 @@ public SubscriptionRequestHandler() {} public void handleMessage(@NotNull final MessageInfo message) { // verify this is the correct type of message for this handler if (message.app_metadata.msgType() != BarrageMessageType.BarrageSubscriptionRequest) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Request type cannot be changed after initialization, expected BarrageSubscriptionRequest metadata"); } if (message.app_metadata.msgPayloadVector() == null) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Subscription request not supplied"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Subscription request not supplied"); } // ensure synchronization with parent class functions @@ -667,7 +673,7 @@ private void apply(final BarrageSubscriptionRequest subscriptionRequest) { } if (!subscriptionFound) { - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, "Subscription was not found."); + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, "Subscription was not found."); } } diff --git a/server/src/main/java/io/deephaven/server/arrow/FlightServiceGrpcImpl.java b/server/src/main/java/io/deephaven/server/arrow/FlightServiceGrpcImpl.java index 01cb671e300..bf9277951ee 100644 --- a/server/src/main/java/io/deephaven/server/arrow/FlightServiceGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/arrow/FlightServiceGrpcImpl.java @@ -17,6 +17,7 @@ import io.deephaven.proto.backplane.grpc.ExportNotification; import io.deephaven.proto.backplane.grpc.WrappedAuthenticationRequest; import io.deephaven.extensions.barrage.BarrageStreamGeneratorImpl; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionService; import io.deephaven.server.session.SessionState; import io.deephaven.server.session.TicketRouter; @@ -24,6 +25,7 @@ import io.grpc.stub.StreamObserver; import org.apache.arrow.flight.impl.Flight; import org.apache.arrow.flight.impl.FlightServiceGrpc; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.inject.Inject; @@ -63,8 +65,8 @@ public FlightServiceGrpcImpl( @Override public StreamObserver handshake( - StreamObserver responseObserver) { - return GrpcUtil.rpcWrapper(log, responseObserver, () -> new HandshakeObserver(responseObserver)); + @NotNull final StreamObserver responseObserver) { + return new HandshakeObserver(responseObserver); } private final class HandshakeObserver implements StreamObserver { @@ -108,8 +110,8 @@ public void onNext(final Flight.HandshakeRequest value) { } if (auth.isEmpty()) { - responseObserver.onError(GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, - "Authentication details invalid")); + responseObserver.onError( + Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "Authentication details invalid")); return; } @@ -147,95 +149,93 @@ public void onCompleted() { return; } responseObserver.onError( - GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, "no authentication details provided")); + Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "no authentication details provided")); } } @Override - public void listFlights(final Flight.Criteria request, final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - ticketRouter.visitFlightInfo(sessionService.getOptionalSession(), responseObserver::onNext); - responseObserver.onCompleted(); - }); + public void listFlights( + @NotNull final Flight.Criteria request, + @NotNull final StreamObserver responseObserver) { + ticketRouter.visitFlightInfo(sessionService.getOptionalSession(), responseObserver::onNext); + responseObserver.onCompleted(); } @Override - public void getFlightInfo(final Flight.FlightDescriptor request, - final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getOptionalSession(); + public void getFlightInfo( + @NotNull final Flight.FlightDescriptor request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getOptionalSession(); - final SessionState.ExportObject export = - ticketRouter.flightInfoFor(session, request, "request"); + final SessionState.ExportObject export = + ticketRouter.flightInfoFor(session, request, "request"); - if (session != null) { - session.nonExport() - .require(export) - .onError(responseObserver) - .submit(() -> { - responseObserver.onNext(export.get()); - responseObserver.onCompleted(); - }); - } else { - if (export.tryRetainReference()) { - try { - if (export.getState() == ExportNotification.State.EXPORTED) { - responseObserver.onNext(export.get()); - responseObserver.onCompleted(); - } - } finally { - export.dropReference(); + if (session != null) { + session.nonExport() + .require(export) + .onError(responseObserver) + .submit(() -> { + responseObserver.onNext(export.get()); + responseObserver.onCompleted(); + }); + } else { + if (export.tryRetainReference()) { + try { + if (export.getState() == ExportNotification.State.EXPORTED) { + responseObserver.onNext(export.get()); + responseObserver.onCompleted(); } - } else { - responseObserver.onError( - GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not find flight info")); + } finally { + export.dropReference(); } + } else { + responseObserver.onError( + Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not find flight info")); } - }); + } } @Override - public void getSchema(final Flight.FlightDescriptor request, - final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getOptionalSession(); + public void getSchema( + @NotNull final Flight.FlightDescriptor request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getOptionalSession(); - final SessionState.ExportObject export = - ticketRouter.flightInfoFor(session, request, "request"); + final SessionState.ExportObject export = + ticketRouter.flightInfoFor(session, request, "request"); - if (session != null) { - session.nonExport() - .require(export) - .onError(responseObserver) - .submit(() -> { - responseObserver.onNext(Flight.SchemaResult.newBuilder() - .setSchema(export.get().getSchema()) - .build()); - responseObserver.onCompleted(); - }); - } else { - if (export.tryRetainReference()) { - try { - if (export.getState() == ExportNotification.State.EXPORTED) { - responseObserver.onNext(Flight.SchemaResult.newBuilder() - .setSchema(export.get().getSchema()) - .build()); - responseObserver.onCompleted(); - } - } finally { - export.dropReference(); - } - } else { - responseObserver.onError( - GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not find flight info")); + if (session != null) { + session.nonExport() + .require(export) + .onError(responseObserver) + .submit(() -> { + responseObserver.onNext(Flight.SchemaResult.newBuilder() + .setSchema(export.get().getSchema()) + .build()); + responseObserver.onCompleted(); + }); + } else if (export.tryRetainReference()) { + try { + if (export.getState() == ExportNotification.State.EXPORTED) { + responseObserver.onNext(Flight.SchemaResult.newBuilder() + .setSchema(export.get().getSchema()) + .build()); + responseObserver.onCompleted(); } + } finally { + export.dropReference(); } - }); + } else { + responseObserver.onError( + Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not find flight info")); + } } - public void doGetCustom(final Flight.Ticket request, final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> ArrowFlightUtil.DoGetCustom( - streamGeneratorFactory, sessionService.getCurrentSession(), ticketRouter, request, responseObserver)); + public void doGetCustom( + final Flight.Ticket request, + final StreamObserver responseObserver) { + ArrowFlightUtil.DoGetCustom( + streamGeneratorFactory, sessionService.getCurrentSession(), ticketRouter, request, responseObserver); } /** @@ -245,8 +245,7 @@ public void doGetCustom(final Flight.Ticket request, final StreamObserver doPutCustom(final StreamObserver responseObserver) { - return GrpcUtil.rpcWrapper(log, responseObserver, () -> new ArrowFlightUtil.DoPutObserver( - sessionService.getCurrentSession(), ticketRouter, responseObserver)); + return new ArrowFlightUtil.DoPutObserver(sessionService.getCurrentSession(), ticketRouter, responseObserver); } /** @@ -256,7 +255,6 @@ public StreamObserver doPutCustom(final StreamObserver doExchangeCustom(final StreamObserver responseObserver) { - return GrpcUtil.rpcWrapper(log, responseObserver, - () -> doExchangeFactory.openExchange(sessionService.getCurrentSession(), responseObserver)); + return doExchangeFactory.openExchange(sessionService.getCurrentSession(), responseObserver); } } diff --git a/server/src/main/java/io/deephaven/server/browserstreaming/BrowserStream.java b/server/src/main/java/io/deephaven/server/browserstreaming/BrowserStream.java index da8a8ca4f5d..9f0d31ef58e 100644 --- a/server/src/main/java/io/deephaven/server/browserstreaming/BrowserStream.java +++ b/server/src/main/java/io/deephaven/server/browserstreaming/BrowserStream.java @@ -7,6 +7,7 @@ import io.deephaven.base.RAPriQueue; import io.deephaven.base.verify.Assert; import io.deephaven.extensions.barrage.util.GrpcUtil; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import io.deephaven.server.util.GrpcServiceOverrideBuilder; import io.deephaven.internal.log.LoggerFactory; @@ -81,7 +82,7 @@ public void onMessageReceived(ReqT message) { @Override public void onCancel() { StatusRuntimeException canceled = - GrpcUtil.statusRuntimeException(Code.CANCELLED, "Stream canceled on the server"); + Exceptions.statusRuntimeException(Code.CANCELLED, "Stream canceled on the server"); GrpcUtil.safelyError(responseObserver, canceled); GrpcUtil.safelyError(requestObserver, canceled); @@ -130,13 +131,13 @@ private BrowserStream(final Mode mode, final SessionState session, final Marshal public void onMessageReceived(T message, StreamData streamData) { synchronized (this) { if (halfClosedSeq != -1 && streamData.getSequence() > halfClosedSeq) { - throw GrpcUtil.statusRuntimeException(Code.ABORTED, "Sequence sent after half close: closed seq=" + throw Exceptions.statusRuntimeException(Code.ABORTED, "Sequence sent after half close: closed seq=" + halfClosedSeq + " recv seq=" + streamData.getSequence()); } if (streamData.isHalfClose()) { if (halfClosedSeq != -1) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Already half closed: closed seq=" + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Already half closed: closed seq=" + halfClosedSeq + " recv seq=" + streamData.getSequence()); } halfClosedSeq = streamData.getSequence(); @@ -144,7 +145,7 @@ public void onMessageReceived(T message, StreamData streamData) { if (mode == Mode.IN_ORDER) { if (streamData.getSequence() < nextSeq) { - throw GrpcUtil.statusRuntimeException(Code.OUT_OF_RANGE, + throw Exceptions.statusRuntimeException(Code.OUT_OF_RANGE, "Duplicate sequence sent: next seq=" + nextSeq + " recv seq=" + streamData.getSequence()); } boolean queueMsg = false; diff --git a/server/src/main/java/io/deephaven/server/browserstreaming/BrowserStreamInterceptor.java b/server/src/main/java/io/deephaven/server/browserstreaming/BrowserStreamInterceptor.java index 7c972ba2ff6..82dbad67356 100644 --- a/server/src/main/java/io/deephaven/server/browserstreaming/BrowserStreamInterceptor.java +++ b/server/src/main/java/io/deephaven/server/browserstreaming/BrowserStreamInterceptor.java @@ -4,8 +4,8 @@ package io.deephaven.server.browserstreaming; import com.google.rpc.Code; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.Ticket; +import io.deephaven.proto.util.Exceptions; import io.deephaven.proto.util.ExportTicketHelper; import io.grpc.Context; import io.grpc.Contexts; @@ -45,7 +45,7 @@ public ServerCall.Listener interceptCall(ServerCall responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - AuthenticationConstantsResponse.Builder builder = AuthenticationConstantsResponse.newBuilder(); - builder.putAllConfigValues(collectConfigs(AUTH_CLIENT_CONFIG_PROPERTY)); - responseObserver.onNext(builder.build()); - responseObserver.onCompleted(); - }); + public void getAuthenticationConstants( + @NotNull final AuthenticationConstantsRequest request, + @NotNull final StreamObserver responseObserver) { + AuthenticationConstantsResponse.Builder builder = AuthenticationConstantsResponse.newBuilder(); + builder.putAllConfigValues(collectConfigs(AUTH_CLIENT_CONFIG_PROPERTY)); + responseObserver.onNext(builder.build()); + responseObserver.onCompleted(); } @Override - public void getConfigurationConstants(ConfigurationConstantsRequest request, - StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - // Read the current session so we throw if not authenticated - sessionService.getCurrentSession(); + public void getConfigurationConstants( + @NotNull final ConfigurationConstantsRequest request, + @NotNull final StreamObserver responseObserver) { + // Read the current session so we throw if not authenticated + sessionService.getCurrentSession(); - ConfigurationConstantsResponse.Builder builder = ConfigurationConstantsResponse.newBuilder(); - builder.putAllConfigValues(collectConfigs(CLIENT_CONFIG_PROPERTY)); - responseObserver.onNext(builder.build()); - responseObserver.onCompleted(); - }); + ConfigurationConstantsResponse.Builder builder = ConfigurationConstantsResponse.newBuilder(); + builder.putAllConfigValues(collectConfigs(CLIENT_CONFIG_PROPERTY)); + responseObserver.onNext(builder.build()); + responseObserver.onCompleted(); } private Map collectConfigs(String clientConfigProperty) { diff --git a/server/src/main/java/io/deephaven/server/console/ConsoleServiceGrpcImpl.java b/server/src/main/java/io/deephaven/server/console/ConsoleServiceGrpcImpl.java index a1c9dd7d3fe..b9d9bb632b2 100644 --- a/server/src/main/java/io/deephaven/server/console/ConsoleServiceGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/console/ConsoleServiceGrpcImpl.java @@ -24,6 +24,7 @@ import io.deephaven.proto.backplane.grpc.Ticket; import io.deephaven.proto.backplane.grpc.TypedTicket; import io.deephaven.proto.backplane.script.grpc.*; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.console.completer.JavaAutoCompleteObserver; import io.deephaven.server.console.completer.PythonAutoCompleteObserver; import io.deephaven.server.session.SessionCloseableObserver; @@ -34,6 +35,7 @@ import io.deephaven.server.util.Scheduler; import io.grpc.stub.ServerCallStreamObserver; import io.grpc.stub.StreamObserver; +import org.jetbrains.annotations.NotNull; import org.jpy.PyObject; import javax.inject.Inject; @@ -87,113 +89,112 @@ public ConsoleServiceGrpcImpl(final TicketRouter ticketRouter, } @Override - public void getConsoleTypes(final GetConsoleTypesRequest request, - final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - // TODO (deephaven-core#3147): for legacy reasons, this method is used prior to authentication - if (!REMOTE_CONSOLE_DISABLED) { - responseObserver.onNext(GetConsoleTypesResponse.newBuilder() - .addConsoleTypes(scriptSessionProvider.get().scriptType().toLowerCase()) - .build()); - } else { - responseObserver.onNext(GetConsoleTypesResponse.getDefaultInstance()); - } - responseObserver.onCompleted(); - }); + public void getConsoleTypes( + @NotNull final GetConsoleTypesRequest request, + @NotNull final StreamObserver responseObserver) { + // TODO (deephaven-core#3147): for legacy reasons, this method is used prior to authentication + if (!REMOTE_CONSOLE_DISABLED) { + responseObserver.onNext(GetConsoleTypesResponse.newBuilder() + .addConsoleTypes(scriptSessionProvider.get().scriptType().toLowerCase()) + .build()); + } else { + responseObserver.onNext(GetConsoleTypesResponse.getDefaultInstance()); + } + responseObserver.onCompleted(); } @Override - public void startConsole(StartConsoleRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - SessionState session = sessionService.getCurrentSession(); - if (REMOTE_CONSOLE_DISABLED) { - responseObserver - .onError(GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "Remote console disabled")); - return; - } + public void startConsole( + @NotNull final StartConsoleRequest request, + @NotNull final StreamObserver responseObserver) { + SessionState session = sessionService.getCurrentSession(); + if (REMOTE_CONSOLE_DISABLED) { + responseObserver + .onError(Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Remote console disabled")); + return; + } - // TODO (#702): initially global session will be null; set it here if applicable + // TODO (#702): initially global session will be null; set it here if applicable - final String sessionType = request.getSessionType(); - if (!scriptSessionProvider.get().scriptType().equalsIgnoreCase(sessionType)) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, - "session type '" + sessionType + "' is not supported"); - } + final String sessionType = request.getSessionType(); + if (!scriptSessionProvider.get().scriptType().equalsIgnoreCase(sessionType)) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, + "session type '" + sessionType + "' is not supported"); + } - session.newExport(request.getResultId(), "resultId") - .onError(responseObserver) - .submit(() -> { - final ScriptSession scriptSession = new DelegatingScriptSession(scriptSessionProvider.get()); + session.newExport(request.getResultId(), "resultId") + .onError(responseObserver) + .submit(() -> { + final ScriptSession scriptSession = new DelegatingScriptSession(scriptSessionProvider.get()); - safelyComplete(responseObserver, StartConsoleResponse.newBuilder() - .setResultId(request.getResultId()) - .build()); + safelyComplete(responseObserver, StartConsoleResponse.newBuilder() + .setResultId(request.getResultId()) + .build()); - return scriptSession; - }); - }); + return scriptSession; + }); } @Override - public void subscribeToLogs(LogSubscriptionRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - sessionService.getCurrentSession(); - if (REMOTE_CONSOLE_DISABLED) { - GrpcUtil.safelyError(responseObserver, Code.FAILED_PRECONDITION, "Remote console disabled"); - return; - } - final LogsClient client = - new LogsClient(request, (ServerCallStreamObserver) responseObserver); - client.start(); - }); + public void subscribeToLogs( + @NotNull final LogSubscriptionRequest request, + @NotNull final StreamObserver responseObserver) { + sessionService.getCurrentSession(); + if (REMOTE_CONSOLE_DISABLED) { + GrpcUtil.safelyError(responseObserver, Code.FAILED_PRECONDITION, "Remote console disabled"); + return; + } + final LogsClient client = + new LogsClient(request, (ServerCallStreamObserver) responseObserver); + client.start(); } @Override - public void executeCommand(ExecuteCommandRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - final Ticket consoleId = request.getConsoleId(); - if (consoleId.getTicket().isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "No consoleId supplied"); - } + public void executeCommand( + @NotNull final ExecuteCommandRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + final Ticket consoleId = request.getConsoleId(); + if (consoleId.getTicket().isEmpty()) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "No consoleId supplied"); + } - SessionState.ExportObject exportedConsole = - ticketRouter.resolve(session, consoleId, "consoleId"); - session.nonExport() - .requiresSerialQueue() - .require(exportedConsole) - .onError(responseObserver) - .submit(() -> { - ScriptSession scriptSession = exportedConsole.get(); - ScriptSession.Changes changes = scriptSession.evaluateScript(request.getCode()); - ExecuteCommandResponse.Builder diff = ExecuteCommandResponse.newBuilder(); - FieldsChangeUpdate.Builder fieldChanges = FieldsChangeUpdate.newBuilder(); - changes.created.entrySet() - .forEach(entry -> fieldChanges.addCreated(makeVariableDefinition(entry))); - changes.updated.entrySet() - .forEach(entry -> fieldChanges.addUpdated(makeVariableDefinition(entry))); - changes.removed.entrySet() - .forEach(entry -> fieldChanges.addRemoved(makeVariableDefinition(entry))); - responseObserver.onNext(diff.setChanges(fieldChanges).build()); - responseObserver.onCompleted(); - }); - }); + SessionState.ExportObject exportedConsole = + ticketRouter.resolve(session, consoleId, "consoleId"); + session.nonExport() + .requiresSerialQueue() + .require(exportedConsole) + .onError(responseObserver) + .submit(() -> { + ScriptSession scriptSession = exportedConsole.get(); + ScriptSession.Changes changes = scriptSession.evaluateScript(request.getCode()); + ExecuteCommandResponse.Builder diff = ExecuteCommandResponse.newBuilder(); + FieldsChangeUpdate.Builder fieldChanges = FieldsChangeUpdate.newBuilder(); + changes.created.entrySet() + .forEach(entry -> fieldChanges.addCreated(makeVariableDefinition(entry))); + changes.updated.entrySet() + .forEach(entry -> fieldChanges.addUpdated(makeVariableDefinition(entry))); + changes.removed.entrySet() + .forEach(entry -> fieldChanges.addRemoved(makeVariableDefinition(entry))); + responseObserver.onNext(diff.setChanges(fieldChanges).build()); + responseObserver.onCompleted(); + }); } @Override - public void getHeapInfo(GetHeapInfoRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final RuntimeMemory runtimeMemory = RuntimeMemory.getInstance(); - final Sample sample = new Sample(); - runtimeMemory.read(sample); - final GetHeapInfoResponse infoResponse = GetHeapInfoResponse.newBuilder() - .setTotalMemory(sample.totalMemory) - .setFreeMemory(sample.freeMemory) - .setMaxMemory(runtimeMemory.maxMemory()) - .build(); - responseObserver.onNext(infoResponse); - responseObserver.onCompleted(); - }); + public void getHeapInfo( + @NotNull final GetHeapInfoRequest request, + @NotNull final StreamObserver responseObserver) { + final RuntimeMemory runtimeMemory = RuntimeMemory.getInstance(); + final Sample sample = new Sample(); + runtimeMemory.read(sample); + final GetHeapInfoResponse infoResponse = GetHeapInfoResponse.newBuilder() + .setTotalMemory(sample.totalMemory) + .setFreeMemory(sample.freeMemory) + .setMaxMemory(runtimeMemory.maxMemory()) + .build(); + responseObserver.onNext(infoResponse); + responseObserver.onCompleted(); } private static FieldInfo makeVariableDefinition(Map.Entry entry) { @@ -214,79 +215,76 @@ private static FieldInfo makeVariableDefinition(String title, String type) { } @Override - public void cancelCommand(CancelCommandRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - // TODO (#53): consider task cancellation - super.cancelCommand(request, responseObserver); - }); + public void cancelCommand( + @NotNull final CancelCommandRequest request, + @NotNull final StreamObserver responseObserver) { + // TODO (#53): consider task cancellation + super.cancelCommand(request, responseObserver); } @Override - public void bindTableToVariable(BindTableToVariableRequest request, - StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - - Ticket tableId = request.getTableId(); - if (tableId.getTicket().isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "No source tableId supplied"); - } - final SessionState.ExportObject
exportedTable = ticketRouter.resolve(session, tableId, "tableId"); - final SessionState.ExportObject exportedConsole; - - ExportBuilder exportBuilder = session.nonExport() - .requiresSerialQueue() - .onError(responseObserver); - - if (request.hasConsoleId()) { - exportedConsole = ticketRouter.resolve(session, request.getConsoleId(), "consoleId"); - exportBuilder.require(exportedTable, exportedConsole); - } else { - exportedConsole = null; - exportBuilder.require(exportedTable); - } + public void bindTableToVariable( + @NotNull final BindTableToVariableRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + + Ticket tableId = request.getTableId(); + if (tableId.getTicket().isEmpty()) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "No source tableId supplied"); + } + final SessionState.ExportObject
exportedTable = ticketRouter.resolve(session, tableId, "tableId"); + final SessionState.ExportObject exportedConsole; + + ExportBuilder exportBuilder = session.nonExport() + .requiresSerialQueue() + .onError(responseObserver); + + if (request.hasConsoleId()) { + exportedConsole = ticketRouter.resolve(session, request.getConsoleId(), "consoleId"); + exportBuilder.require(exportedTable, exportedConsole); + } else { + exportedConsole = null; + exportBuilder.require(exportedTable); + } - exportBuilder.submit(() -> { - ScriptSession scriptSession = - exportedConsole != null ? exportedConsole.get() : scriptSessionProvider.get(); - Table table = exportedTable.get(); - scriptSession.setVariable(request.getVariableName(), table); - if (DynamicNode.notDynamicOrIsRefreshing(table)) { - scriptSession.manage(table); - } - responseObserver.onNext(BindTableToVariableResponse.getDefaultInstance()); - responseObserver.onCompleted(); - }); + exportBuilder.submit(() -> { + ScriptSession scriptSession = + exportedConsole != null ? exportedConsole.get() : scriptSessionProvider.get(); + Table table = exportedTable.get(); + scriptSession.setVariable(request.getVariableName(), table); + if (DynamicNode.notDynamicOrIsRefreshing(table)) { + scriptSession.manage(table); + } + responseObserver.onNext(BindTableToVariableResponse.getDefaultInstance()); + responseObserver.onCompleted(); }); } @Override public StreamObserver autoCompleteStream( - StreamObserver responseObserver) { - return GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - if (AUTOCOMPLETE_DISABLED) { - return new NoopAutoCompleteObserver(session, responseObserver); - } - if (PythonDeephavenSession.SCRIPT_TYPE.equals(scriptSessionProvider.get().scriptType())) { - PyObject[] settings = new PyObject[1]; - try { - final ScriptSession scriptSession = scriptSessionProvider.get(); - scriptSession.evaluateScript( - "from deephaven_internal.auto_completer import jedi_settings ; jedi_settings.set_scope(globals())"); - settings[0] = (PyObject) scriptSession.getVariable("jedi_settings"); - } catch (Exception err) { - log.error().append("Error trying to enable jedi autocomplete").append(err).endl(); - } - boolean canJedi = settings[0] != null && settings[0].call("can_jedi").getBooleanValue(); - log.info().append(canJedi ? "Using jedi for python autocomplete" - : "No jedi dependency available in python environment; disabling autocomplete.").endl(); - return canJedi ? new PythonAutoCompleteObserver(responseObserver, scriptSessionProvider, session) - : new NoopAutoCompleteObserver(session, responseObserver); + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + if (AUTOCOMPLETE_DISABLED) { + return new NoopAutoCompleteObserver(session, responseObserver); + } + if (PythonDeephavenSession.SCRIPT_TYPE.equals(scriptSessionProvider.get().scriptType())) { + PyObject[] settings = new PyObject[1]; + try { + final ScriptSession scriptSession = scriptSessionProvider.get(); + scriptSession.evaluateScript( + "from deephaven_internal.auto_completer import jedi_settings ; jedi_settings.set_scope(globals())"); + settings[0] = (PyObject) scriptSession.getVariable("jedi_settings"); + } catch (Exception err) { + log.error().append("Error trying to enable jedi autocomplete").append(err).endl(); } + boolean canJedi = settings[0] != null && settings[0].call("can_jedi").getBooleanValue(); + log.info().append(canJedi ? "Using jedi for python autocomplete" + : "No jedi dependency available in python environment; disabling autocomplete.").endl(); + return canJedi ? new PythonAutoCompleteObserver(responseObserver, scriptSessionProvider, session) + : new NoopAutoCompleteObserver(session, responseObserver); + } - return new JavaAutoCompleteObserver(session, responseObserver); - }); + return new JavaAutoCompleteObserver(session, responseObserver); } private static class NoopAutoCompleteObserver extends SessionCloseableObserver diff --git a/server/src/main/java/io/deephaven/server/console/ScopeTicketResolver.java b/server/src/main/java/io/deephaven/server/console/ScopeTicketResolver.java index 5d9cd89edc1..6a1ea86a8d9 100644 --- a/server/src/main/java/io/deephaven/server/console/ScopeTicketResolver.java +++ b/server/src/main/java/io/deephaven/server/console/ScopeTicketResolver.java @@ -12,10 +12,10 @@ import io.deephaven.engine.updategraph.DynamicNode; import io.deephaven.engine.updategraph.UpdateGraphProcessor; import io.deephaven.engine.util.ScriptSession; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.Ticket; import io.deephaven.proto.flight.util.TicketRouterHelper; import io.deephaven.proto.util.ByteHelper; +import io.deephaven.proto.util.Exceptions; import io.deephaven.proto.util.ScopeTicketHelper; import io.deephaven.server.auth.AuthorizationProvider; import io.deephaven.server.session.SessionState; @@ -63,7 +63,7 @@ public SessionState.ExportObject flightInfoFor( final ScriptSession gss = scriptSessionProvider.get(); Object scopeVar = gss.getVariable(scopeName, null); if (scopeVar == null) { - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, "Could not resolve '" + logId + ": no variable exists with name '" + scopeName + "'"); } if (scopeVar instanceof Table) { @@ -71,7 +71,7 @@ public SessionState.ExportObject flightInfoFor( return TicketRouter.getFlightInfo((Table) scopeVar, descriptor, flightTicketForName(scopeName)); } - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, "Could not resolve '" + logId + "': no variable exists with name '" + scopeName + "'"); }); @@ -117,7 +117,7 @@ private SessionState.ExportObject resolve( export = authTransformation.transform(export); if (export == null) { - return SessionState.wrapAsFailedExport(GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + return SessionState.wrapAsFailedExport(Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': no variable exists with name '" + scopeName + "'")); } @@ -205,12 +205,12 @@ public static Flight.FlightDescriptor descriptorForName(final String name) { */ public static String nameForTicket(final ByteBuffer ticket, final String logId) { if (ticket == null || ticket.remaining() == 0) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': no ticket supplied"); } if (ticket.remaining() < 3 || ticket.get(ticket.position()) != TICKET_PREFIX || ticket.get(ticket.position() + 1) != '/') { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': found 0x" + ByteHelper.byteBufToHex(ticket) + "' (hex)"); } @@ -221,7 +221,7 @@ public static String nameForTicket(final ByteBuffer ticket, final String logId) ticket.position(initialPosition + 2); return decoder.decode(ticket).toString(); } catch (CharacterCodingException e) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': failed to decode: " + e.getMessage()); } finally { ticket.position(initialPosition); @@ -238,11 +238,11 @@ public static String nameForTicket(final ByteBuffer ticket, final String logId) */ public static String nameForDescriptor(final Flight.FlightDescriptor descriptor, final String logId) { if (descriptor.getType() != Flight.FlightDescriptor.DescriptorType.PATH) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve descriptor '" + logId + "': only paths are supported"); } if (descriptor.getPathCount() != 2) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve descriptor '" + logId + "': unexpected path length (found: " + TicketRouterHelper.getLogNameFor(descriptor) + ", expected: 2)"); } diff --git a/server/src/main/java/io/deephaven/server/console/completer/JavaAutoCompleteObserver.java b/server/src/main/java/io/deephaven/server/console/completer/JavaAutoCompleteObserver.java index 10facdfe4fc..21d935beacf 100644 --- a/server/src/main/java/io/deephaven/server/console/completer/JavaAutoCompleteObserver.java +++ b/server/src/main/java/io/deephaven/server/console/completer/JavaAutoCompleteObserver.java @@ -3,7 +3,6 @@ import com.google.rpc.Code; import io.deephaven.engine.util.ScriptSession; import io.deephaven.engine.util.VariableProvider; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.internal.log.LoggerFactory; import io.deephaven.io.logger.Logger; import io.deephaven.lang.completion.ChunkerCompleter; @@ -13,6 +12,7 @@ import io.deephaven.lang.parse.ParsedDocument; import io.deephaven.lang.shared.lsp.CompletionCancelled; import io.deephaven.proto.backplane.script.grpc.*; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.console.ConsoleServiceGrpcImpl; import io.deephaven.server.session.SessionCloseableObserver; import io.deephaven.server.session.SessionState; @@ -91,8 +91,7 @@ public void onNext(AutoCompleteRequest value) { break; } case REQUEST_NOT_SET: { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Autocomplete command missing request"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Autocomplete command missing request"); } } } @@ -101,7 +100,7 @@ private void getCompletionItems(GetCompletionItemsRequest request, SessionState.ExportObject exportedConsole, CompletionParser parser, StreamObserver responseObserver) { final ScriptSession scriptSession = exportedConsole.get(); - try (final SafeCloseable ignored = scriptSession.getExecutionContext().open()) { + try { final VariableProvider vars = scriptSession.getVariableProvider(); final VersionedTextDocumentIdentifier doc = request.getTextDocument(); final CompletionLookups h = CompletionLookups.preload(scriptSession); diff --git a/server/src/main/java/io/deephaven/server/console/completer/PythonAutoCompleteObserver.java b/server/src/main/java/io/deephaven/server/console/completer/PythonAutoCompleteObserver.java index b119c0455b7..2d5aa1dc27c 100644 --- a/server/src/main/java/io/deephaven/server/console/completer/PythonAutoCompleteObserver.java +++ b/server/src/main/java/io/deephaven/server/console/completer/PythonAutoCompleteObserver.java @@ -2,12 +2,12 @@ import com.google.rpc.Code; import io.deephaven.engine.util.ScriptSession; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.internal.log.LoggerFactory; import io.deephaven.io.logger.Logger; import io.deephaven.lang.completion.ChunkerCompleter; import io.deephaven.lang.parse.CompletionParser; import io.deephaven.proto.backplane.script.grpc.*; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.console.ConsoleServiceGrpcImpl; import io.deephaven.server.session.SessionCloseableObserver; import io.deephaven.server.session.SessionState; @@ -91,8 +91,7 @@ public void onNext(AutoCompleteRequest value) { break; } case REQUEST_NOT_SET: { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Autocomplete command missing request"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Autocomplete command missing request"); } } } @@ -101,8 +100,7 @@ private void getCompletionItems(GetCompletionItemsRequest request, SessionState.ExportObject exportedConsole, StreamObserver responseObserver) { final ScriptSession scriptSession = exportedConsole.get(); - try (final SafeCloseable ignored = scriptSession.getExecutionContext().open()) { - + try { PyObject completer = (PyObject) scriptSession.getVariable("jedi_settings"); boolean canJedi = completer.callMethod("is_enabled").getBooleanValue(); if (!canJedi) { diff --git a/server/src/main/java/io/deephaven/server/grpc/Common.java b/server/src/main/java/io/deephaven/server/grpc/Common.java index b17516e06a1..6515c7fe060 100644 --- a/server/src/main/java/io/deephaven/server/grpc/Common.java +++ b/server/src/main/java/io/deephaven/server/grpc/Common.java @@ -1,18 +1,17 @@ package io.deephaven.server.grpc; import com.google.rpc.Code; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.TableReference; import io.deephaven.proto.backplane.grpc.TableReference.RefCase; import io.deephaven.proto.backplane.grpc.Ticket; -import io.deephaven.server.grpc.GrpcErrorHelper; +import io.deephaven.proto.util.Exceptions; public class Common { public static void validate(Ticket ticket) { GrpcErrorHelper.checkHasNoUnknownFields(ticket); if (ticket.getTicket().isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Ticket is empty"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Ticket is empty"); } } @@ -32,7 +31,7 @@ public static void validate(TableReference tableReference) { break; case REF_NOT_SET: default: - throw GrpcUtil.statusRuntimeException(Code.INTERNAL, + throw Exceptions.statusRuntimeException(Code.INTERNAL, String.format("Server missing TableReference type %s", ref)); } } diff --git a/server/src/main/java/io/deephaven/server/grpc/GrpcErrorHelper.java b/server/src/main/java/io/deephaven/server/grpc/GrpcErrorHelper.java index 87db3f57251..d88f1723704 100644 --- a/server/src/main/java/io/deephaven/server/grpc/GrpcErrorHelper.java +++ b/server/src/main/java/io/deephaven/server/grpc/GrpcErrorHelper.java @@ -4,14 +4,12 @@ import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; import com.google.protobuf.Message; -import com.google.protobuf.MessageOrBuilder; import com.google.rpc.Code; -import io.deephaven.extensions.barrage.util.GrpcUtil; +import io.deephaven.proto.util.Exceptions; import io.grpc.StatusRuntimeException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.List; import java.util.Map.Entry; import java.util.Objects; @@ -20,7 +18,7 @@ public static void checkHasField(Message message, int fieldNumber) throws Status final Descriptor descriptor = message.getDescriptorForType(); final FieldDescriptor fieldDescriptor = descriptor.findFieldByNumber(fieldNumber); if (!message.hasField(fieldDescriptor)) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, String.format("%s must have field %s (%d)", + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, String.format("%s must have field %s (%d)", descriptor.getFullName(), fieldDescriptor.getName(), fieldNumber)); } } @@ -34,7 +32,7 @@ public static void checkRepeatedFieldNonEmpty(Message message, int fieldNumber) descriptor.getFullName(), fieldDescriptor.getName(), fieldNumber)); } if (message.getRepeatedFieldCount(fieldDescriptor) <= 0) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, String.format("%s must have at least one %s (%d)", descriptor.getFullName(), fieldDescriptor.getName(), fieldNumber)); } @@ -45,7 +43,7 @@ public static void checkHasOneOf(Message message, String oneOfName) throws Statu final OneofDescriptor oneofDescriptor = descriptor.getOneofs().stream().filter(o -> oneOfName.equals(o.getName())).findFirst().orElseThrow(); if (!message.hasOneof(oneofDescriptor)) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, String.format( + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, String.format( "%s must have oneof %s. Note: this may also indicate that the server is older than the client and doesn't know about this new oneof option.", descriptor.getFullName(), oneOfName)); } @@ -53,7 +51,7 @@ public static void checkHasOneOf(Message message, String oneOfName) throws Statu public static void checkHasNoUnknownFields(Message message) { if (!message.getUnknownFields().asMap().isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, String.format("%s has unknown field(s)", message.getDescriptorForType().getFullName())); } } diff --git a/server/src/main/java/io/deephaven/server/hierarchicaltable/HierarchicalTableServiceGrpcImpl.java b/server/src/main/java/io/deephaven/server/hierarchicaltable/HierarchicalTableServiceGrpcImpl.java index 58d44e93086..8de31c21444 100644 --- a/server/src/main/java/io/deephaven/server/hierarchicaltable/HierarchicalTableServiceGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/hierarchicaltable/HierarchicalTableServiceGrpcImpl.java @@ -1,3 +1,6 @@ +/** + * Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending + */ package io.deephaven.server.hierarchicaltable; import com.google.rpc.Code; @@ -16,10 +19,10 @@ import io.deephaven.engine.table.impl.hierarchical.RollupTableImpl; import io.deephaven.engine.table.impl.select.WhereFilter; import io.deephaven.extensions.barrage.util.ExportUtil; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.internal.log.LoggerFactory; import io.deephaven.io.logger.Logger; import io.deephaven.proto.backplane.grpc.*; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.auth.AuthorizationProvider; import io.deephaven.server.grpc.Common; import io.deephaven.server.grpc.GrpcErrorHelper; @@ -68,37 +71,35 @@ public HierarchicalTableServiceGrpcImpl( public void rollup( @NotNull final RollupRequest request, @NotNull final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - validate(request); - - final SessionState session = sessionService.getCurrentSession(); - - final SessionState.ExportObject
sourceTableExport = ticketRouter.resolve( - session, request.getSourceTableId(), "rollup.sourceTableId"); - - session.newExport(request.getResultRollupTableId(), "rollup.resultRollupTableId") - .require(sourceTableExport) - .onError(responseObserver) - .submit(() -> { - final Table sourceTable = sourceTableExport.get(); - - authWiring.checkPermissionRollup(session.getAuthContext(), request, List.of(sourceTable)); - - final Collection aggregations = request.getAggregationsList().stream() - .map(AggregationAdapter::adapt) - .collect(Collectors.toList()); - final boolean includeConstituents = request.getIncludeConstituents(); - final Collection groupByColumns = request.getGroupByColumnsList().stream() - .map(ColumnName::of) - .collect(Collectors.toList()); - final RollupTable result = sourceTable.rollup( - aggregations, includeConstituents, groupByColumns); - - final RollupTable transformedResult = authTransformation.transform(result); - safelyComplete(responseObserver, RollupResponse.getDefaultInstance()); - return transformedResult; - }); - }); + validate(request); + + final SessionState session = sessionService.getCurrentSession(); + + final SessionState.ExportObject
sourceTableExport = ticketRouter.resolve( + session, request.getSourceTableId(), "rollup.sourceTableId"); + + session.newExport(request.getResultRollupTableId(), "rollup.resultRollupTableId") + .require(sourceTableExport) + .onError(responseObserver) + .submit(() -> { + final Table sourceTable = sourceTableExport.get(); + + authWiring.checkPermissionRollup(session.getAuthContext(), request, List.of(sourceTable)); + + final Collection aggregations = request.getAggregationsList().stream() + .map(AggregationAdapter::adapt) + .collect(Collectors.toList()); + final boolean includeConstituents = request.getIncludeConstituents(); + final Collection groupByColumns = request.getGroupByColumnsList().stream() + .map(ColumnName::of) + .collect(Collectors.toList()); + final RollupTable result = sourceTable.rollup( + aggregations, includeConstituents, groupByColumns); + + final RollupTable transformedResult = authTransformation.transform(result); + safelyComplete(responseObserver, RollupResponse.getDefaultInstance()); + return transformedResult; + }); } private static void validate(@NotNull final RollupRequest request) { @@ -114,41 +115,39 @@ private static void validate(@NotNull final RollupRequest request) { public void tree( @NotNull final TreeRequest request, @NotNull final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - validate(request); + validate(request); - final SessionState session = sessionService.getCurrentSession(); + final SessionState session = sessionService.getCurrentSession(); - final SessionState.ExportObject
sourceTableExport = ticketRouter.resolve( - session, request.getSourceTableId(), "tree.sourceTableId"); + final SessionState.ExportObject
sourceTableExport = ticketRouter.resolve( + session, request.getSourceTableId(), "tree.sourceTableId"); - session.newExport(request.getResultTreeTableId(), "tree.resultTreeTableId") - .require(sourceTableExport) - .onError(responseObserver) - .submit(() -> { - final Table sourceTable = sourceTableExport.get(); + session.newExport(request.getResultTreeTableId(), "tree.resultTreeTableId") + .require(sourceTableExport) + .onError(responseObserver) + .submit(() -> { + final Table sourceTable = sourceTableExport.get(); - authWiring.checkPermissionTree(session.getAuthContext(), request, List.of(sourceTable)); + authWiring.checkPermissionTree(session.getAuthContext(), request, List.of(sourceTable)); - final ColumnName identifierColumn = ColumnName.of(request.getIdentifierColumn()); - final ColumnName parentIdentifierColumn = ColumnName.of(request.getParentIdentifierColumn()); + final ColumnName identifierColumn = ColumnName.of(request.getIdentifierColumn()); + final ColumnName parentIdentifierColumn = ColumnName.of(request.getParentIdentifierColumn()); - final Table sourceTableToUse; - if (request.getPromoteOrphans()) { - sourceTableToUse = TreeTable.promoteOrphans( - sourceTable, identifierColumn.name(), parentIdentifierColumn.name()); - } else { - sourceTableToUse = sourceTable; - } + final Table sourceTableToUse; + if (request.getPromoteOrphans()) { + sourceTableToUse = TreeTable.promoteOrphans( + sourceTable, identifierColumn.name(), parentIdentifierColumn.name()); + } else { + sourceTableToUse = sourceTable; + } - final TreeTable result = sourceTableToUse.tree( - identifierColumn.name(), parentIdentifierColumn.name()); + final TreeTable result = sourceTableToUse.tree( + identifierColumn.name(), parentIdentifierColumn.name()); - final TreeTable transformedResult = authTransformation.transform(result); - safelyComplete(responseObserver, TreeResponse.getDefaultInstance()); - return transformedResult; - }); - }); + final TreeTable transformedResult = authTransformation.transform(result); + safelyComplete(responseObserver, TreeResponse.getDefaultInstance()); + return transformedResult; + }); } private static void validate(@NotNull final TreeRequest request) { @@ -165,84 +164,82 @@ private static void validate(@NotNull final TreeRequest request) { public void apply( @NotNull final HierarchicalTableApplyRequest request, @NotNull final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - validate(request); - - final SessionState session = sessionService.getCurrentSession(); - - final SessionState.ExportObject inputHierarchicalTableExport = ticketRouter.resolve( - session, request.getInputHierarchicalTableId(), "apply.inputHierarchicalTableId"); - - session.newExport(request.getResultHierarchicalTableId(), "apply.resultHierarchicalTableId") - .require(inputHierarchicalTableExport) - .onError(responseObserver) - .submit(() -> { - final HierarchicalTable inputHierarchicalTable = inputHierarchicalTableExport.get(); - - authWiring.checkPermissionApply(session.getAuthContext(), request, - List.of(inputHierarchicalTable.getSource())); - - if (request.getFiltersCount() == 0 && request.getSortsCount() == 0) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "No operations specified"); + validate(request); + + final SessionState session = sessionService.getCurrentSession(); + + final SessionState.ExportObject inputHierarchicalTableExport = ticketRouter.resolve( + session, request.getInputHierarchicalTableId(), "apply.inputHierarchicalTableId"); + + session.newExport(request.getResultHierarchicalTableId(), "apply.resultHierarchicalTableId") + .require(inputHierarchicalTableExport) + .onError(responseObserver) + .submit(() -> { + final HierarchicalTable inputHierarchicalTable = inputHierarchicalTableExport.get(); + + authWiring.checkPermissionApply(session.getAuthContext(), request, + List.of(inputHierarchicalTable.getSource())); + + if (request.getFiltersCount() == 0 && request.getSortsCount() == 0) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "No operations specified"); + } + final Collection finishedConditions = request.getFiltersCount() == 0 + ? null + : FilterTableGrpcImpl.finishConditions(request.getFiltersList()); + final Collection translatedSorts = + translateAndValidateSorts(request, (BaseGridAttributes) inputHierarchicalTable); + + final HierarchicalTable result; + if (inputHierarchicalTable instanceof RollupTable) { + RollupTable rollupTable = (RollupTable) inputHierarchicalTable; + // Rollups only support filtering on the group-by columns, so we can safely use the + // aggregated node definition here. + final TableDefinition nodeDefinition = + rollupTable.getNodeDefinition(RollupTable.NodeType.Aggregated); + if (finishedConditions != null) { + final Collection filters = + makeWhereFilters(finishedConditions, nodeDefinition); + RollupTableImpl.initializeAndValidateFilters( + rollupTable.getSource(), + rollupTable.getGroupByColumns(), + filters, + message -> Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, message)); + rollupTable = rollupTable.withFilters(filters); } - final Collection finishedConditions = request.getFiltersCount() == 0 - ? null - : FilterTableGrpcImpl.finishConditions(request.getFiltersList()); - final Collection translatedSorts = - translateAndValidateSorts(request, (BaseGridAttributes) inputHierarchicalTable); - - final HierarchicalTable result; - if (inputHierarchicalTable instanceof RollupTable) { - RollupTable rollupTable = (RollupTable) inputHierarchicalTable; - // Rollups only support filtering on the group-by columns, so we can safely use the - // aggregated node definition here. - final TableDefinition nodeDefinition = - rollupTable.getNodeDefinition(RollupTable.NodeType.Aggregated); - if (finishedConditions != null) { - final Collection filters = - makeWhereFilters(finishedConditions, nodeDefinition); - RollupTableImpl.initializeAndValidateFilters( - rollupTable.getSource(), - rollupTable.getGroupByColumns(), - filters, - message -> GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, message)); - rollupTable = rollupTable.withFilters(filters); - } - if (translatedSorts != null) { - RollupTable.NodeOperationsRecorder aggregatedSorts = - rollupTable.makeNodeOperationsRecorder(RollupTable.NodeType.Aggregated); - aggregatedSorts = aggregatedSorts.sort(translatedSorts); - if (rollupTable.includesConstituents()) { - final RollupTable.NodeOperationsRecorder constituentSorts = rollupTable - .translateAggregatedNodeOperationsForConstituentNodes(aggregatedSorts); - rollupTable = rollupTable.withNodeOperations(aggregatedSorts, constituentSorts); - } else { - rollupTable = rollupTable.withNodeOperations(aggregatedSorts); - } - } - result = rollupTable; - } else if (inputHierarchicalTable instanceof TreeTable) { - TreeTable treeTable = (TreeTable) inputHierarchicalTable; - final TableDefinition nodeDefinition = treeTable.getNodeDefinition(); - if (finishedConditions != null) { - treeTable = treeTable.withFilters(makeWhereFilters(finishedConditions, nodeDefinition)); - } - if (translatedSorts != null) { - TreeTable.NodeOperationsRecorder treeSorts = treeTable.makeNodeOperationsRecorder(); - treeSorts = treeSorts.sort(translatedSorts); - treeTable = treeTable.withNodeOperations(treeSorts); + if (translatedSorts != null) { + RollupTable.NodeOperationsRecorder aggregatedSorts = + rollupTable.makeNodeOperationsRecorder(RollupTable.NodeType.Aggregated); + aggregatedSorts = aggregatedSorts.sort(translatedSorts); + if (rollupTable.includesConstituents()) { + final RollupTable.NodeOperationsRecorder constituentSorts = rollupTable + .translateAggregatedNodeOperationsForConstituentNodes(aggregatedSorts); + rollupTable = rollupTable.withNodeOperations(aggregatedSorts, constituentSorts); + } else { + rollupTable = rollupTable.withNodeOperations(aggregatedSorts); } - result = treeTable; - } else { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Input is not a supported HierarchicalTable type"); } - - final HierarchicalTable transformedResult = authTransformation.transform(result); - safelyComplete(responseObserver, HierarchicalTableApplyResponse.getDefaultInstance()); - return transformedResult; - }); - }); + result = rollupTable; + } else if (inputHierarchicalTable instanceof TreeTable) { + TreeTable treeTable = (TreeTable) inputHierarchicalTable; + final TableDefinition nodeDefinition = treeTable.getNodeDefinition(); + if (finishedConditions != null) { + treeTable = treeTable.withFilters(makeWhereFilters(finishedConditions, nodeDefinition)); + } + if (translatedSorts != null) { + TreeTable.NodeOperationsRecorder treeSorts = treeTable.makeNodeOperationsRecorder(); + treeSorts = treeSorts.sort(translatedSorts); + treeTable = treeTable.withNodeOperations(treeSorts); + } + result = treeTable; + } else { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Input is not a supported HierarchicalTable type"); + } + + final HierarchicalTable transformedResult = authTransformation.transform(result); + safelyComplete(responseObserver, HierarchicalTableApplyResponse.getDefaultInstance()); + return transformedResult; + }); } private static void validate(@NotNull final HierarchicalTableApplyRequest request) { @@ -275,7 +272,7 @@ private static Collection translateAndValidateSorts( final Set sortableColumnNames = inputHierarchicalTable.getSortableColumns(); if (sortableColumnNames != null) { if (sortableColumnNames.isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Sorting is not supported on this hierarchical table"); } final Collection unavailableSortColumnNames = translatedSorts.stream() @@ -283,7 +280,7 @@ private static Collection translateAndValidateSorts( .filter(scn -> !sortableColumnNames.contains(scn)) .collect(Collectors.toList()); if (!unavailableSortColumnNames.isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Sorting attempted on restricted column(s): " + unavailableSortColumnNames.stream().collect(Collectors.joining(", ", "[", "]")) + ", available column(s) for sorting are: " @@ -306,7 +303,7 @@ private static SortColumn translateSort(@NotNull final SortDescriptor sortDescri case UNKNOWN: case REVERSE: default: - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Unsupported or unknown sort direction: " + sortDescriptor.getDirection()); } } @@ -315,87 +312,85 @@ private static SortColumn translateSort(@NotNull final SortDescriptor sortDescri public void view( @NotNull final HierarchicalTableViewRequest request, @NotNull final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - validate(request); - - final SessionState session = sessionService.getCurrentSession(); - - final SessionState.ExportBuilder resultExportBuilder = - session.newExport(request.getResultViewId(), "view.resultViewId"); - - final boolean usedExisting; - final Ticket targetTicket; - switch (request.getTargetCase()) { - case HIERARCHICAL_TABLE_ID: - usedExisting = false; - targetTicket = request.getHierarchicalTableId(); - break; - case EXISTING_VIEW_ID: - usedExisting = true; - targetTicket = request.getExistingViewId(); - break; - case TARGET_NOT_SET: - default: - throw new IllegalStateException(); - } - final SessionState.ExportObject targetExport = ticketRouter.resolve( - session, targetTicket, "view.target"); - - final SessionState.ExportObject
keyTableExport; - if (request.hasExpansions()) { - keyTableExport = ticketRouter.resolve( - session, request.getExpansions().getKeyTableId(), "view.expansions.keyTableId"); - resultExportBuilder.require(targetExport, keyTableExport); - } else { - keyTableExport = null; - resultExportBuilder.require(targetExport); - } + validate(request); - resultExportBuilder.onError(responseObserver) - .submit(() -> { - final Table keyTable = keyTableExport == null ? null : keyTableExport.get(); - final Object target = targetExport.get(); - final HierarchicalTableView targetExistingView = usedExisting - ? (HierarchicalTableView) target - : null; - final HierarchicalTable targetHierarchicalTable = usedExisting - ? targetExistingView.getHierarchicalTable() - : (HierarchicalTable) target; - - authWiring.checkPermissionView(session.getAuthContext(), request, keyTable == null - ? List.of(targetHierarchicalTable.getSource()) - : List.of(keyTable, targetHierarchicalTable.getSource())); - - final HierarchicalTableView result; - if (usedExisting) { - if (keyTable != null) { - result = HierarchicalTableView.makeFromExistingView( - targetExistingView, - keyTable, - request.getExpansions().hasKeyTableActionColumn() - ? ColumnName.of(request.getExpansions().getKeyTableActionColumn()) - : null); - } else { - result = HierarchicalTableView.makeFromExistingView(targetExistingView); - } + final SessionState session = sessionService.getCurrentSession(); + + final SessionState.ExportBuilder resultExportBuilder = + session.newExport(request.getResultViewId(), "view.resultViewId"); + + final boolean usedExisting; + final Ticket targetTicket; + switch (request.getTargetCase()) { + case HIERARCHICAL_TABLE_ID: + usedExisting = false; + targetTicket = request.getHierarchicalTableId(); + break; + case EXISTING_VIEW_ID: + usedExisting = true; + targetTicket = request.getExistingViewId(); + break; + case TARGET_NOT_SET: + default: + throw new IllegalStateException(); + } + final SessionState.ExportObject targetExport = ticketRouter.resolve( + session, targetTicket, "view.target"); + + final SessionState.ExportObject
keyTableExport; + if (request.hasExpansions()) { + keyTableExport = ticketRouter.resolve( + session, request.getExpansions().getKeyTableId(), "view.expansions.keyTableId"); + resultExportBuilder.require(targetExport, keyTableExport); + } else { + keyTableExport = null; + resultExportBuilder.require(targetExport); + } + + resultExportBuilder.onError(responseObserver) + .submit(() -> { + final Table keyTable = keyTableExport == null ? null : keyTableExport.get(); + final Object target = targetExport.get(); + final HierarchicalTableView targetExistingView = usedExisting + ? (HierarchicalTableView) target + : null; + final HierarchicalTable targetHierarchicalTable = usedExisting + ? targetExistingView.getHierarchicalTable() + : (HierarchicalTable) target; + + authWiring.checkPermissionView(session.getAuthContext(), request, keyTable == null + ? List.of(targetHierarchicalTable.getSource()) + : List.of(keyTable, targetHierarchicalTable.getSource())); + + final HierarchicalTableView result; + if (usedExisting) { + if (keyTable != null) { + result = HierarchicalTableView.makeFromExistingView( + targetExistingView, + keyTable, + request.getExpansions().hasKeyTableActionColumn() + ? ColumnName.of(request.getExpansions().getKeyTableActionColumn()) + : null); } else { - if (keyTable != null) { - result = HierarchicalTableView.makeFromHierarchicalTable( - targetHierarchicalTable, - keyTable, - request.getExpansions().hasKeyTableActionColumn() - ? ColumnName.of(request.getExpansions().getKeyTableActionColumn()) - : null); - } else { - result = HierarchicalTableView.makeFromHierarchicalTable(targetHierarchicalTable); - } + result = HierarchicalTableView.makeFromExistingView(targetExistingView); + } + } else { + if (keyTable != null) { + result = HierarchicalTableView.makeFromHierarchicalTable( + targetHierarchicalTable, + keyTable, + request.getExpansions().hasKeyTableActionColumn() + ? ColumnName.of(request.getExpansions().getKeyTableActionColumn()) + : null); + } else { + result = HierarchicalTableView.makeFromHierarchicalTable(targetHierarchicalTable); } + } - final HierarchicalTableView transformedResult = authTransformation.transform(result); - safelyComplete(responseObserver, HierarchicalTableViewResponse.getDefaultInstance()); - return transformedResult; - }); - }); + final HierarchicalTableView transformedResult = authTransformation.transform(result); + safelyComplete(responseObserver, HierarchicalTableViewResponse.getDefaultInstance()); + return transformedResult; + }); } private static void validate(@NotNull final HierarchicalTableViewRequest request) { @@ -414,9 +409,8 @@ private static void validate(@NotNull final HierarchicalTableViewRequest request Assert.statementNeverExecuted("No target specified, despite prior validation"); break; default: - throw GrpcUtil.statusRuntimeException(Code.INTERNAL, - String.format("%s has unexpected target case %s", - request.getDescriptorForType().getFullName(), request.getTargetCase())); + throw Exceptions.statusRuntimeException(Code.INTERNAL, String.format("%s has unexpected target case %s", + request.getDescriptorForType().getFullName(), request.getTargetCase())); } } @@ -424,30 +418,28 @@ private static void validate(@NotNull final HierarchicalTableViewRequest request public void exportSource( @NotNull final HierarchicalTableSourceExportRequest request, @NotNull final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - validate(request); - - final SessionState session = sessionService.getCurrentSession(); - - final SessionState.ExportObject hierarchicalTableExport = ticketRouter.resolve( - session, request.getHierarchicalTableId(), "exportSource.hierarchicalTableId"); - - session.newExport(request.getResultTableId(), "exportSource.resultTableId") - .require(hierarchicalTableExport) - .onError(responseObserver) - .submit(() -> { - final HierarchicalTable hierarchicalTable = hierarchicalTableExport.get(); - - final Table result = hierarchicalTable.getSource(); - authWiring.checkPermissionExportSource(session.getAuthContext(), request, List.of(result)); - - final Table transformedResult = authTransformation.transform(result); - final ExportedTableCreationResponse response = - ExportUtil.buildTableCreationResponse(request.getResultTableId(), transformedResult); - safelyComplete(responseObserver, response); - return transformedResult; - }); - }); + validate(request); + + final SessionState session = sessionService.getCurrentSession(); + + final SessionState.ExportObject> hierarchicalTableExport = ticketRouter.resolve( + session, request.getHierarchicalTableId(), "exportSource.hierarchicalTableId"); + + session.newExport(request.getResultTableId(), "exportSource.resultTableId") + .require(hierarchicalTableExport) + .onError(responseObserver) + .submit(() -> { + final HierarchicalTable hierarchicalTable = hierarchicalTableExport.get(); + + final Table result = hierarchicalTable.getSource(); + authWiring.checkPermissionExportSource(session.getAuthContext(), request, List.of(result)); + + final Table transformedResult = authTransformation.transform(result); + final ExportedTableCreationResponse response = + ExportUtil.buildTableCreationResponse(request.getResultTableId(), transformedResult); + safelyComplete(responseObserver, response); + return transformedResult; + }); } private static void validate(@NotNull final HierarchicalTableSourceExportRequest request) { diff --git a/server/src/main/java/io/deephaven/server/hierarchicaltable/HierarchicalTableViewSubscription.java b/server/src/main/java/io/deephaven/server/hierarchicaltable/HierarchicalTableViewSubscription.java index c69baa9e74a..a298ed7d791 100644 --- a/server/src/main/java/io/deephaven/server/hierarchicaltable/HierarchicalTableViewSubscription.java +++ b/server/src/main/java/io/deephaven/server/hierarchicaltable/HierarchicalTableViewSubscription.java @@ -22,6 +22,7 @@ import io.deephaven.extensions.barrage.util.HierarchicalTableSchemaUtil; import io.deephaven.internal.log.LoggerFactory; import io.deephaven.io.logger.Logger; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.util.Scheduler; import io.deephaven.time.DateTime; import io.deephaven.util.SafeCloseable; @@ -371,7 +372,7 @@ public void setViewport( if (viewportColumns != null) { if (viewportColumns.length() > view.getHierarchicalTable().getAvailableColumnDefinitions().size()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, String.format( + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, String.format( "Requested columns out of range: length=%d, available length=%d", viewportColumns.length(), view.getHierarchicalTable().getAvailableColumnDefinitions().size())); @@ -379,17 +380,17 @@ public void setViewport( } if (viewportRows != null) { if (!viewportRows.isContiguous()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "HierarchicalTableView subscriptions only support contiguous viewports"); } if (viewportRows.size() > LARGEST_POOLED_CHUNK_CAPACITY) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, String.format( + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, String.format( "HierarchicalTableView subscriptions only support viewport size up to %d rows, requested %d rows", LARGEST_POOLED_CHUNK_CAPACITY, viewportRows.size())); } } if (reverseViewport) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "HierarchicalTableView subscriptions do not support reverse viewports"); } diff --git a/server/src/main/java/io/deephaven/server/notebook/FilesystemStorageServiceGrpcImpl.java b/server/src/main/java/io/deephaven/server/notebook/FilesystemStorageServiceGrpcImpl.java index b560d991c50..8f9405da2cd 100644 --- a/server/src/main/java/io/deephaven/server/notebook/FilesystemStorageServiceGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/notebook/FilesystemStorageServiceGrpcImpl.java @@ -1,3 +1,6 @@ +/** + * Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending + */ package io.deephaven.server.notebook; import com.google.common.hash.HashFunction; @@ -25,8 +28,10 @@ import io.deephaven.proto.backplane.grpc.SaveFileRequest; import io.deephaven.proto.backplane.grpc.SaveFileResponse; import io.deephaven.proto.backplane.grpc.StorageServiceGrpc; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionService; import io.grpc.stub.StreamObserver; +import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import javax.inject.Singleton; @@ -104,62 +109,64 @@ private Path resolveOrThrow(String incomingPath) { return resolved; } } - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Invalid path: " + incomingPath); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Invalid path: " + incomingPath); } private void requireNotRoot(Path path, String message) { if (path.equals(root)) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, message); + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, message); } } @Override - public void listItems(ListItemsRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - sessionService.getCurrentSession(); - - ListItemsResponse.Builder builder = ListItemsResponse.newBuilder(); - PathMatcher matcher = - request.hasFilterGlob() ? createPathFilter(request.getFilterGlob()) : ignore -> true; - Path dir = resolveOrThrow(request.getPath()); - try (Stream list = Files.list(dir)) { - for (Path p : (Iterable) list::iterator) { - if (!matcher.matches(dir.relativize(p))) { - continue; - } - BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class); - boolean isDirectory = attrs.isDirectory(); - ItemInfo.Builder info = ItemInfo.newBuilder() - .setPath("/" + root.relativize(p)); - if (isDirectory) { - info.setType(ItemType.DIRECTORY); - } else { - info.setSize(attrs.size()) - .setEtag(hash(p))// Note, there is a potential race here between the size and the hash - .setType(ItemType.FILE); - } - builder.addItems(info.build()); + public void listItems( + @NotNull final ListItemsRequest request, + @NotNull final StreamObserver responseObserver) { + sessionService.getCurrentSession(); + + ListItemsResponse.Builder builder = ListItemsResponse.newBuilder(); + PathMatcher matcher = + request.hasFilterGlob() ? createPathFilter(request.getFilterGlob()) : ignore -> true; + Path dir = resolveOrThrow(request.getPath()); + try (Stream list = Files.list(dir)) { + for (Path p : (Iterable) list::iterator) { + if (!matcher.matches(dir.relativize(p))) { + continue; } - } catch (NoSuchFileException noSuchFileException) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "Directory does not exist"); + BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class); + boolean isDirectory = attrs.isDirectory(); + ItemInfo.Builder info = ItemInfo.newBuilder() + .setPath("/" + root.relativize(p)); + if (isDirectory) { + info.setType(ItemType.DIRECTORY); + } else { + info.setSize(attrs.size()) + .setEtag(hash(p))// Note, there is a potential race here between the size and the hash + .setType(ItemType.FILE); + } + builder.addItems(info.build()); } - responseObserver.onNext(builder.build()); - responseObserver.onCompleted(); - }); + } catch (NoSuchFileException noSuchFileException) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Directory does not exist"); + } catch (IOException ioe) { + throw GrpcUtil.securelyWrapError(log, ioe); + } + responseObserver.onNext(builder.build()); + responseObserver.onCompleted(); } private static PathMatcher createPathFilter(String filterGlob) { if (filterGlob.contains("**")) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Bad glob, only single `*`s are supported"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Bad glob, only single `*`s are supported"); } if (filterGlob.contains("/")) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Bad glob, only the same directory can be checked"); } try { return FileSystems.getDefault().getPathMatcher("glob:" + filterGlob); } catch (PatternSyntaxException e) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Bad glob, can't parse expression: " + e.getMessage()); } } @@ -173,123 +180,135 @@ private static String hash(Path path) throws IOException { } @Override - public void fetchFile(FetchFileRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - sessionService.getCurrentSession(); - - final byte[] bytes; - final String etag; - try { - bytes = Files.readAllBytes(resolveOrThrow(request.getPath())); - // Hash those bytes, as long as we are reading them to send, since we want the hash to be consistent - // with the contents we send. This avoids a race condition, at the cost of requiring that the server - // always read the full bytes - etag = ByteSource.wrap(bytes).hash(HASH_FUNCTION).toString(); - } catch (NoSuchFileException noSuchFileException) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "File does not exist"); - } - final FetchFileResponse.Builder response = FetchFileResponse.newBuilder(); - response.setEtag(etag); - if (!request.hasEtag() || !etag.equals(request.getEtag())) { - response.setContents(ByteString.copyFrom(bytes)); - } - responseObserver.onNext(response.build()); - responseObserver.onCompleted(); - }); + public void fetchFile( + @NotNull final FetchFileRequest request, + @NotNull final StreamObserver responseObserver) { + sessionService.getCurrentSession(); + + final byte[] bytes; + final String etag; + try { + bytes = Files.readAllBytes(resolveOrThrow(request.getPath())); + // Hash those bytes, as long as we are reading them to send, since we want the hash to be consistent + // with the contents we send. This avoids a race condition, at the cost of requiring that the server + // always read the full bytes + etag = ByteSource.wrap(bytes).hash(HASH_FUNCTION).toString(); + } catch (NoSuchFileException noSuchFileException) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "File does not exist"); + } catch (IOException ioe) { + throw GrpcUtil.securelyWrapError(log, ioe); + } + + final FetchFileResponse.Builder response = FetchFileResponse.newBuilder(); + response.setEtag(etag); + if (!request.hasEtag() || !etag.equals(request.getEtag())) { + response.setContents(ByteString.copyFrom(bytes)); + } + responseObserver.onNext(response.build()); + responseObserver.onCompleted(); } @Override - public void saveFile(SaveFileRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - sessionService.getCurrentSession(); - - Path path = resolveOrThrow(request.getPath()); - requireNotRoot(path, "Can't overwrite the root directory"); - StandardOpenOption option = - request.getAllowOverwrite() ? StandardOpenOption.TRUNCATE_EXISTING : StandardOpenOption.CREATE_NEW; - - byte[] bytes = request.getContents().toByteArray(); - String etag = ByteSource.wrap(bytes).hash(HASH_FUNCTION).toString(); - try { - Files.write(path, bytes, StandardOpenOption.CREATE, option); - } catch (FileAlreadyExistsException alreadyExistsException) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "File already exists"); - } catch (NoSuchFileException noSuchFileException) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "Directory does not exist"); - } - responseObserver.onNext(SaveFileResponse.newBuilder().setEtag(etag).build()); - responseObserver.onCompleted(); - }); + public void saveFile( + @NotNull final SaveFileRequest request, + @NotNull final StreamObserver responseObserver) { + sessionService.getCurrentSession(); + + Path path = resolveOrThrow(request.getPath()); + requireNotRoot(path, "Can't overwrite the root directory"); + StandardOpenOption option = + request.getAllowOverwrite() ? StandardOpenOption.TRUNCATE_EXISTING : StandardOpenOption.CREATE_NEW; + + String etag; + byte[] bytes = request.getContents().toByteArray(); + try { + etag = ByteSource.wrap(bytes).hash(HASH_FUNCTION).toString(); + Files.write(path, bytes, StandardOpenOption.CREATE, option); + } catch (FileAlreadyExistsException alreadyExistsException) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "File already exists"); + } catch (NoSuchFileException noSuchFileException) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Directory does not exist"); + } catch (IOException ioe) { + throw GrpcUtil.securelyWrapError(log, ioe); + } + + responseObserver.onNext(SaveFileResponse.newBuilder().setEtag(etag).build()); + responseObserver.onCompleted(); } @Override - public void moveItem(MoveItemRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - sessionService.getCurrentSession(); - - Path source = resolveOrThrow(request.getOldPath()); - Path target = resolveOrThrow(request.getNewPath()); - requireNotRoot(target, "Can't overwrite the root directory"); - - StandardCopyOption[] options = - request.getAllowOverwrite() ? new StandardCopyOption[] {StandardCopyOption.REPLACE_EXISTING} - : new StandardCopyOption[0]; - - try { - Files.move(source, target, options); - } catch (NoSuchFileException noSuchFileException) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "File does not exist, cannot rename"); - } catch (FileAlreadyExistsException alreadyExistsException) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, - "File already exists, cannot rename to replace"); - } catch (DirectoryNotEmptyException directoryNotEmptyException) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "Cannot replace non-empty directory"); - } - responseObserver.onNext(MoveItemResponse.getDefaultInstance()); - responseObserver.onCompleted(); - }); + public void moveItem( + @NotNull final MoveItemRequest request, + @NotNull final StreamObserver responseObserver) { + sessionService.getCurrentSession(); + + Path source = resolveOrThrow(request.getOldPath()); + Path target = resolveOrThrow(request.getNewPath()); + requireNotRoot(target, "Can't overwrite the root directory"); + + StandardCopyOption[] options = + request.getAllowOverwrite() ? new StandardCopyOption[] {StandardCopyOption.REPLACE_EXISTING} + : new StandardCopyOption[0]; + + try { + Files.move(source, target, options); + } catch (NoSuchFileException noSuchFileException) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "File does not exist, cannot rename"); + } catch (FileAlreadyExistsException alreadyExistsException) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, + "File already exists, cannot rename to replace"); + } catch (DirectoryNotEmptyException directoryNotEmptyException) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Cannot replace non-empty directory"); + } catch (IOException ioe) { + throw GrpcUtil.securelyWrapError(log, ioe); + } + responseObserver.onNext(MoveItemResponse.getDefaultInstance()); + responseObserver.onCompleted(); } @Override - public void createDirectory(CreateDirectoryRequest request, - StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - sessionService.getCurrentSession(); - - Path dir = resolveOrThrow(request.getPath()); - requireNotRoot(dir, "Can't overwrite the root directory"); - - try { - Files.createDirectory(dir); - } catch (FileAlreadyExistsException fileAlreadyExistsException) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, - "Something already exists with that name"); - } catch (NoSuchFileException noSuchFileException) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, - "Can't create directory, parent directory doesn't exist"); - } - responseObserver.onNext(CreateDirectoryResponse.getDefaultInstance()); - responseObserver.onCompleted(); - }); + public void createDirectory( + @NotNull final CreateDirectoryRequest request, + @NotNull final StreamObserver responseObserver) { + sessionService.getCurrentSession(); + + Path dir = resolveOrThrow(request.getPath()); + requireNotRoot(dir, "Can't overwrite the root directory"); + + try { + Files.createDirectory(dir); + } catch (FileAlreadyExistsException fileAlreadyExistsException) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, + "Something already exists with that name"); + } catch (NoSuchFileException noSuchFileException) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, + "Can't create directory, parent directory doesn't exist"); + } catch (IOException ioe) { + throw GrpcUtil.securelyWrapError(log, ioe); + } + responseObserver.onNext(CreateDirectoryResponse.getDefaultInstance()); + responseObserver.onCompleted(); } @Override - public void deleteItem(DeleteItemRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - sessionService.getCurrentSession(); - - Path path = resolveOrThrow(request.getPath()); - requireNotRoot(path, "Can't delete the root directory"); - - try { - Files.delete(path); - } catch (NoSuchFileException noSuchFileException) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "Cannot delete, file does not exists"); - } catch (DirectoryNotEmptyException directoryNotEmptyException) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "Cannot delete non-empty directory"); - } - responseObserver.onNext(DeleteItemResponse.getDefaultInstance()); - responseObserver.onCompleted(); - }); + public void deleteItem( + @NotNull final DeleteItemRequest request, + @NotNull final StreamObserver responseObserver) { + sessionService.getCurrentSession(); + + Path path = resolveOrThrow(request.getPath()); + requireNotRoot(path, "Can't delete the root directory"); + + try { + Files.delete(path); + } catch (NoSuchFileException noSuchFileException) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Cannot delete, file does not exists"); + } catch (DirectoryNotEmptyException directoryNotEmptyException) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Cannot delete non-empty directory"); + } catch (IOException ioe) { + throw GrpcUtil.securelyWrapError(log, ioe); + } + responseObserver.onNext(DeleteItemResponse.getDefaultInstance()); + responseObserver.onCompleted(); } } diff --git a/server/src/main/java/io/deephaven/server/object/ObjectServiceGrpcImpl.java b/server/src/main/java/io/deephaven/server/object/ObjectServiceGrpcImpl.java index 951ecfaa6ce..4ebfdd47949 100644 --- a/server/src/main/java/io/deephaven/server/object/ObjectServiceGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/object/ObjectServiceGrpcImpl.java @@ -6,7 +6,6 @@ import com.google.protobuf.ByteStringAccess; import com.google.rpc.Code; import io.deephaven.extensions.barrage.util.BarrageProtoUtil.ExposedByteArrayOutputStream; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.internal.log.LoggerFactory; import io.deephaven.io.logger.Logger; import io.deephaven.plugin.type.ObjectType; @@ -18,11 +17,13 @@ import io.deephaven.proto.backplane.grpc.FetchObjectResponse.Builder; import io.deephaven.proto.backplane.grpc.ObjectServiceGrpc; import io.deephaven.proto.backplane.grpc.TypedTicket; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionService; import io.deephaven.server.session.SessionState; import io.deephaven.server.session.SessionState.ExportObject; import io.deephaven.server.session.TicketRouter; import io.grpc.stub.StreamObserver; +import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import java.io.IOException; @@ -50,29 +51,29 @@ public ObjectServiceGrpcImpl(SessionService sessionService, TicketRouter ticketR } @Override - public void fetchObject(FetchObjectRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - final String type = request.getSourceId().getType(); - if (type.isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "No type supplied"); - } - if (request.getSourceId().getTicket().getTicket().isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "No ticket supplied"); - } - final SessionState.ExportObject object = ticketRouter.resolve( - session, request.getSourceId().getTicket(), "sourceId"); - session.nonExport() - .require(object) - .onError(responseObserver) - .submit(() -> { - final Object o = object.get(); - final FetchObjectResponse response = serialize(type, session, o); - responseObserver.onNext(response); - responseObserver.onCompleted(); - return null; - }); - }); + public void fetchObject( + @NotNull final FetchObjectRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + final String type = request.getSourceId().getType(); + if (type.isEmpty()) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "No type supplied"); + } + if (request.getSourceId().getTicket().getTicket().isEmpty()) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "No ticket supplied"); + } + final SessionState.ExportObject object = ticketRouter.resolve( + session, request.getSourceId().getTicket(), "sourceId"); + session.nonExport() + .require(object) + .onError(responseObserver) + .submit(() -> { + final Object o = object.get(); + final FetchObjectResponse response = serialize(type, session, o); + responseObserver.onNext(response); + responseObserver.onCompleted(); + return null; + }); } private FetchObjectResponse serialize(String expectedType, SessionState state, Object object) throws IOException { @@ -80,12 +81,12 @@ private FetchObjectResponse serialize(String expectedType, SessionState state, O // TODO(deephaven-core#1872): Optimize ObjectTypeLookup final Optional o = objectTypeLookup.findObjectType(object); if (o.isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, String.format("No ObjectType found, expected type '%s'", expectedType)); } final ObjectType objectType = o.get(); if (!expectedType.equals(objectType.name())) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, String.format( + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, String.format( "Unexpected ObjectType, expected type '%s', actual type '%s'", expectedType, objectType.name())); } final ExportCollector exportCollector = new ExportCollector(state); diff --git a/server/src/main/java/io/deephaven/server/partitionedtable/PartitionedTableServiceGrpcImpl.java b/server/src/main/java/io/deephaven/server/partitionedtable/PartitionedTableServiceGrpcImpl.java index 6ea1646a285..175c08f3d70 100644 --- a/server/src/main/java/io/deephaven/server/partitionedtable/PartitionedTableServiceGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/partitionedtable/PartitionedTableServiceGrpcImpl.java @@ -1,3 +1,6 @@ +/** + * Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending + */ package io.deephaven.server.partitionedtable; import com.google.rpc.Code; @@ -5,7 +8,6 @@ import io.deephaven.engine.table.PartitionedTable; import io.deephaven.engine.table.Table; import io.deephaven.engine.updategraph.UpdateGraphProcessor; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.internal.log.LoggerFactory; import io.deephaven.io.logger.Logger; import io.deephaven.proto.backplane.grpc.ExportedTableCreationResponse; @@ -14,12 +16,14 @@ import io.deephaven.proto.backplane.grpc.PartitionByRequest; import io.deephaven.proto.backplane.grpc.PartitionByResponse; import io.deephaven.proto.backplane.grpc.PartitionedTableServiceGrpc; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.auth.AuthorizationProvider; import io.deephaven.server.session.SessionService; import io.deephaven.server.session.SessionState; import io.deephaven.server.session.TicketResolverBase; import io.deephaven.server.session.TicketRouter; import io.grpc.stub.StreamObserver; +import org.jetbrains.annotations.NotNull; import javax.inject.Inject; @@ -53,120 +57,118 @@ public PartitionedTableServiceGrpcImpl( } @Override - public void partitionBy(PartitionByRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - - SessionState.ExportObject
targetTable = - ticketRouter.resolve(session, request.getTableId(), "tableId"); - - session.newExport(request.getResultId(), "resultId") - .require(targetTable) - .onError(responseObserver) - .submit(() -> { - authWiring.checkPermissionPartitionBy(session.getAuthContext(), request, - Collections.singletonList(targetTable.get())); - PartitionedTable partitionedTable = targetTable.get().partitionBy(request.getDropKeys(), - request.getKeyColumnNamesList().toArray(String[]::new)); - safelyComplete(responseObserver, PartitionByResponse.getDefaultInstance()); - return partitionedTable; - }); - - }); + public void partitionBy( + @NotNull final PartitionByRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + + SessionState.ExportObject
targetTable = + ticketRouter.resolve(session, request.getTableId(), "tableId"); + + session.newExport(request.getResultId(), "resultId") + .require(targetTable) + .onError(responseObserver) + .submit(() -> { + authWiring.checkPermissionPartitionBy(session.getAuthContext(), request, + Collections.singletonList(targetTable.get())); + PartitionedTable partitionedTable = targetTable.get().partitionBy(request.getDropKeys(), + request.getKeyColumnNamesList().toArray(String[]::new)); + safelyComplete(responseObserver, PartitionByResponse.getDefaultInstance()); + return partitionedTable; + }); } @Override - public void merge(MergeRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - - SessionState.ExportObject partitionedTable = - ticketRouter.resolve(session, request.getPartitionedTable(), "partitionedTable"); - - session.newExport(request.getResultId(), "resultId") - .require(partitionedTable) - .onError(responseObserver) - .submit(() -> { - authWiring.checkPermissionMerge(session.getAuthContext(), request, - Collections.singletonList(partitionedTable.get().table())); - Table merged; - if (partitionedTable.get().table().isRefreshing()) { - merged = updateGraphProcessor.sharedLock() - .computeLocked(partitionedTable.get()::merge); - } else { - merged = partitionedTable.get().merge(); - } - merged = authorizationTransformation.transform(merged); - final ExportedTableCreationResponse response = - buildTableCreationResponse(request.getResultId(), merged); - safelyComplete(responseObserver, response); - return merged; - }); - }); - + public void merge( + @NotNull final MergeRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + + SessionState.ExportObject partitionedTable = + ticketRouter.resolve(session, request.getPartitionedTable(), "partitionedTable"); + + session.newExport(request.getResultId(), "resultId") + .require(partitionedTable) + .onError(responseObserver) + .submit(() -> { + authWiring.checkPermissionMerge(session.getAuthContext(), request, + Collections.singletonList(partitionedTable.get().table())); + Table merged; + if (partitionedTable.get().table().isRefreshing()) { + merged = updateGraphProcessor.sharedLock() + .computeLocked(partitionedTable.get()::merge); + } else { + merged = partitionedTable.get().merge(); + } + merged = authorizationTransformation.transform(merged); + final ExportedTableCreationResponse response = + buildTableCreationResponse(request.getResultId(), merged); + safelyComplete(responseObserver, response); + return merged; + }); } @Override - public void getTable(GetTableRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - - SessionState.ExportObject partitionedTable = - ticketRouter.resolve(session, request.getPartitionedTable(), "partitionedTable"); - SessionState.ExportObject
keys = - ticketRouter.resolve(session, request.getKeyTableTicket(), "keyTableTicket"); - - session.newExport(request.getResultId(), "resultId") - .require(partitionedTable, keys) - .onError(responseObserver) - .submit(() -> { - Table table; - Table keyTable = keys.get(); - authWiring.checkPermissionGetTable(session.getAuthContext(), request, - List.of(partitionedTable.get().table(), keyTable)); - if (!keyTable.isRefreshing()) { + public void getTable( + @NotNull final GetTableRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + + SessionState.ExportObject partitionedTable = + ticketRouter.resolve(session, request.getPartitionedTable(), "partitionedTable"); + SessionState.ExportObject
keys = + ticketRouter.resolve(session, request.getKeyTableTicket(), "keyTableTicket"); + + session.newExport(request.getResultId(), "resultId") + .require(partitionedTable, keys) + .onError(responseObserver) + .submit(() -> { + Table table; + Table keyTable = keys.get(); + authWiring.checkPermissionGetTable(session.getAuthContext(), request, + List.of(partitionedTable.get().table(), keyTable)); + if (!keyTable.isRefreshing()) { + long keyTableSize = keyTable.size(); + if (keyTableSize != 1) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Provided key table does not have one row, instead has " + keyTableSize); + } + long row = keyTable.getRowSet().firstRowKey(); + Object[] values = + partitionedTable.get().keyColumnNames().stream() + .map(keyTable::getColumnSource) + .map(cs -> cs.get(row)) + .toArray(); + table = partitionedTable.get().constituentFor(values); + } else { + table = updateGraphProcessor.sharedLock().computeLocked(() -> { long keyTableSize = keyTable.size(); if (keyTableSize != 1) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Provided key table does not have one row, instead has " + keyTableSize); } - long row = keyTable.getRowSet().firstRowKey(); - Object[] values = - partitionedTable.get().keyColumnNames().stream() - .map(keyTable::getColumnSource) - .map(cs -> cs.get(row)) - .toArray(); - table = partitionedTable.get().constituentFor(values); - } else { - table = updateGraphProcessor.sharedLock().computeLocked(() -> { - long keyTableSize = keyTable.size(); - if (keyTableSize != 1) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Provided key table does not have one row, instead has " + keyTableSize); + Table requestedRow = partitionedTable.get().table().whereIn(keyTable, + partitionedTable.get().keyColumnNames().toArray(String[]::new)); + if (requestedRow.size() != 1) { + if (requestedRow.isEmpty()) { + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, + "Key matches zero rows in the partitioned table"); + } else { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, + "Key matches more than one entry in the partitioned table: " + + requestedRow.size()); } - Table requestedRow = partitionedTable.get().table().whereIn(keyTable, - partitionedTable.get().keyColumnNames().toArray(String[]::new)); - if (requestedRow.size() != 1) { - if (requestedRow.isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, - "Key matches zero rows in the partitioned table"); - } else { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, - "Key matches more than one entry in the partitioned table: " - + requestedRow.size()); - } - } - return (Table) requestedRow - .getColumnSource(partitionedTable.get().constituentColumnName()) - .get(requestedRow.getRowSet().firstRowKey()); - }); - } - table = authorizationTransformation.transform(table); - final ExportedTableCreationResponse response = - buildTableCreationResponse(request.getResultId(), table); - safelyComplete(responseObserver, response); - return table; - }); - }); + } + return (Table) requestedRow + .getColumnSource(partitionedTable.get().constituentColumnName()) + .get(requestedRow.getRowSet().firstRowKey()); + }); + } + table = authorizationTransformation.transform(table); + final ExportedTableCreationResponse response = + buildTableCreationResponse(request.getResultId(), table); + safelyComplete(responseObserver, response); + return table; + }); } } diff --git a/server/src/main/java/io/deephaven/server/session/ExportTicketResolver.java b/server/src/main/java/io/deephaven/server/session/ExportTicketResolver.java index 22855cef6c1..6214b36cab0 100644 --- a/server/src/main/java/io/deephaven/server/session/ExportTicketResolver.java +++ b/server/src/main/java/io/deephaven/server/session/ExportTicketResolver.java @@ -5,8 +5,8 @@ import com.google.rpc.Code; import io.deephaven.engine.table.Table; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.flight.util.FlightExportTicketHelper; +import io.deephaven.proto.util.Exceptions; import io.deephaven.proto.util.ExportTicketHelper; import io.deephaven.server.auth.AuthorizationProvider; import org.apache.arrow.flight.impl.Flight; @@ -41,7 +41,7 @@ public String getLogNameFor(final ByteBuffer ticket, final String logId) { public SessionState.ExportObject flightInfoFor( @Nullable final SessionState session, final Flight.FlightDescriptor descriptor, final String logId) { if (session == null) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "Could not resolve '" + logId + "': no exports can exist without a session to search"); } @@ -54,7 +54,7 @@ public SessionState.ExportObject flightInfoFor( FlightExportTicketHelper.descriptorToFlightTicket(descriptor, logId)); } - throw GrpcUtil.statusRuntimeException(Code.NOT_FOUND, + throw Exceptions.statusRuntimeException(Code.NOT_FOUND, "Could not resolve '" + logId + "': flight '" + descriptor.toString() + " does not exist"); }); } @@ -68,7 +68,7 @@ public void forAllFlightInfo(@Nullable final SessionState session, final Consume public SessionState.ExportObject resolve( @Nullable final SessionState session, final ByteBuffer ticket, final String logId) { if (session == null) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "Could not resolve '" + logId + "': no exports can exist without an active session"); } @@ -79,7 +79,7 @@ public SessionState.ExportObject resolve( public SessionState.ExportObject resolve( @Nullable final SessionState session, final Flight.FlightDescriptor descriptor, final String logId) { if (session == null) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "Could not resolve '" + logId + "': no exports can exist without a session to search"); } diff --git a/server/src/main/java/io/deephaven/server/session/SessionModule.java b/server/src/main/java/io/deephaven/server/session/SessionModule.java index 031f3695f38..4bf591132bb 100644 --- a/server/src/main/java/io/deephaven/server/session/SessionModule.java +++ b/server/src/main/java/io/deephaven/server/session/SessionModule.java @@ -29,7 +29,7 @@ static BindableService bindSessionServiceGrpcImpl( @Binds @IntoSet ServerInterceptor bindSessionServiceInterceptor( - SessionServiceGrpcImpl.AuthServerInterceptor sessionServiceInterceptor); + SessionServiceGrpcImpl.SessionServiceInterceptor sessionServiceGrpcInterceptor); @Binds @IntoSet diff --git a/server/src/main/java/io/deephaven/server/session/SessionServiceGrpcImpl.java b/server/src/main/java/io/deephaven/server/session/SessionServiceGrpcImpl.java index 5d4bc9f3db2..1ff3bb4c0b0 100644 --- a/server/src/main/java/io/deephaven/server/session/SessionServiceGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/session/SessionServiceGrpcImpl.java @@ -7,6 +7,8 @@ import com.google.rpc.Code; import io.deephaven.auth.AuthContext; import io.deephaven.auth.AuthenticationException; +import io.deephaven.csv.util.MutableObject; +import io.deephaven.engine.liveness.LivenessScopeStack; import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.internal.log.LoggerFactory; import io.deephaven.io.logger.Logger; @@ -22,18 +24,23 @@ import io.deephaven.proto.backplane.grpc.SessionServiceGrpc; import io.deephaven.proto.backplane.grpc.TerminationNotificationRequest; import io.deephaven.proto.backplane.grpc.TerminationNotificationResponse; +import io.deephaven.proto.util.Exceptions; +import io.deephaven.util.FunctionalInterfaces; +import io.deephaven.util.SafeCloseable; import io.grpc.Context; -import io.grpc.Contexts; import io.grpc.ForwardingServerCall.SimpleForwardingServerCall; +import io.grpc.ForwardingServerCallListener; import io.grpc.Metadata; import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; import io.grpc.Status; +import io.grpc.StatusRuntimeException; import io.grpc.stub.ServerCallStreamObserver; import io.grpc.stub.StreamObserver; import org.apache.arrow.flight.auth.AuthConstants; import org.apache.arrow.flight.auth2.Auth2Constants; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.inject.Inject; @@ -65,137 +72,133 @@ public SessionServiceGrpcImpl( } @Override - public void newSession(final HandshakeRequest request, final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - // TODO: once jsapi is updated to use flight auth, then newSession can be deprecated or removed - final AuthContext authContext = new AuthContext.SuperUser(); - - final SessionState session = service.newSession(authContext); - responseObserver.onNext(HandshakeResponse.newBuilder() - .setMetadataHeader(ByteString.copyFromUtf8(DEEPHAVEN_SESSION_ID)) - .setSessionToken(session.getExpiration().getBearerTokenAsByteString()) - .setTokenDeadlineTimeMillis(session.getExpiration().deadlineMillis) - .setTokenExpirationDelayMillis(service.getExpirationDelayMs()) - .build()); - - responseObserver.onCompleted(); - }); + public void newSession( + @NotNull final HandshakeRequest request, + @NotNull final StreamObserver responseObserver) { + // TODO: once jsapi is updated to use flight auth, then newSession can be deprecated or removed + final AuthContext authContext = new AuthContext.SuperUser(); + + final SessionState session = service.newSession(authContext); + responseObserver.onNext(HandshakeResponse.newBuilder() + .setMetadataHeader(ByteString.copyFromUtf8(DEEPHAVEN_SESSION_ID)) + .setSessionToken(session.getExpiration().getBearerTokenAsByteString()) + .setTokenDeadlineTimeMillis(session.getExpiration().deadlineMillis) + .setTokenExpirationDelayMillis(service.getExpirationDelayMs()) + .build()); + + responseObserver.onCompleted(); } @Override - public void refreshSessionToken(final HandshakeRequest request, - final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - // TODO: once jsapi is updated to use flight auth, then newSession can be deprecated or removed - if (request.getAuthProtocol() != 0) { - responseObserver.onError( - GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Protocol version not allowed.")); - return; - } + public void refreshSessionToken( + @NotNull final HandshakeRequest request, + @NotNull final StreamObserver responseObserver) { + // TODO: once jsapi is updated to use flight auth, then newSession can be deprecated or removed + if (request.getAuthProtocol() != 0) { + responseObserver.onError( + Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Protocol version not allowed.")); + return; + } - final SessionState session = service.getCurrentSession(); - final SessionService.TokenExpiration expiration = service.refreshToken(session); + final SessionState session = service.getCurrentSession(); + final SessionService.TokenExpiration expiration = service.refreshToken(session); - responseObserver.onNext(HandshakeResponse.newBuilder() - .setMetadataHeader(ByteString.copyFromUtf8(DEEPHAVEN_SESSION_ID)) - .setSessionToken(expiration.getBearerTokenAsByteString()) - .setTokenDeadlineTimeMillis(expiration.deadlineMillis) - .setTokenExpirationDelayMillis(service.getExpirationDelayMs()) - .build()); + responseObserver.onNext(HandshakeResponse.newBuilder() + .setMetadataHeader(ByteString.copyFromUtf8(DEEPHAVEN_SESSION_ID)) + .setSessionToken(expiration.getBearerTokenAsByteString()) + .setTokenDeadlineTimeMillis(expiration.deadlineMillis) + .setTokenExpirationDelayMillis(service.getExpirationDelayMs()) + .build()); - responseObserver.onCompleted(); - }); + responseObserver.onCompleted(); } @Override - public void closeSession(final HandshakeRequest request, - final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - if (request.getAuthProtocol() != 0) { - responseObserver.onError( - GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Protocol version not allowed.")); - return; - } + public void closeSession( + @NotNull final HandshakeRequest request, + @NotNull final StreamObserver responseObserver) { + if (request.getAuthProtocol() != 0) { + responseObserver.onError( + Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Protocol version not allowed.")); + return; + } - final SessionState session = service.getCurrentSession(); - service.closeSession(session); - responseObserver.onNext(CloseSessionResponse.getDefaultInstance()); - responseObserver.onCompleted(); - }); + final SessionState session = service.getCurrentSession(); + service.closeSession(session); + responseObserver.onNext(CloseSessionResponse.getDefaultInstance()); + responseObserver.onCompleted(); } @Override - public void release(final ReleaseRequest request, final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = service.getCurrentSession(); - - if (!request.hasId()) { - responseObserver - .onError(GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Release ticket not supplied")); - return; - } - final SessionState.ExportObject export = session.getExportIfExists(request.getId(), "id"); - if (export == null) { - responseObserver.onError(GrpcUtil.statusRuntimeException(Code.UNAVAILABLE, "Export not yet defined")); - return; - } + public void release( + @NotNull final ReleaseRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = service.getCurrentSession(); + + if (!request.hasId()) { + responseObserver + .onError(Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Release ticket not supplied")); + return; + } + final SessionState.ExportObject export = session.getExportIfExists(request.getId(), "id"); + if (export == null) { + responseObserver.onError(Exceptions.statusRuntimeException(Code.UNAVAILABLE, "Export not yet defined")); + return; + } - // If the export is already in a terminal state, the implementation quietly ignores the request as there - // are no additional resources to release. - export.cancel(); - responseObserver.onNext(ReleaseResponse.getDefaultInstance()); - responseObserver.onCompleted(); - }); + // If the export is already in a terminal state, the implementation quietly ignores the request as there + // are no additional resources to release. + export.cancel(); + responseObserver.onNext(ReleaseResponse.getDefaultInstance()); + responseObserver.onCompleted(); } @Override - public void exportFromTicket(ExportRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = service.getCurrentSession(); - - if (!request.hasSourceId()) { - responseObserver - .onError(GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Source ticket not supplied")); - return; - } - if (!request.hasResultId()) { - responseObserver - .onError(GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Result ticket not supplied")); - return; - } + public void exportFromTicket( + @NotNull final ExportRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = service.getCurrentSession(); + + if (!request.hasSourceId()) { + responseObserver + .onError(Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Source ticket not supplied")); + return; + } + if (!request.hasResultId()) { + responseObserver + .onError(Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Result ticket not supplied")); + return; + } - final SessionState.ExportObject source = ticketRouter.resolve( - session, request.getSourceId(), "sourceId"); - session.newExport(request.getResultId(), "resultId") - .require(source) - .onError(responseObserver) - .submit(() -> { - GrpcUtil.safelyComplete(responseObserver, ExportResponse.getDefaultInstance()); - return source.get(); - }); - }); + final SessionState.ExportObject source = ticketRouter.resolve( + session, request.getSourceId(), "sourceId"); + session.newExport(request.getResultId(), "resultId") + .require(source) + .onError(responseObserver) + .submit(() -> { + GrpcUtil.safelyComplete(responseObserver, ExportResponse.getDefaultInstance()); + return source.get(); + }); } @Override - public void exportNotifications(final ExportNotificationRequest request, - final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = service.getCurrentSession(); - - session.addExportListener(responseObserver); - ((ServerCallStreamObserver) responseObserver).setOnCancelHandler(() -> { - session.removeExportListener(responseObserver); - }); + public void exportNotifications( + @NotNull final ExportNotificationRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = service.getCurrentSession(); + + session.addExportListener(responseObserver); + ((ServerCallStreamObserver) responseObserver).setOnCancelHandler(() -> { + session.removeExportListener(responseObserver); }); } @Override - public void terminationNotification(TerminationNotificationRequest request, - StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = service.getCurrentSession(); - service.addTerminationListener(session, responseObserver); - }); + public void terminationNotification( + @NotNull final TerminationNotificationRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = service.getCurrentSession(); + service.addTerminationListener(session, responseObserver); } public static void insertCallHeader(String key, String value) { @@ -264,13 +267,13 @@ private void addHeaders(final Metadata md) { } @Singleton - public static class AuthServerInterceptor implements ServerInterceptor { + public static class SessionServiceInterceptor implements ServerInterceptor { private final SessionService service; private static final Status authenticationDetailsInvalid = Status.UNAUTHENTICATED.withDescription("Authentication details invalid"); @Inject - public AuthServerInterceptor(final SessionService service) { + public SessionServiceInterceptor(final SessionService service) { this.service = service; } @@ -309,7 +312,108 @@ public ServerCall.Listener interceptCall(final ServerCall serverCall = new InterceptedCall<>(service, call, session); final Context context = Context.current().withValues( SESSION_CONTEXT_KEY, session, SESSION_CALL_KEY, serverCall); - return Contexts.interceptCall(context, serverCall, metadata, serverCallHandler); + + final SessionState finalSession = session; + + final MutableObject> listener = new MutableObject<>(); + rpcWrapper(serverCall, context, finalSession, () -> listener.setValue(new SessionServiceCallListener<>( + serverCallHandler.startCall(serverCall, metadata), serverCall, context, finalSession))); + if (listener.getValue() == null) { + return new ServerCall.Listener<>() {}; + } + return listener.getValue(); + } + } + + private static class SessionServiceCallListener extends + ForwardingServerCallListener.SimpleForwardingServerCallListener { + private final ServerCall call; + private final Context context; + private final SessionState session; + + public SessionServiceCallListener( + ServerCall.Listener delegate, + ServerCall call, + Context context, + SessionState session) { + super(delegate); + this.call = call; + this.context = context; + this.session = session; + } + + @Override + public void onMessage(ReqT message) { + rpcWrapper(call, context, session, () -> super.onMessage(message)); + } + + @Override + public void onHalfClose() { + rpcWrapper(call, context, session, super::onHalfClose); + } + + @Override + public void onCancel() { + rpcWrapper(call, context, session, super::onCancel); + } + + @Override + public void onComplete() { + rpcWrapper(call, context, session, super::onComplete); + } + + @Override + public void onReady() { + rpcWrapper(call, context, session, super::onReady); + } + } + + /** + * Utility to avoid errors escaping to the stream, to make sure the server log and client both see the message if + * there is an error, and if the error was not meant to propagate to a gRPC client, obfuscates it. + * + * @param call the gRPC call + * @param context the gRPC context to attach + * @param session the session that this gRPC call is associated with + * @param lambda the code to safely execute + */ + private static void rpcWrapper( + @NotNull final ServerCall call, + @NotNull final Context context, + @Nullable final SessionState session, + @NotNull final FunctionalInterfaces.ThrowingRunnable lambda) { + Context previous = context.attach(); + try (final SafeCloseable ignored1 = LivenessScopeStack.open(); + final SafeCloseable ignored2 = session == null ? null : session.getExecutionContext().open()) { + lambda.run(); + } catch (final StatusRuntimeException err) { + if (err.getStatus().equals(Status.UNAUTHENTICATED)) { + log.info().append("ignoring unauthenticated request").endl(); + } else { + log.error().append(err).endl(); + } + closeWithError(call, err); + } catch (final InterruptedException err) { + Thread.currentThread().interrupt(); + closeWithError(call, GrpcUtil.securelyWrapError(log, err, Code.UNAVAILABLE)); + } catch (final Throwable err) { + closeWithError(call, GrpcUtil.securelyWrapError(log, err)); + } finally { + context.detach(previous); + } + } + + private static void closeWithError( + @NotNull final ServerCall call, + @NotNull final StatusRuntimeException err) { + try { + Metadata metadata = Status.trailersFromThrowable(err); + if (metadata == null) { + metadata = new Metadata(); + } + call.close(Status.fromThrowable(err), metadata); + } catch (final Exception unexpectedErr) { + log.debug().append("Unanticipated gRPC Error: ").append(unexpectedErr).endl(); } } } diff --git a/server/src/main/java/io/deephaven/server/session/SessionState.java b/server/src/main/java/io/deephaven/server/session/SessionState.java index 9670fc3c63d..90fb40fea19 100644 --- a/server/src/main/java/io/deephaven/server/session/SessionState.java +++ b/server/src/main/java/io/deephaven/server/session/SessionState.java @@ -21,7 +21,6 @@ import io.deephaven.engine.tablelogger.QueryOperationPerformanceLogLogger; import io.deephaven.engine.tablelogger.QueryPerformanceLogLogger; import io.deephaven.engine.updategraph.DynamicNode; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.hash.KeyedIntObjectHash; import io.deephaven.hash.KeyedIntObjectHashMap; import io.deephaven.hash.KeyedIntObjectKey; @@ -31,6 +30,7 @@ import io.deephaven.proto.backplane.grpc.ExportNotification; import io.deephaven.proto.backplane.grpc.Ticket; import io.deephaven.proto.flight.util.FlightExportTicketHelper; +import io.deephaven.proto.util.Exceptions; import io.deephaven.proto.util.ExportTicketHelper; import io.deephaven.server.util.Scheduler; import io.deephaven.engine.context.ExecutionContext; @@ -201,7 +201,7 @@ protected void updateExpiration(@NotNull final SessionService.TokenExpiration ex } if (prevToken == null) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); } log.debug().append(logPrefix).append("token, expires at ") @@ -233,6 +233,13 @@ public AuthContext getAuthContext() { return authContext; } + /** + * @return the execution context for this session + */ + public ExecutionContext getExecutionContext() { + return executionContext; + } + /** * Grab the ExportObject for the provided ticket. * @@ -264,7 +271,7 @@ public ExportObject getExport(final Flight.Ticket ticket, final String lo @SuppressWarnings("unchecked") public ExportObject getExport(final int exportId) { if (isExpired()) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); } final ExportObject result; @@ -274,7 +281,7 @@ public ExportObject getExport(final int exportId) { result = (ExportObject) exportMap.get(exportId); if (result == null) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Export id " + exportId + " does not exist and cannot be used out-of-order!"); } } else { @@ -293,7 +300,7 @@ public ExportObject getExport(final int exportId) { @SuppressWarnings("unchecked") public ExportObject getExportIfExists(final int exportId) { if (isExpired()) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); } return (ExportObject) exportMap.get(exportId); @@ -320,7 +327,7 @@ public ExportObject getExportIfExists(final Ticket ticket, final String l */ public ExportObject newServerSideExport(final T export) { if (isExpired()) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); } final int exportId = SERVER_EXPORT_UPDATER.getAndDecrement(this); @@ -365,7 +372,7 @@ public ExportBuilder newExport(final Ticket ticket, final String logId) { @VisibleForTesting public ExportBuilder newExport(final int exportId) { if (isExpired()) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); } if (exportId <= 0) { throw new IllegalArgumentException("exportId's <= 0 are reserved for server allocation only"); @@ -380,7 +387,7 @@ public ExportBuilder newExport(final int exportId) { */ public ExportBuilder nonExport() { if (isExpired()) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); } return new ExportBuilder<>(NON_EXPORT_ID); } @@ -401,7 +408,7 @@ public void addOnCloseCallback(final Closeable onClose) { if (isExpired()) { // After the session has expired, nothing new can be added to the collection, so throw an exception (and // release the lock, allowing each item already in the collection to be released) - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); } onCloseCallbacks.add(onClose); } @@ -682,7 +689,7 @@ private synchronized void setWork(final Callable exportMain, final ExportErro */ public T get() { if (session != null && session.isExpired()) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); } // Note: an export may be released while still being a dependency of queued work; so let's make sure we're @@ -1040,7 +1047,7 @@ public void addExportListener(final StreamObserver observer) final ExportListener listener; synchronized (exportListeners) { if (isExpired()) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); } listener = new ExportListener(observer); @@ -1287,11 +1294,10 @@ public ExportBuilder onErrorHandler(final ExportErrorGrpcHandler errorHandler final String dependentStr = dependentExportId == null ? "" : (" (related parent export id: " + dependentExportId + ")"); if (cause == null) { - errorHandler.onError(GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + errorHandler.onError(Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Export in state " + resultState + dependentStr)); } else { - errorHandler.onError(GrpcUtil.statusRuntimeException( - Code.FAILED_PRECONDITION, + errorHandler.onError(Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Details Logged w/ID '" + errorContext + "'" + dependentStr)); } })); @@ -1376,7 +1382,7 @@ public int getIntKey(final ExportObject exportObject) { @Override public ExportObject newValue(final int key) { if (isExpired()) { - throw GrpcUtil.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, "session has expired"); } return new ExportObject<>(SessionState.this, key); diff --git a/server/src/main/java/io/deephaven/server/session/TicketRouter.java b/server/src/main/java/io/deephaven/server/session/TicketRouter.java index 28218a9d30d..4e5c4e7a617 100644 --- a/server/src/main/java/io/deephaven/server/session/TicketRouter.java +++ b/server/src/main/java/io/deephaven/server/session/TicketRouter.java @@ -6,12 +6,12 @@ import com.google.rpc.Code; import io.deephaven.engine.table.Table; import io.deephaven.extensions.barrage.util.BarrageUtil; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.hash.KeyedIntObjectHashMap; import io.deephaven.hash.KeyedIntObjectKey; import io.deephaven.hash.KeyedObjectHashMap; import io.deephaven.hash.KeyedObjectKey; import io.deephaven.proto.backplane.grpc.Ticket; +import io.deephaven.proto.util.Exceptions; import org.apache.arrow.flight.impl.Flight; import org.jetbrains.annotations.Nullable; @@ -50,7 +50,7 @@ public SessionState.ExportObject resolve( final ByteBuffer ticket, final String logId) { if (ticket.remaining() == 0) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "could not resolve '" + logId + "' it's an empty ticket"); } return getResolver(ticket.get(ticket.position()), logId).resolve(session, ticket, logId); @@ -235,7 +235,7 @@ public static Flight.FlightInfo getFlightInfo(final Table table, private TicketResolver getResolver(final byte route, final String logId) { final TicketResolver resolver = byteResolverMap.get(route); if (resolver == null) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': no resolver for route '" + route + "' (byte)"); } return resolver; @@ -243,18 +243,18 @@ private TicketResolver getResolver(final byte route, final String logId) { private TicketResolver getResolver(final Flight.FlightDescriptor descriptor, final String logId) { if (descriptor.getType() != Flight.FlightDescriptor.DescriptorType.PATH) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': flight descriptor is not a path"); } if (descriptor.getPathCount() <= 0) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': flight descriptor does not have route path"); } final String route = descriptor.getPath(0); final TicketResolver resolver = descriptorResolverMap.get(route); if (resolver == null) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "Could not resolve '" + logId + "': no resolver for route '" + route + "'"); } diff --git a/server/src/main/java/io/deephaven/server/table/ExportedTableUpdateListener.java b/server/src/main/java/io/deephaven/server/table/ExportedTableUpdateListener.java index 0e93661f174..bdb40d31505 100644 --- a/server/src/main/java/io/deephaven/server/table/ExportedTableUpdateListener.java +++ b/server/src/main/java/io/deephaven/server/table/ExportedTableUpdateListener.java @@ -10,7 +10,6 @@ import io.deephaven.engine.table.impl.InstrumentedTableUpdateListener; import io.deephaven.engine.table.impl.NotificationStepReceiver; import io.deephaven.engine.table.impl.SwapListener; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.hash.KeyedLongObjectHashMap; import io.deephaven.hash.KeyedLongObjectKey; import io.deephaven.internal.log.LoggerFactory; @@ -18,6 +17,7 @@ import io.deephaven.proto.backplane.grpc.ExportNotification; import io.deephaven.proto.backplane.grpc.ExportedTableUpdateMessage; import io.deephaven.proto.backplane.grpc.Ticket; +import io.deephaven.proto.util.Exceptions; import io.deephaven.proto.util.ExportTicketHelper; import io.deephaven.server.session.SessionState; import io.grpc.StatusRuntimeException; @@ -61,7 +61,7 @@ public ExportedTableUpdateListener( */ public void onNext(final ExportNotification notification) { if (isDestroyed) { - throw GrpcUtil.statusRuntimeException(Code.CANCELLED, "client cancelled the stream"); + throw Exceptions.statusRuntimeException(Code.CANCELLED, "client cancelled the stream"); } final Ticket ticket = notification.getTicket(); diff --git a/server/src/main/java/io/deephaven/server/table/inputtables/InputTableServiceGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/inputtables/InputTableServiceGrpcImpl.java index fc498294ac1..4a52597c444 100644 --- a/server/src/main/java/io/deephaven/server/table/inputtables/InputTableServiceGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/inputtables/InputTableServiceGrpcImpl.java @@ -17,10 +17,12 @@ import io.deephaven.proto.backplane.grpc.DeleteTableRequest; import io.deephaven.proto.backplane.grpc.DeleteTableResponse; import io.deephaven.proto.backplane.grpc.InputTableServiceGrpc; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionService; import io.deephaven.server.session.SessionState; import io.deephaven.server.session.TicketRouter; import io.grpc.stub.StreamObserver; +import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import java.io.IOException; @@ -45,101 +47,101 @@ public InputTableServiceGrpcImpl( } @Override - public void addTableToInputTable(AddTableRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - - SessionState.ExportObject
targetTable = - ticketRouter.resolve(session, request.getInputTable(), "inputTable"); - SessionState.ExportObject
tableToAddExport = - ticketRouter.resolve(session, request.getTableToAdd(), "tableToAdd"); - - session.nonExport() - .requiresSerialQueue() - .onError(responseObserver) - .require(targetTable, tableToAddExport) - .submit(() -> { - Object inputTable = targetTable.get().getAttribute(Table.INPUT_TABLE_ATTRIBUTE); - if (!(inputTable instanceof MutableInputTable)) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Table can't be used as an input table"); - } - - MutableInputTable mutableInputTable = (MutableInputTable) inputTable; - Table tableToAdd = tableToAddExport.get(); - - authWiring.checkPermissionAddTableToInputTable( - ExecutionContext.getContext().getAuthContext(), request, - List.of(targetTable.get(), tableToAdd)); - - // validate that the columns are compatible - try { - mutableInputTable.validateAddOrModify(tableToAdd); - } catch (TableDefinition.IncompatibleTableDefinitionException exception) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Provided tables's columns are not compatible: " + exception.getMessage()); - } - - // actually add the tables contents - try { - mutableInputTable.add(tableToAdd); - GrpcUtil.safelyComplete(responseObserver, AddTableResponse.getDefaultInstance()); - } catch (IOException ioException) { - throw GrpcUtil.statusRuntimeException(Code.DATA_LOSS, "Error adding table to input table"); - } - }); - }); + public void addTableToInputTable( + @NotNull final AddTableRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + + SessionState.ExportObject
targetTable = + ticketRouter.resolve(session, request.getInputTable(), "inputTable"); + SessionState.ExportObject
tableToAddExport = + ticketRouter.resolve(session, request.getTableToAdd(), "tableToAdd"); + + session.nonExport() + .requiresSerialQueue() + .onError(responseObserver) + .require(targetTable, tableToAddExport) + .submit(() -> { + Object inputTable = targetTable.get().getAttribute(Table.INPUT_TABLE_ATTRIBUTE); + if (!(inputTable instanceof MutableInputTable)) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Table can't be used as an input table"); + } + + MutableInputTable mutableInputTable = (MutableInputTable) inputTable; + Table tableToAdd = tableToAddExport.get(); + + authWiring.checkPermissionAddTableToInputTable( + ExecutionContext.getContext().getAuthContext(), request, + List.of(targetTable.get(), tableToAdd)); + + // validate that the columns are compatible + try { + mutableInputTable.validateAddOrModify(tableToAdd); + } catch (TableDefinition.IncompatibleTableDefinitionException exception) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Provided tables's columns are not compatible: " + exception.getMessage()); + } + + // actually add the tables contents + try { + mutableInputTable.add(tableToAdd); + GrpcUtil.safelyComplete(responseObserver, AddTableResponse.getDefaultInstance()); + } catch (IOException ioException) { + throw Exceptions.statusRuntimeException(Code.DATA_LOSS, + "Error adding table to input table"); + } + }); } @Override - public void deleteTableFromInputTable(DeleteTableRequest request, - StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - - SessionState.ExportObject
targetTable = - ticketRouter.resolve(session, request.getInputTable(), "inputTable"); - SessionState.ExportObject
tableToDeleteExport = - ticketRouter.resolve(session, request.getTableToRemove(), "tableToDelete"); - - session.nonExport() - .requiresSerialQueue() - .onError(responseObserver) - .require(targetTable, tableToDeleteExport) - .submit(() -> { - Object inputTable = targetTable.get().getAttribute(Table.INPUT_TABLE_ATTRIBUTE); - if (!(inputTable instanceof MutableInputTable)) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Table can't be used as an input table"); - } - - MutableInputTable mutableInputTable = (MutableInputTable) inputTable; - Table tableToDelete = tableToDeleteExport.get(); - - authWiring.checkPermissionDeleteTableFromInputTable( - ExecutionContext.getContext().getAuthContext(), request, - List.of(targetTable.get(), tableToDelete)); - - // validate that the columns are compatible - try { - mutableInputTable.validateDelete(tableToDelete); - } catch (TableDefinition.IncompatibleTableDefinitionException exception) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Provided tables's columns are not compatible: " + exception.getMessage()); - } catch (UnsupportedOperationException exception) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Provided input table does not support delete."); - } - - // actually delete the table's contents - try { - mutableInputTable.delete(tableToDelete); - GrpcUtil.safelyComplete(responseObserver, DeleteTableResponse.getDefaultInstance()); - } catch (IOException ioException) { - throw GrpcUtil.statusRuntimeException(Code.DATA_LOSS, - "Error deleting table from inputtable"); - } - }); - }); + public void deleteTableFromInputTable( + @NotNull final DeleteTableRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + + SessionState.ExportObject
targetTable = + ticketRouter.resolve(session, request.getInputTable(), "inputTable"); + SessionState.ExportObject
tableToDeleteExport = + ticketRouter.resolve(session, request.getTableToRemove(), "tableToDelete"); + + session.nonExport() + .requiresSerialQueue() + .onError(responseObserver) + .require(targetTable, tableToDeleteExport) + .submit(() -> { + Object inputTable = targetTable.get().getAttribute(Table.INPUT_TABLE_ATTRIBUTE); + if (!(inputTable instanceof MutableInputTable)) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Table can't be used as an input table"); + } + + MutableInputTable mutableInputTable = (MutableInputTable) inputTable; + Table tableToDelete = tableToDeleteExport.get(); + + authWiring.checkPermissionDeleteTableFromInputTable( + ExecutionContext.getContext().getAuthContext(), request, + List.of(targetTable.get(), tableToDelete)); + + // validate that the columns are compatible + try { + mutableInputTable.validateDelete(tableToDelete); + } catch (TableDefinition.IncompatibleTableDefinitionException exception) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Provided tables's columns are not compatible: " + exception.getMessage()); + } catch (UnsupportedOperationException exception) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Provided input table does not support delete."); + } + + // actually delete the table's contents + try { + mutableInputTable.delete(tableToDelete); + GrpcUtil.safelyComplete(responseObserver, DeleteTableResponse.getDefaultInstance()); + } catch (IOException ioException) { + throw Exceptions.statusRuntimeException(Code.DATA_LOSS, + "Error deleting table from inputtable"); + } + }); } } diff --git a/server/src/main/java/io/deephaven/server/table/ops/AggSpecAdapter.java b/server/src/main/java/io/deephaven/server/table/ops/AggSpecAdapter.java index b7acb665d5e..9d04321ea40 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/AggSpecAdapter.java +++ b/server/src/main/java/io/deephaven/server/table/ops/AggSpecAdapter.java @@ -12,7 +12,6 @@ import io.deephaven.api.agg.spec.AggSpecWAvg; import io.deephaven.api.agg.spec.AggSpecWSum; import io.deephaven.api.object.UnionObject; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecAbsSum; import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecApproximatePercentile; import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecAvg; @@ -38,6 +37,7 @@ import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecWeighted; import io.deephaven.proto.backplane.grpc.AggSpec.TypeCase; import io.deephaven.proto.backplane.grpc.NullValue; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.grpc.GrpcErrorHelper; import java.lang.reflect.InvocationTargetException; @@ -98,32 +98,32 @@ public static void validate(AggSpecNonUniqueSentinel nonUniqueSentinel) { return; case NULL_VALUE: if (nonUniqueSentinel.getNullValue() != NullValue.NULL_VALUE) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "AggSpecNonUniqueSentinel null_value out of range"); } return; case BYTE_VALUE: if (nonUniqueSentinel.getByteValue() != (byte) nonUniqueSentinel.getByteValue()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "AggSpecNonUniqueSentinel byte_value out of range"); } return; case SHORT_VALUE: if (nonUniqueSentinel.getShortValue() != (short) nonUniqueSentinel.getShortValue()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "AggSpecNonUniqueSentinel short_value out of range"); } return; case CHAR_VALUE: if (nonUniqueSentinel.getCharValue() != (char) nonUniqueSentinel.getCharValue()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "AggSpecNonUniqueSentinel char_value out of range"); } return; case TYPE_NOT_SET: // Should be caught by checkHasOneOf, fall-through to internal error if not. default: - throw GrpcUtil.statusRuntimeException(Code.INTERNAL, + throw Exceptions.statusRuntimeException(Code.INTERNAL, String.format("Server missing AggSpecNonUniqueSentinel type %s", type)); } } @@ -211,9 +211,9 @@ private static UnionObject adapt(AggSpecNonUniqueSentinel nonUniqueSentinel) { case CHAR_VALUE: return UnionObject.of((char) nonUniqueSentinel.getCharValue()); case TYPE_NOT_SET: - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "AggSpecNonUniqueSentinel type not set"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "AggSpecNonUniqueSentinel type not set"); default: - throw GrpcUtil.statusRuntimeException(Code.INTERNAL, String + throw Exceptions.statusRuntimeException(Code.INTERNAL, String .format("Server is missing AggSpecNonUniqueSentinel case %s", nonUniqueSentinel.getTypeCase())); } } @@ -244,10 +244,10 @@ public AggSpec adapt(io.deephaven.proto.backplane.grpc.AggSpec spec) { return adapter; } if (unimplemented.contains(type)) { - throw GrpcUtil.statusRuntimeException(Code.UNIMPLEMENTED, + throw Exceptions.statusRuntimeException(Code.UNIMPLEMENTED, String.format("AggSpec type %s is unimplemented", type)); } - throw GrpcUtil.statusRuntimeException(Code.INTERNAL, + throw Exceptions.statusRuntimeException(Code.INTERNAL, String.format("Server is missing AggSpec type %s", type)); } diff --git a/server/src/main/java/io/deephaven/server/table/ops/AggregationAdapter.java b/server/src/main/java/io/deephaven/server/table/ops/AggregationAdapter.java index d4736e43e76..6b84224d6ee 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/AggregationAdapter.java +++ b/server/src/main/java/io/deephaven/server/table/ops/AggregationAdapter.java @@ -12,12 +12,12 @@ import io.deephaven.api.agg.LastRowKey; import io.deephaven.api.agg.Partition; import io.deephaven.api.agg.spec.AggSpec; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.Aggregation.AggregationColumns; import io.deephaven.proto.backplane.grpc.Aggregation.AggregationCount; import io.deephaven.proto.backplane.grpc.Aggregation.AggregationPartition; import io.deephaven.proto.backplane.grpc.Aggregation.AggregationRowKey; import io.deephaven.proto.backplane.grpc.Aggregation.TypeCase; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.grpc.GrpcErrorHelper; import java.lang.reflect.InvocationTargetException; @@ -107,10 +107,10 @@ public Aggregation adapt(io.deephaven.proto.backplane.grpc.Aggregation aggregati return adapter; } if (unimplemented.contains(type)) { - throw GrpcUtil.statusRuntimeException(Code.UNIMPLEMENTED, + throw Exceptions.statusRuntimeException(Code.UNIMPLEMENTED, String.format("Aggregation type %s is unimplemented", type)); } - throw GrpcUtil.statusRuntimeException(Code.INTERNAL, + throw Exceptions.statusRuntimeException(Code.INTERNAL, String.format("Server is missing Aggregation type %s", type)); } diff --git a/server/src/main/java/io/deephaven/server/table/ops/ComboAggregateGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/ComboAggregateGrpcImpl.java index 064ae6aa870..94802340df5 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/ComboAggregateGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/ComboAggregateGrpcImpl.java @@ -11,9 +11,9 @@ import io.deephaven.base.verify.Assert; import io.deephaven.engine.table.ColumnDefinition; import io.deephaven.engine.table.Table; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.BatchTableRequest; import io.deephaven.proto.backplane.grpc.ComboAggregateRequest; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import io.grpc.StatusRuntimeException; import org.jetbrains.annotations.NotNull; @@ -41,13 +41,12 @@ public ComboAggregateGrpcImpl(final TableServiceContextualAuthWiring authWiring) @Override public void validateRequest(ComboAggregateRequest request) throws StatusRuntimeException { if (request.getAggregatesCount() == 0) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "ComboAggregateRequest incorrectly has zero aggregates provided"); } for (String groupByColumn : request.getGroupByColumnsList()) { if (!NameValidator.isValidColumnName(groupByColumn)) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "ComboAggregateRequest group by"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "ComboAggregateRequest group by"); } } if (isSimpleAggregation(request)) { @@ -55,21 +54,21 @@ public void validateRequest(ComboAggregateRequest request) throws StatusRuntimeE // which would suggest they meant to set force_combo=true ComboAggregateRequest.Aggregate aggregate = request.getAggregates(0); if (aggregate.getMatchPairsCount() != 0) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "force_combo is false and only one aggregate provided, but match_pairs is specified"); } if (aggregate.getPercentile() != 0) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "force_combo is false and only one aggregate provided, but percentile is specified"); } if (aggregate.getAvgMedian()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "force_combo is false and only one aggregate provided, but avg_median is specified"); } if (aggregate.getType() != ComboAggregateRequest.AggType.COUNT && aggregate.getType() != ComboAggregateRequest.AggType.WEIGHTED_AVG) { if (!aggregate.getColumnName().isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "force_combo is false and only one aggregate provided, but column_name is specified for type other than COUNT or WEIGHTED_AVG"); } } @@ -77,24 +76,24 @@ public void validateRequest(ComboAggregateRequest request) throws StatusRuntimeE for (ComboAggregateRequest.Aggregate aggregate : request.getAggregatesList()) { if (aggregate.getType() != ComboAggregateRequest.AggType.PERCENTILE) { if (aggregate.getPercentile() != 0) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "percentile is specified for type " + aggregate.getType()); } if (aggregate.getAvgMedian()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "avg_median is specified for type " + aggregate.getType()); } } if (aggregate.getType() == ComboAggregateRequest.AggType.COUNT) { if (aggregate.getMatchPairsCount() != 0) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "match_pairs is specified for type COUNT"); } } if (aggregate.getType() != ComboAggregateRequest.AggType.COUNT && aggregate.getType() != ComboAggregateRequest.AggType.WEIGHTED_AVG) { if (!aggregate.getColumnName().isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "column_name is specified for type " + aggregate.getType()); } } diff --git a/server/src/main/java/io/deephaven/server/table/ops/CreateInputTableGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/CreateInputTableGrpcImpl.java index aa619a1f8cb..717542465a8 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/CreateInputTableGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/CreateInputTableGrpcImpl.java @@ -11,10 +11,10 @@ import io.deephaven.engine.table.impl.util.AppendOnlyArrayBackedMutableTable; import io.deephaven.engine.table.impl.util.KeyedArrayBackedMutableTable; import io.deephaven.extensions.barrage.util.BarrageUtil; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.BatchTableRequest; import io.deephaven.proto.backplane.grpc.CreateInputTableRequest; import io.deephaven.proto.flight.util.SchemaHelper; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import io.grpc.StatusRuntimeException; import org.apache.arrow.flatbuf.Schema; @@ -44,14 +44,13 @@ public CreateInputTableGrpcImpl(final TableServiceContextualAuthWiring authWirin public void validateRequest(CreateInputTableRequest request) throws StatusRuntimeException { // ensure we have one of either schema or source table (protobuf will ensure we don't have both) if (!request.hasSchema() && !request.hasSourceTableId()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Must specify one of schema and source_table_id"); } if (request.getKind().getKindCase() == null || request.getKind().getKindCase() == KIND_NOT_SET) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "Unrecognized InputTableKind"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Unrecognized InputTableKind"); } } diff --git a/server/src/main/java/io/deephaven/server/table/ops/EmptyTableGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/EmptyTableGrpcImpl.java index b2359750bab..49b3b857c37 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/EmptyTableGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/EmptyTableGrpcImpl.java @@ -8,9 +8,9 @@ import io.deephaven.base.verify.Assert; import io.deephaven.engine.table.Table; import io.deephaven.engine.util.TableTools; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.BatchTableRequest; import io.deephaven.proto.backplane.grpc.EmptyTableRequest; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import io.grpc.StatusRuntimeException; @@ -30,7 +30,7 @@ public EmptyTableGrpcImpl(final TableServiceContextualAuthWiring authWiring) { @Override public void validateRequest(final EmptyTableRequest request) throws StatusRuntimeException { if (request.getSize() < 0) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Size must be greater than zero"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Size must be greater than zero"); } } diff --git a/server/src/main/java/io/deephaven/server/table/ops/HeadOrTailByGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/HeadOrTailByGrpcImpl.java index 1daaa06525d..5436de9ceb5 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/HeadOrTailByGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/HeadOrTailByGrpcImpl.java @@ -11,9 +11,9 @@ import io.deephaven.engine.table.impl.select.SelectColumn; import io.deephaven.engine.table.impl.select.SelectColumnFactory; import io.deephaven.engine.updategraph.UpdateGraphProcessor; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.BatchTableRequest; import io.deephaven.proto.backplane.grpc.HeadOrTailByRequest; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import io.deephaven.server.table.validation.ColumnExpressionValidator; import io.grpc.StatusRuntimeException; @@ -46,7 +46,8 @@ protected HeadOrTailByGrpcImpl( public void validateRequest(final HeadOrTailByRequest request) throws StatusRuntimeException { final long nRows = request.getNumRows(); if (nRows < 0) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "numRows must be >= 0 (found: " + nRows + ")"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "numRows must be >= 0 (found: " + nRows + ")"); } } diff --git a/server/src/main/java/io/deephaven/server/table/ops/HeadOrTailGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/HeadOrTailGrpcImpl.java index 2efb186a2ce..91c0a59e083 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/HeadOrTailGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/HeadOrTailGrpcImpl.java @@ -7,9 +7,9 @@ import io.deephaven.auth.codegen.impl.TableServiceContextualAuthWiring; import io.deephaven.base.verify.Assert; import io.deephaven.engine.table.Table; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.BatchTableRequest; import io.deephaven.proto.backplane.grpc.HeadOrTailRequest; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import io.grpc.StatusRuntimeException; @@ -38,7 +38,8 @@ protected HeadOrTailGrpcImpl( public void validateRequest(final HeadOrTailRequest request) throws StatusRuntimeException { final long nRows = request.getNumRows(); if (nRows < 0) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "numRows must be >= 0 (found: " + nRows + ")"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "numRows must be >= 0 (found: " + nRows + ")"); } } diff --git a/server/src/main/java/io/deephaven/server/table/ops/JoinTablesGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/JoinTablesGrpcImpl.java index 17f41c70b2e..e057ec34bec 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/JoinTablesGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/JoinTablesGrpcImpl.java @@ -11,7 +11,6 @@ import io.deephaven.engine.table.Table; import io.deephaven.engine.table.impl.select.MatchPairFactory; import io.deephaven.engine.updategraph.UpdateGraphProcessor; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.AsOfJoinTablesRequest; import io.deephaven.proto.backplane.grpc.BatchTableRequest; import io.deephaven.proto.backplane.grpc.CrossJoinTablesRequest; @@ -19,6 +18,7 @@ import io.deephaven.proto.backplane.grpc.LeftJoinTablesRequest; import io.deephaven.proto.backplane.grpc.NaturalJoinTablesRequest; import io.deephaven.proto.backplane.grpc.Ticket; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import io.grpc.StatusRuntimeException; @@ -59,7 +59,7 @@ public void validateRequest(final T request) throws StatusRuntimeException { MatchPairFactory.getExpressions(getColMatchList.apply(request)); MatchPairFactory.getExpressions(getColAddList.apply(request)); } catch (final ExpressionException err) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, err.getMessage() + ": " + err.getProblemExpression()); } } @@ -75,7 +75,7 @@ public Table create(final T request, final List columnsToMatch = MatchPairFactory.getExpressions(getColMatchList.apply(request)); columnsToAdd = MatchPairFactory.getExpressions(getColAddList.apply(request)); } catch (final ExpressionException err) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, err.getMessage() + ": " + err.getProblemExpression()); } @@ -114,7 +114,7 @@ public void validateRequest(final AsOfJoinTablesRequest request) throws StatusRu super.validateRequest(request); if (request.getAsOfMatchRule() == AsOfJoinTablesRequest.MatchRule.UNRECOGNIZED) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Unrecognized as-of match rule"); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Unrecognized as-of match rule"); } } @@ -208,8 +208,7 @@ public LeftJoinTablesGrpcImpl( public static Table doJoin(final Table lhs, final Table rhs, final MatchPair[] columnsToMatch, final MatchPair[] columnsToAdd, final LeftJoinTablesRequest request) { - throw GrpcUtil.statusRuntimeException(Code.UNIMPLEMENTED, - "LeftJoinTables is currently unimplemented"); + throw Exceptions.statusRuntimeException(Code.UNIMPLEMENTED, "LeftJoinTables is currently unimplemented"); } } diff --git a/server/src/main/java/io/deephaven/server/table/ops/MergeTablesGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/MergeTablesGrpcImpl.java index b3378a1c68a..9bd9997dcdb 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/MergeTablesGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/MergeTablesGrpcImpl.java @@ -9,9 +9,9 @@ import io.deephaven.engine.table.Table; import io.deephaven.engine.updategraph.UpdateGraphProcessor; import io.deephaven.engine.util.TableTools; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.BatchTableRequest; import io.deephaven.proto.backplane.grpc.MergeTablesRequest; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import io.grpc.StatusRuntimeException; @@ -37,7 +37,7 @@ public MergeTablesGrpcImpl( @Override public void validateRequest(final MergeTablesRequest request) throws StatusRuntimeException { if (request.getSourceIdsList().isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Cannot merge zero source tables."); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Cannot merge zero source tables."); } } diff --git a/server/src/main/java/io/deephaven/server/table/ops/SelectDistinctGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/SelectDistinctGrpcImpl.java index 3fd90bea7ea..c96364c4ce6 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/SelectDistinctGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/SelectDistinctGrpcImpl.java @@ -7,9 +7,9 @@ import io.deephaven.auth.codegen.impl.TableServiceContextualAuthWiring; import io.deephaven.base.verify.Assert; import io.deephaven.engine.table.Table; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.BatchTableRequest; import io.deephaven.proto.backplane.grpc.SelectDistinctRequest; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import javax.inject.Inject; @@ -37,7 +37,7 @@ public Table create(final SelectDistinctRequest request, final Set requestedMissing = new HashSet<>(request.getColumnNamesList()); requestedMissing.removeAll(parent.getDefinition().getColumnNameMap().keySet()); if (!requestedMissing.isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "column(s) not found: " + String.join(", ", requestedMissing)); } diff --git a/server/src/main/java/io/deephaven/server/table/ops/SnapshotWhenTableGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/SnapshotWhenTableGrpcImpl.java index db99dd526d4..7cbb18fcba9 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/SnapshotWhenTableGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/SnapshotWhenTableGrpcImpl.java @@ -12,10 +12,10 @@ import io.deephaven.base.verify.Assert; import io.deephaven.engine.table.Table; import io.deephaven.engine.updategraph.UpdateGraphProcessor; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.BatchTableRequest.Operation; import io.deephaven.proto.backplane.grpc.SnapshotWhenTableRequest; import io.deephaven.proto.backplane.grpc.TableReference; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.grpc.Common; import io.deephaven.server.grpc.GrpcErrorHelper; import io.deephaven.server.session.SessionState; @@ -76,7 +76,7 @@ public void validateRequest(SnapshotWhenTableRequest request) throws StatusRunti try { options(request); } catch (UnsupportedOperationException e) { - throw GrpcUtil.statusRuntimeException(Code.UNIMPLEMENTED, e.getMessage()); + throw Exceptions.statusRuntimeException(Code.UNIMPLEMENTED, e.getMessage()); } } diff --git a/server/src/main/java/io/deephaven/server/table/ops/SortTableGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/SortTableGrpcImpl.java index ea912f31fb9..66f559a40af 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/SortTableGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/SortTableGrpcImpl.java @@ -12,10 +12,10 @@ import io.deephaven.engine.table.Table; import io.deephaven.engine.table.impl.AbsoluteSortColumnConventions; import io.deephaven.engine.table.impl.QueryTable; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.BatchTableRequest; import io.deephaven.proto.backplane.grpc.SortDescriptor; import io.deephaven.proto.backplane.grpc.SortTableRequest; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import javax.inject.Inject; @@ -79,7 +79,7 @@ public Table create(final SortTableRequest request, direction = 1; break; default: - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Unexpected sort direction: " + direction); } diff --git a/server/src/main/java/io/deephaven/server/table/ops/TableServiceGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/TableServiceGrpcImpl.java index 68f5b54fe60..4a19ef2077d 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/TableServiceGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/TableServiceGrpcImpl.java @@ -7,7 +7,6 @@ import io.deephaven.clientsupport.gotorow.SeekRow; import io.deephaven.auth.codegen.impl.TableServiceContextualAuthWiring; import io.deephaven.engine.table.Table; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.extensions.barrage.util.ExportUtil; import io.deephaven.internal.log.LoggerFactory; import io.deephaven.io.logger.Logger; @@ -52,6 +51,7 @@ import io.deephaven.proto.backplane.grpc.UnstructuredFilterTableRequest; import io.deephaven.proto.backplane.grpc.UpdateByRequest; import io.deephaven.proto.backplane.grpc.WhereInRequest; +import io.deephaven.proto.util.Exceptions; import io.deephaven.proto.util.ExportTicketHelper; import io.deephaven.server.grpc.GrpcErrorHelper; import io.deephaven.server.session.SessionService; @@ -63,6 +63,7 @@ import io.grpc.StatusRuntimeException; import io.grpc.stub.ServerCallStreamObserver; import io.grpc.stub.StreamObserver; +import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import java.math.BigDecimal; @@ -103,7 +104,7 @@ private GrpcTableOperation getOp(final BatchTableRequest.Operation.OpCase // noinspection unchecked final GrpcTableOperation operation = (GrpcTableOperation) operationMap.get(op); if (operation == null) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "BatchTableRequest.Operation.OpCode is unset, incompatible, or not yet supported. (found: " + op + ")"); } @@ -111,163 +112,191 @@ private GrpcTableOperation getOp(final BatchTableRequest.Operation.OpCase } @Override - public void emptyTable(final EmptyTableRequest request, - final StreamObserver responseObserver) { + public void emptyTable( + @NotNull final EmptyTableRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.EMPTY_TABLE, request, responseObserver); } @Override - public void timeTable(final TimeTableRequest request, - final StreamObserver responseObserver) { + public void timeTable( + @NotNull final TimeTableRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.TIME_TABLE, request, responseObserver); } @Override - public void mergeTables(final MergeTablesRequest request, - final StreamObserver responseObserver) { + public void mergeTables( + @NotNull final MergeTablesRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.MERGE, request, responseObserver); } @Override - public void selectDistinct(final SelectDistinctRequest request, - final StreamObserver responseObserver) { + public void selectDistinct( + @NotNull final SelectDistinctRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.SELECT_DISTINCT, request, responseObserver); } @Override - public void update(final SelectOrUpdateRequest request, - final StreamObserver responseObserver) { + public void update( + @NotNull final SelectOrUpdateRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.UPDATE, request, responseObserver); } @Override - public void lazyUpdate(final SelectOrUpdateRequest request, - final StreamObserver responseObserver) { + public void lazyUpdate( + @NotNull final SelectOrUpdateRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.LAZY_UPDATE, request, responseObserver); } @Override - public void view(final SelectOrUpdateRequest request, - final StreamObserver responseObserver) { + public void view( + @NotNull final SelectOrUpdateRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.VIEW, request, responseObserver); } @Override - public void updateView(final SelectOrUpdateRequest request, - final StreamObserver responseObserver) { + public void updateView( + @NotNull final SelectOrUpdateRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.UPDATE_VIEW, request, responseObserver); } @Override - public void select(final SelectOrUpdateRequest request, - final StreamObserver responseObserver) { + public void select( + @NotNull final SelectOrUpdateRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.SELECT, request, responseObserver); } @Override - public void headBy(final HeadOrTailByRequest request, - final StreamObserver responseObserver) { + public void headBy( + @NotNull final HeadOrTailByRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.HEAD_BY, request, responseObserver); } @Override - public void tailBy(final HeadOrTailByRequest request, - final StreamObserver responseObserver) { + public void tailBy( + @NotNull final HeadOrTailByRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.TAIL_BY, request, responseObserver); } @Override - public void head(final HeadOrTailRequest request, - final StreamObserver responseObserver) { + public void head( + @NotNull final HeadOrTailRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.HEAD, request, responseObserver); } @Override - public void tail(final HeadOrTailRequest request, - final StreamObserver responseObserver) { + public void tail( + @NotNull final HeadOrTailRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.TAIL, request, responseObserver); } @Override - public void ungroup(final UngroupRequest request, - final StreamObserver responseObserver) { + public void ungroup( + @NotNull final UngroupRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.UNGROUP, request, responseObserver); } @Override - public void comboAggregate(final ComboAggregateRequest request, - final StreamObserver responseObserver) { + public void comboAggregate( + @NotNull final ComboAggregateRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.COMBO_AGGREGATE, request, responseObserver); } @Override - public void aggregateAll(AggregateAllRequest request, - StreamObserver responseObserver) { + public void aggregateAll( + @NotNull final AggregateAllRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(OpCase.AGGREGATE_ALL, request, responseObserver); } @Override - public void aggregate(AggregateRequest request, StreamObserver responseObserver) { + public void aggregate( + @NotNull final AggregateRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.AGGREGATE, request, responseObserver); } @Override - public void snapshot(final SnapshotTableRequest request, - final StreamObserver responseObserver) { + public void snapshot( + @NotNull final SnapshotTableRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.SNAPSHOT, request, responseObserver); } @Override - public void snapshotWhen(final SnapshotWhenTableRequest request, - final StreamObserver responseObserver) { + public void snapshotWhen( + @NotNull final SnapshotWhenTableRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.SNAPSHOT_WHEN, request, responseObserver); } @Override - public void dropColumns(final DropColumnsRequest request, - final StreamObserver responseObserver) { + public void dropColumns( + @NotNull final DropColumnsRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.DROP_COLUMNS, request, responseObserver); } @Override - public void filter(final FilterTableRequest request, - final StreamObserver responseObserver) { + public void filter( + @NotNull final FilterTableRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.FILTER, request, responseObserver); } @Override - public void unstructuredFilter(final UnstructuredFilterTableRequest request, - final StreamObserver responseObserver) { + public void unstructuredFilter( + @NotNull final UnstructuredFilterTableRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.UNSTRUCTURED_FILTER, request, responseObserver); } @Override - public void sort(final SortTableRequest request, - final StreamObserver responseObserver) { + public void sort( + @NotNull final SortTableRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.SORT, request, responseObserver); } @Override - public void flatten(final FlattenRequest request, - final StreamObserver responseObserver) { + public void flatten( + @NotNull final FlattenRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.FLATTEN, request, responseObserver); } @Override - public void crossJoinTables(final CrossJoinTablesRequest request, - final StreamObserver responseObserver) { + public void crossJoinTables( + @NotNull final CrossJoinTablesRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.CROSS_JOIN, request, responseObserver); } @Override - public void naturalJoinTables(final NaturalJoinTablesRequest request, - final StreamObserver responseObserver) { + public void naturalJoinTables( + @NotNull final NaturalJoinTablesRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.NATURAL_JOIN, request, responseObserver); } @Override - public void exactJoinTables(final ExactJoinTablesRequest request, - final StreamObserver responseObserver) { + public void exactJoinTables( + @NotNull final ExactJoinTablesRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.EXACT_JOIN, request, responseObserver); } @@ -320,13 +349,14 @@ private Object getSeekValue(Literal literal, Class dataType) { return new BigInteger(literal.getStringValue()); } if (!String.class.isAssignableFrom(dataType) && dataType != char.class) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Invalid String type for seek: " + dataType); } return literal.getStringValue(); } else if (literal.hasNanoTimeValue()) { if (!DateTime.class.isAssignableFrom(dataType)) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Invalid Date type for seek: " + dataType); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Invalid Date type for seek: " + dataType); } return new DateTime(literal.getNanoTimeValue()); } else if (literal.hasLongValue()) { @@ -372,166 +402,166 @@ private Object getSeekValue(Literal literal, Class dataType) { } else if (literal.hasBoolValue()) { return literal.getBoolValue(); } - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "Invalid column type for seek: " + dataType); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Invalid column type for seek: " + dataType); } @Override - public void whereIn(WhereInRequest request, StreamObserver responseObserver) { + public void whereIn( + @NotNull final WhereInRequest request, + @NotNull final StreamObserver responseObserver) { oneShotOperationWrapper(BatchTableRequest.Operation.OpCase.WHERE_IN, request, responseObserver); } @Override - public void seekRow(SeekRowRequest request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - final Ticket sourceId = request.getSourceId(); - if (sourceId.getTicket().isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "No consoleId supplied"); - } - SessionState.ExportObject
exportedTable = - ticketRouter.resolve(session, sourceId, "sourceId"); - session.nonExport() - .require(exportedTable) - .onError(responseObserver) - .submit(() -> { - final Table table = exportedTable.get(); - authWiring.checkPermissionSeekRow(session.getAuthContext(), request, - Collections.singletonList(table)); - final String columnName = request.getColumnName(); - final Class dataType = table.getDefinition().getColumn(columnName).getDataType(); - final Object seekValue = getSeekValue(request.getSeekValue(), dataType); - final Long result = table.apply(new SeekRow( - request.getStartingRow(), - columnName, - seekValue, - request.getInsensitive(), - request.getContains(), - request.getIsBackward())); - SeekRowResponse.Builder rowResponse = SeekRowResponse.newBuilder(); - safelyComplete(responseObserver, rowResponse.setResultRow(result).build()); - }); - }); - } - - @Override - public void batch(final BatchTableRequest request, - final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - GrpcErrorHelper.checkRepeatedFieldNonEmpty(request, BatchTableRequest.OPS_FIELD_NUMBER); - GrpcErrorHelper.checkHasNoUnknownFields(request); - for (Operation operation : request.getOpsList()) { - GrpcErrorHelper.checkHasOneOf(operation, "op"); - GrpcErrorHelper.checkHasNoUnknownFields(operation); - } - final SessionState session = sessionService.getCurrentSession(); - - // step 1: initialize exports - final List> exportBuilders = request.getOpsList().stream() - .map(op -> createBatchExportBuilder(session, op)) - .collect(Collectors.toList()); + public void seekRow( + @NotNull final SeekRowRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + final Ticket sourceId = request.getSourceId(); + if (sourceId.getTicket().isEmpty()) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "No consoleId supplied"); + } + SessionState.ExportObject
exportedTable = + ticketRouter.resolve(session, sourceId, "sourceId"); + session.nonExport() + .require(exportedTable) + .onError(responseObserver) + .submit(() -> { + final Table table = exportedTable.get(); + authWiring.checkPermissionSeekRow(session.getAuthContext(), request, + Collections.singletonList(table)); + final String columnName = request.getColumnName(); + final Class dataType = table.getDefinition().getColumn(columnName).getDataType(); + final Object seekValue = getSeekValue(request.getSeekValue(), dataType); + final Long result = table.apply(new SeekRow( + request.getStartingRow(), + columnName, + seekValue, + request.getInsensitive(), + request.getContains(), + request.getIsBackward())); + SeekRowResponse.Builder rowResponse = SeekRowResponse.newBuilder(); + safelyComplete(responseObserver, rowResponse.setResultRow(result).build()); + }); + } - // step 2: resolve dependencies - exportBuilders.forEach(export -> export.resolveDependencies(session, exportBuilders)); + @Override + public void batch( + @NotNull final BatchTableRequest request, + @NotNull final StreamObserver responseObserver) { + GrpcErrorHelper.checkRepeatedFieldNonEmpty(request, BatchTableRequest.OPS_FIELD_NUMBER); + GrpcErrorHelper.checkHasNoUnknownFields(request); + for (Operation operation : request.getOpsList()) { + GrpcErrorHelper.checkHasOneOf(operation, "op"); + GrpcErrorHelper.checkHasNoUnknownFields(operation); + } + final SessionState session = sessionService.getCurrentSession(); - // step 3: check for cyclical dependencies; this is our only opportunity to check non-export cycles - // TODO: check for cycles + // step 1: initialize exports + final List> exportBuilders = request.getOpsList().stream() + .map(op -> createBatchExportBuilder(session, op)) + .collect(Collectors.toList()); - // step 4: submit the batched operations - final AtomicInteger remaining = new AtomicInteger(exportBuilders.size()); - final AtomicReference firstFailure = new AtomicReference<>(); + // step 2: resolve dependencies + exportBuilders.forEach(export -> export.resolveDependencies(session, exportBuilders)); - final Runnable onOneResolved = () -> { - if (remaining.decrementAndGet() == 0) { - final StatusRuntimeException failure = firstFailure.get(); - if (failure != null) { - safelyError(responseObserver, failure); - } else { - safelyComplete(responseObserver); - } - } - }; + // step 3: check for cyclical dependencies; this is our only opportunity to check non-export cycles + // TODO: check for cycles - for (int i = 0; i < exportBuilders.size(); ++i) { - final BatchExportBuilder exportBuilder = exportBuilders.get(i); - final int exportId = exportBuilder.exportBuilder.getExportId(); + // step 4: submit the batched operations + final AtomicInteger remaining = new AtomicInteger(exportBuilders.size()); + final AtomicReference firstFailure = new AtomicReference<>(); - final TableReference resultId; - if (exportId == SessionState.NON_EXPORT_ID) { - resultId = TableReference.newBuilder().setBatchOffset(i).build(); + final Runnable onOneResolved = () -> { + if (remaining.decrementAndGet() == 0) { + final StatusRuntimeException failure = firstFailure.get(); + if (failure != null) { + safelyError(responseObserver, failure); } else { - resultId = ExportTicketHelper.tableReference(exportId); + safelyComplete(responseObserver); } + } + }; - exportBuilder.exportBuilder.onError((result, errorContext, cause, dependentId) -> { - String errorInfo = errorContext; - if (dependentId != null) { - errorInfo += " dependency: " + dependentId; - } - if (cause instanceof StatusRuntimeException) { - errorInfo += " cause: " + cause.getMessage(); - firstFailure.compareAndSet(null, (StatusRuntimeException) cause); - } - final ExportedTableCreationResponse response = ExportedTableCreationResponse.newBuilder() - .setResultId(resultId) - .setSuccess(false) - .setErrorInfo(errorInfo) - .build(); - safelyOnNext(responseObserver, response); - onOneResolved.run(); - }).submit(() -> { - final Table table = exportBuilder.doExport(); - final ExportedTableCreationResponse response = - ExportUtil.buildTableCreationResponse(resultId, table); - safelyOnNext(responseObserver, response); - onOneResolved.run(); - return table; - }); + for (int i = 0; i < exportBuilders.size(); ++i) { + final BatchExportBuilder exportBuilder = exportBuilders.get(i); + final int exportId = exportBuilder.exportBuilder.getExportId(); + + final TableReference resultId; + if (exportId == SessionState.NON_EXPORT_ID) { + resultId = TableReference.newBuilder().setBatchOffset(i).build(); + } else { + resultId = ExportTicketHelper.tableReference(exportId); } - }); + + exportBuilder.exportBuilder.onError((result, errorContext, cause, dependentId) -> { + String errorInfo = errorContext; + if (dependentId != null) { + errorInfo += " dependency: " + dependentId; + } + if (cause instanceof StatusRuntimeException) { + errorInfo += " cause: " + cause.getMessage(); + firstFailure.compareAndSet(null, (StatusRuntimeException) cause); + } + final ExportedTableCreationResponse response = ExportedTableCreationResponse.newBuilder() + .setResultId(resultId) + .setSuccess(false) + .setErrorInfo(errorInfo) + .build(); + safelyOnNext(responseObserver, response); + onOneResolved.run(); + }).submit(() -> { + final Table table = exportBuilder.doExport(); + final ExportedTableCreationResponse response = + ExportUtil.buildTableCreationResponse(resultId, table); + safelyOnNext(responseObserver, response); + onOneResolved.run(); + return table; + }); + } } @Override - public void exportedTableUpdates(final ExportedTableUpdatesRequest request, - final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - authWiring.checkPermissionExportedTableUpdates(session.getAuthContext(), request, Collections.emptyList()); - final ExportedTableUpdateListener listener = new ExportedTableUpdateListener(session, responseObserver); - session.addExportListener(listener); - ((ServerCallStreamObserver) responseObserver).setOnCancelHandler( - () -> session.removeExportListener(listener)); - }); + public void exportedTableUpdates( + @NotNull final ExportedTableUpdatesRequest request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); + authWiring.checkPermissionExportedTableUpdates(session.getAuthContext(), request, Collections.emptyList()); + final ExportedTableUpdateListener listener = new ExportedTableUpdateListener(session, responseObserver); + session.addExportListener(listener); + ((ServerCallStreamObserver) responseObserver).setOnCancelHandler( + () -> session.removeExportListener(listener)); } @Override - public void getExportedTableCreationResponse(final Ticket request, - final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); + public void getExportedTableCreationResponse( + @NotNull final Ticket request, + @NotNull final StreamObserver responseObserver) { + final SessionState session = sessionService.getCurrentSession(); - if (request.getTicket().isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "No request ticket supplied"); - } + if (request.getTicket().isEmpty()) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "No request ticket supplied"); + } - final SessionState.ExportObject export = ticketRouter.resolve(session, request, "request"); - - session.nonExport() - .require(export) - .onError(responseObserver) - .submit(() -> { - final Object obj = export.get(); - if (!(obj instanceof Table)) { - responseObserver.onError( - GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "Ticket is not a table")); - return; - } - authWiring.checkPermissionGetExportedTableCreationResponse( - session.getAuthContext(), request, Collections.singletonList((Table) obj)); - final ExportedTableCreationResponse response = - ExportUtil.buildTableCreationResponse(request, (Table) obj); - safelyComplete(responseObserver, response); - }); - }); + final SessionState.ExportObject export = ticketRouter.resolve(session, request, "request"); + + session.nonExport() + .require(export) + .onError(responseObserver) + .submit(() -> { + final Object obj = export.get(); + if (!(obj instanceof Table)) { + responseObserver.onError( + Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, + "Ticket is not a table")); + return; + } + authWiring.checkPermissionGetExportedTableCreationResponse( + session.getAuthContext(), request, Collections.singletonList((Table) obj)); + final ExportedTableCreationResponse response = + ExportUtil.buildTableCreationResponse(request, (Table) obj); + safelyComplete(responseObserver, response); + }); } /** @@ -546,37 +576,35 @@ private void oneShotOperationWrapper( final BatchTableRequest.Operation.OpCase op, final T request, final StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - final GrpcTableOperation operation = getOp(op); - operation.validateRequest(request); - - final Ticket resultId = operation.getResultTicket(request); - if (resultId.getTicket().isEmpty()) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, "No result ticket supplied"); - } + final SessionState session = sessionService.getCurrentSession(); + final GrpcTableOperation operation = getOp(op); + operation.validateRequest(request); - final List> dependencies = operation.getTableReferences(request).stream() - .map(ref -> resolveOneShotReference(session, ref)) - .collect(Collectors.toList()); + final Ticket resultId = operation.getResultTicket(request); + if (resultId.getTicket().isEmpty()) { + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "No result ticket supplied"); + } - session.newExport(resultId, "resultId") - .require(dependencies) - .onError(responseObserver) - .submit(() -> { - operation.checkPermission(request, dependencies); - final Table result = operation.create(request, dependencies); - final ExportedTableCreationResponse response = - ExportUtil.buildTableCreationResponse(resultId, result); - safelyComplete(responseObserver, response); - return result; - }); - }); + final List> dependencies = operation.getTableReferences(request).stream() + .map(ref -> resolveOneShotReference(session, ref)) + .collect(Collectors.toList()); + + session.newExport(resultId, "resultId") + .require(dependencies) + .onError(responseObserver) + .submit(() -> { + operation.checkPermission(request, dependencies); + final Table result = operation.create(request, dependencies); + final ExportedTableCreationResponse response = + ExportUtil.buildTableCreationResponse(resultId, result); + safelyComplete(responseObserver, response); + return result; + }); } private SessionState.ExportObject
resolveOneShotReference(SessionState session, TableReference ref) { if (!ref.hasTicket()) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "One-shot operations must use ticket references"); } return ticketRouter.resolve(session, ref.getTicket(), "sourceId"); @@ -590,11 +618,11 @@ private SessionState.ExportObject
resolveBatchReference(SessionState sess case BATCH_OFFSET: final int offset = ref.getBatchOffset(); if (offset < 0 || offset >= exportBuilders.size()) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "invalid table reference: " + ref); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "invalid table reference: " + ref); } return exportBuilders.get(offset).exportBuilder.getExport(); default: - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, "invalid table reference: " + ref); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "invalid table reference: " + ref); } } diff --git a/server/src/main/java/io/deephaven/server/table/ops/TimeTableGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/TimeTableGrpcImpl.java index 4c142177632..096de9f7a10 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/TimeTableGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/TimeTableGrpcImpl.java @@ -9,9 +9,9 @@ import io.deephaven.engine.table.Table; import io.deephaven.engine.table.impl.TimeTable; import io.deephaven.engine.updategraph.UpdateGraphProcessor; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.BatchTableRequest; import io.deephaven.proto.backplane.grpc.TimeTableRequest; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import io.deephaven.server.util.Scheduler; import io.deephaven.time.DateTimeUtils; @@ -42,7 +42,7 @@ public TimeTableGrpcImpl( public void validateRequest(final TimeTableRequest request) throws StatusRuntimeException { final long periodNanos = request.getPeriodNanos(); if (periodNanos <= 0) { - throw GrpcUtil.statusRuntimeException(Code.FAILED_PRECONDITION, + throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, "periodNanos must be >= 0 (found: " + periodNanos + ")"); } } diff --git a/server/src/main/java/io/deephaven/server/table/ops/UpdateByGrpcImpl.java b/server/src/main/java/io/deephaven/server/table/ops/UpdateByGrpcImpl.java index bc2a0fb450c..762db2803f8 100644 --- a/server/src/main/java/io/deephaven/server/table/ops/UpdateByGrpcImpl.java +++ b/server/src/main/java/io/deephaven/server/table/ops/UpdateByGrpcImpl.java @@ -13,7 +13,6 @@ import io.deephaven.auth.codegen.impl.TableServiceContextualAuthWiring; import io.deephaven.base.verify.Assert; import io.deephaven.engine.table.Table; -import io.deephaven.extensions.barrage.util.GrpcUtil; import io.deephaven.proto.backplane.grpc.BatchTableRequest; import io.deephaven.proto.backplane.grpc.UpdateByRequest; import io.deephaven.proto.backplane.grpc.UpdateByRequest.UpdateByOperation.UpdateByColumn; @@ -25,6 +24,7 @@ import io.deephaven.proto.backplane.grpc.UpdateByRequest.UpdateByOperation.UpdateByColumn.UpdateBySpec.UpdateByEma.UpdateByEmaOptions; import io.deephaven.proto.backplane.grpc.UpdateByRequest.UpdateByOperation.UpdateByColumn.UpdateBySpec.UpdateByFill; import io.deephaven.proto.backplane.grpc.UpdateByRequest.UpdateByOptions; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.session.SessionState; import io.grpc.StatusRuntimeException; @@ -60,7 +60,7 @@ public void validateRequest(final UpdateByRequest request) throws StatusRuntimeE ColumnName.of(columnName); } } catch (IllegalArgumentException e) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, e.getMessage()); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, e.getMessage()); } } diff --git a/server/src/main/java/io/deephaven/server/util/GrpcServiceOverrideBuilder.java b/server/src/main/java/io/deephaven/server/util/GrpcServiceOverrideBuilder.java index baae11833c6..8e0dc7868da 100644 --- a/server/src/main/java/io/deephaven/server/util/GrpcServiceOverrideBuilder.java +++ b/server/src/main/java/io/deephaven/server/util/GrpcServiceOverrideBuilder.java @@ -4,7 +4,7 @@ package io.deephaven.server.util; import com.google.rpc.Code; -import io.deephaven.extensions.barrage.util.GrpcUtil; +import io.deephaven.proto.util.Exceptions; import io.deephaven.server.browserstreaming.BrowserStream; import io.deephaven.server.browserstreaming.BrowserStreamInterceptor; import io.deephaven.server.browserstreaming.StreamData; @@ -226,50 +226,49 @@ public ServerCalls.UnaryMethod next() { return this::invokeNext; } - public void invokeOpen(ReqT request, StreamObserver responseObserver) { - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - StreamData streamData = StreamData.STREAM_DATA_KEY.get(); - SessionState session = sessionService.getCurrentSession(); - if (streamData == null) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, - "no x-deephaven-stream headers, cannot handle open request"); - } - - BrowserStream browserStream = factory.create(session, responseObserver); - browserStream.onMessageReceived(request, streamData); - - if (!streamData.isHalfClose()) { - // if this isn't a half-close, we should export it for later calls - if it is, the client won't send - // more messages - session.newExport(streamData.getRpcTicket(), "rpcTicket") - // not setting an onError here, failure can only happen if the session ends - .submit(() -> browserStream); - } - - }); + public void invokeOpen( + @NotNull final ReqT request, + @NotNull final StreamObserver responseObserver) { + StreamData streamData = StreamData.STREAM_DATA_KEY.get(); + SessionState session = sessionService.getCurrentSession(); + if (streamData == null) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "no x-deephaven-stream headers, cannot handle open request"); + } + + BrowserStream browserStream = factory.create(session, responseObserver); + browserStream.onMessageReceived(request, streamData); + + if (!streamData.isHalfClose()) { + // if this isn't a half-close, we should export it for later calls - if it is, the client won't send + // more messages + session.newExport(streamData.getRpcTicket(), "rpcTicket") + // not setting an onError here, failure can only happen if the session ends + .submit(() -> browserStream); + } } - public void invokeNext(ReqT request, StreamObserver responseObserver) { + public void invokeNext( + @NotNull final ReqT request, + @NotNull final StreamObserver responseObserver) { StreamData streamData = StreamData.STREAM_DATA_KEY.get(); if (streamData == null || streamData.getRpcTicket() == null) { - throw GrpcUtil.statusRuntimeException(Code.INVALID_ARGUMENT, + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "no x-deephaven-stream headers, cannot handle next request"); } - GrpcUtil.rpcWrapper(log, responseObserver, () -> { - final SessionState session = sessionService.getCurrentSession(); - - final SessionState.ExportObject> browserStream = - session.getExport(streamData.getRpcTicket(), "rpcTicket"); - - session.nonExport() - .require(browserStream) - .onError(responseObserver) - .submit(() -> { - browserStream.get().onMessageReceived(request, streamData); - responseObserver.onNext(null);// TODO simple response payload - responseObserver.onCompleted(); - }); - }); + final SessionState session = sessionService.getCurrentSession(); + + final SessionState.ExportObject> browserStream = + session.getExport(streamData.getRpcTicket(), "rpcTicket"); + + session.nonExport() + .require(browserStream) + .onError(responseObserver) + .submit(() -> { + browserStream.get().onMessageReceived(request, streamData); + responseObserver.onNext(null);// TODO simple response payload + responseObserver.onCompleted(); + }); } }