Class DispatchingOnReadyHandler<ResponseT>
- java.lang.Object
-
- pl.morgwai.base.grpc.utils.DispatchingOnReadyHandler<ResponseT>
-
- All Implemented Interfaces:
Runnable
public class DispatchingOnReadyHandler<ResponseT> extends Object implements Runnable
Handles streaming of messages to aCallStreamObserver
from multiple threads with respect to flow-control to ensure that no excessive buffering occurs.Setting an instance using
setOnReadyHandler(dispatchingOnReadyHandler)
will eventually have similar effects as the below pseudo-code:for (int i = 0; i < numberOfTasks; i++) taskExecutor.execute(() -> { try { while ( ! completionIndicator.apply(i)) streamObserver.onNext(messageProducer.apply(i)); if (allTasksCompleted()) streamObserver.onCompleted(); } catch (Throwable t) { var toReport = handleException(taskNumber, throwable); if (toReport != null) streamObserver.onError(toReport); } finally { cleanupHandler.accept(i); } });
However, calls tostreamObserver
are properly synchronized and the work is automatically suspended/resumed wheneverstreamObserver
becomes unready/ready and executor's threads are released during time when observer is unready.Typical usage:
public void myServerStreamingMethod( RequestMessage request, StreamObserver<ResponseMessage> basicResponseObserver) { var state = new MyCallState(request, NUMBER_OF_TASKS); var responseObserver = (ServerCallStreamObserver<ResponseMessage>) basicResponseObserver; responseObserver.setOnCancelHandler(() -> log.fine("client cancelled")); final var handler = new DispatchingServerStreamingCallHandler<>( responseObserver, taskExecutor, NUMBER_OF_TASKS, (i) -> state.isCompleted(i), (i) -> state.produceNextResponseMessage(i), (i, error) -> { state.fail(error); // interrupt other tasks if ( ! (error instanceof StatusRuntimeException)) { synchronized (handler) { responseObserver.onError(error); } } }, (i) -> state.cleanup(i) ); responseObserver.setOnReadyHandler(handler); }
NOTE: this class is not suitable for cases where executor's thread should not be released, such as JDBC/JPA processing where executor threads correspond to pooled connections that must be retained in order not to lose given DB transaction/cursor. In such cases processing should be implemented similar as the below code:
public void myServerStreamingMethod( RequestMessage request, StreamObserver<ResponseMessage> basicResponseObserver) { responseObserver.setOnReadyHandler(() -> { synchronized (responseObserver) { responseObserver.notify(); } }); jdbcExecutor.execute(() -> { try { var state = new MyCallState(request); while ( ! state.isCompleted()) { synchronized (responseObserver) { while ( ! responseObserver.isReady()) responseObserver.wait(); } responseObserver.onNext(state.produceNextResponseMessage()); } responseObserver.onCompleted(); } catch (Throwable t) { if ( ! (t instanceof StatusRuntimeException)) responseObserver.onError(t); } finally { state.cleanup(); } }); }
-
-
Nested Class Summary
Nested Classes Modifier and Type Class Description static interface
DispatchingOnReadyHandler.ThrowingFunction<ParamT,ResultT>
-
Field Summary
Fields Modifier and Type Field Description protected Consumer<Integer>
cleanupHandler
protected DispatchingOnReadyHandler.ThrowingFunction<Integer,Boolean>
completionIndicator
protected BiFunction<Integer,Throwable,Throwable>
exceptionHandler
protected DispatchingOnReadyHandler.ThrowingFunction<Integer,ResponseT>
messageProducer
-
Constructor Summary
Constructors Modifier Constructor Description protected
DispatchingOnReadyHandler(io.grpc.stub.CallStreamObserver<ResponseT> streamObserver, Executor taskExecutor, int numberOfTasks)
Constructor for those who prefer to overrideisCompleted(int)
,produceMessage(int)
,handleException(int, Throwable)
andcleanup(int)
in a subclass instead of providing lambdas.DispatchingOnReadyHandler(io.grpc.stub.CallStreamObserver<ResponseT> streamObserver, Executor taskExecutor, int numberOfTasks, Function<Integer,Boolean> completionIndicator, Function<Integer,ResponseT> messageProducer)
Constructs a handler for "no-exception" case.DispatchingOnReadyHandler(io.grpc.stub.CallStreamObserver<ResponseT> streamObserver, Executor taskExecutor, int numberOfTasks, DispatchingOnReadyHandler.ThrowingFunction<Integer,Boolean> completionIndicator, DispatchingOnReadyHandler.ThrowingFunction<Integer,ResponseT> messageProducer, BiFunction<Integer,Throwable,Throwable> exceptionHandler, Consumer<Integer> cleanupHandler)
Constructs a "full-version" handler that includes handling exception thrown bycompletionIndicator
andmessageProducer
.DispatchingOnReadyHandler(io.grpc.stub.CallStreamObserver<ResponseT> streamObserver, Executor taskExecutor, Callable<Boolean> completionIndicator, Callable<ResponseT> messageProducer, Function<Throwable,Throwable> exceptionHandler, Runnable cleanupHandler)
Constructs a handler for "single-thread" case that includes handling exception thrown bycompletionIndicator
andmessageProducer
.DispatchingOnReadyHandler(io.grpc.stub.CallStreamObserver<ResponseT> streamObserver, Executor taskExecutor, Supplier<Boolean> completionIndicator, Supplier<ResponseT> messageProducer)
Constructs a handler for "no-exception single-thread" case.
-
Method Summary
All Methods Instance Methods Concrete Methods Modifier and Type Method Description protected void
cleanup(int i)
protected Throwable
handleException(int i, Throwable error)
protected boolean
isCompleted(int i)
protected ResponseT
produceMessage(int i)
void
run()
Dispatches tasks to handle a single cycle of#responseObserver
's readiness.
-
-
-
Field Detail
-
completionIndicator
protected DispatchingOnReadyHandler.ThrowingFunction<Integer,Boolean> completionIndicator
-
messageProducer
protected DispatchingOnReadyHandler.ThrowingFunction<Integer,ResponseT> messageProducer
-
exceptionHandler
protected BiFunction<Integer,Throwable,Throwable> exceptionHandler
-
-
Constructor Detail
-
DispatchingOnReadyHandler
public DispatchingOnReadyHandler(io.grpc.stub.CallStreamObserver<ResponseT> streamObserver, Executor taskExecutor, int numberOfTasks, DispatchingOnReadyHandler.ThrowingFunction<Integer,Boolean> completionIndicator, DispatchingOnReadyHandler.ThrowingFunction<Integer,ResponseT> messageProducer, BiFunction<Integer,Throwable,Throwable> exceptionHandler, Consumer<Integer> cleanupHandler)
Constructs a "full-version" handler that includes handling exception thrown bycompletionIndicator
andmessageProducer
.If and only if
exceptionHandler
returns non-null andStreamObserver.onError(Throwable)
hasn't been called yet, then it will be called with obtained value as its argument.
-
DispatchingOnReadyHandler
public DispatchingOnReadyHandler(io.grpc.stub.CallStreamObserver<ResponseT> streamObserver, Executor taskExecutor, int numberOfTasks, Function<Integer,Boolean> completionIndicator, Function<Integer,ResponseT> messageProducer)
Constructs a handler for "no-exception" case.If
Error
orRuntimeException
occurs, it is reported viaStreamObserver.onError(Throwable)
(except forStatusRuntimeException
and unless some error has been already reported) and re-thrown (includingStatusRuntimeException
).
-
DispatchingOnReadyHandler
public DispatchingOnReadyHandler(io.grpc.stub.CallStreamObserver<ResponseT> streamObserver, Executor taskExecutor, Supplier<Boolean> completionIndicator, Supplier<ResponseT> messageProducer)
Constructs a handler for "no-exception single-thread" case.This is roughly equivalent to
#copyWithFlowControl(java.util.Iterator, CallStreamObserver)
, except that it will run on different executor and forError
/RuntimeException
reporting.
-
DispatchingOnReadyHandler
public DispatchingOnReadyHandler(io.grpc.stub.CallStreamObserver<ResponseT> streamObserver, Executor taskExecutor, Callable<Boolean> completionIndicator, Callable<ResponseT> messageProducer, Function<Throwable,Throwable> exceptionHandler, Runnable cleanupHandler)
Constructs a handler for "single-thread" case that includes handling exception thrown bycompletionIndicator
andmessageProducer
.
-
DispatchingOnReadyHandler
protected DispatchingOnReadyHandler(io.grpc.stub.CallStreamObserver<ResponseT> streamObserver, Executor taskExecutor, int numberOfTasks)
Constructor for those who prefer to overrideisCompleted(int)
,produceMessage(int)
,handleException(int, Throwable)
andcleanup(int)
in a subclass instead of providing lambdas.
-
-