grpc-ecosystem / grpc-spring

Spring Boot starter module for gRPC framework.
https://grpc-ecosystem.github.io/grpc-spring/
Apache License 2.0
3.46k stars 811 forks source link

How to cancel server request execution as soon as a client cancels a request due to a timeout for example? #989

Closed heshamMassoud closed 9 months ago

heshamMassoud commented 9 months ago

The context

I would like to stop the execution of the gRPC call on the server side, whenever the client cancels the request (due to a timeout for example).

The question

Is there a way to handle client cancellation interrupts on the server? Is this possible with the gRPC-Spring-Boot-Starter?

The application's environment

Which versions do you use?

ST-DDT commented 9 months ago

Generally speaking this works the same in grpc-spring as in regular grpc-java.

On a per method level you could cast the StreamObserver to set a cancellation handler, you can then use that to interrupt the execution. Please note that this depends on whether the method is unary or streaming and what exactly you are doing in there. https://grpc.github.io/grpc-java/javadoc/io/grpc/stub/ServerCallStreamObserver.html#setOnCancelHandler(java.lang.Runnable)

For streaming calls, it might be sufficient to just close/complete the observer. For long running processing steps/unary calls, it might be necessary to keep track of the executing Thread and send Thread.interrupt() there. Just make sure the Thread is actually still processing the method, and the method is capable of being interrupted.

When using an interceptor this works somewhat similar, you have to check which thread is used to execute the method. Just check the stacktrace when executing the method where this passes through the ServerInterceptor/ServerCall/ServerCallListener. Store the used thread for the duration of the method invocation ONLY!

e.g.

private final List<Thread> threads;

onSomething(...) {
  try {
    threads.add(Thread.current());
    super.onSomething();
  } catch (InterruptedException e) {
    // check if you have interrupted it, if yes, catch it, re-throw it otherwise
  } finally {
    threads.remove(Thread.current());
  }
}

There are (Simple)ForwardingServerCall(Listener) implementations that you can probably use for the other required parts.

Whatever variant you are using, make sure that you try it out in a heavy concurrent environment, because with low throughput it uses the same Thread throughout the entire lifecycle of a call, whereas it might use different threads otherwise. See #126 for my fix for running into issues there.

ST-DDT commented 9 months ago

I consider this solved. If you have further questions regarding this, please write them below.