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) return null; return Status.INTERNAL.asException(); }, (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
Called bycleanup(int)
.protected DispatchingOnReadyHandler.ThrowingFunction<Integer,Boolean>
completionIndicator
Called byisCompleted(int)
.protected BiFunction<Integer,Throwable,Throwable>
exceptionHandler
Called byhandleException(int, Throwable)
.protected DispatchingOnReadyHandler.ThrowingFunction<Integer,ResponseT>
messageProducer
Called byproduceMessage(int)
.
-
Constructor Summary
Constructors Modifier Constructor Description protected
DispatchingOnReadyHandler(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(CallStreamObserver<ResponseT> streamObserver, Executor taskExecutor, int numberOfTasks, Function<Integer,Boolean> completionIndicator, Function<Integer,ResponseT> messageProducer)
Constructs a handler for "no-exception" case.DispatchingOnReadyHandler(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(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(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)
Cleans up after taski
is completed.protected Throwable
handleException(int i, Throwable error)
Handles exception thrown by taski
.protected boolean
isCompleted(int i)
Indicates if the taski
is completed.protected ResponseT
produceMessage(int i)
Asks taski
to produce a next message.void
run()
Dispatches tasks to handle a single cycle of observer's readiness.void
setTaskToStringHandler(Function<Integer,String> taskToStringHandler)
Sets handler to obtain String representation of taski
for logging purposes.
-
-
-
Field Detail
-
completionIndicator
protected DispatchingOnReadyHandler.ThrowingFunction<Integer,Boolean> completionIndicator
Called byisCompleted(int)
.
-
messageProducer
protected DispatchingOnReadyHandler.ThrowingFunction<Integer,ResponseT> messageProducer
Called byproduceMessage(int)
.
-
exceptionHandler
protected BiFunction<Integer,Throwable,Throwable> exceptionHandler
Called byhandleException(int, Throwable)
.
-
cleanupHandler
protected Consumer<Integer> cleanupHandler
Called bycleanup(int)
.
-
-
Constructor Detail
-
DispatchingOnReadyHandler
public DispatchingOnReadyHandler(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(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(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(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(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.
-
-
Method Detail
-
isCompleted
protected boolean isCompleted(int i) throws Exception
Indicates if the taski
is completed. Default implementation callscompletionIndicator
.- Throws:
Exception
-
produceMessage
protected ResponseT produceMessage(int i) throws Exception
Asks taski
to produce a next message. Default implementation callsmessageProducer
.- Throws:
Exception
-
handleException
protected Throwable handleException(int i, Throwable error)
Handles exception thrown by taski
. Default implementation callsexceptionHandler
.
-
cleanup
protected void cleanup(int i)
Cleans up after taski
is completed. Default implementation callscleanupHandler
.
-
setTaskToStringHandler
public void setTaskToStringHandler(Function<Integer,String> taskToStringHandler)
Sets handler to obtain String representation of taski
for logging purposes.
-
-