grpc-java icon indicating copy to clipboard operation
grpc-java copied to clipboard

stub: ensure BlockingClientCall tasks run after cancellation

Open benjaminp opened this issue 3 months ago • 3 comments

After canceling a BlockingClientCall, the caller may not interact with it further. That means the call executor, a ThreadSafeThreadlessExecutor, will not execute any more tasks. There may still be tasks submitted to the call executor, though, until the underlying call completes. Some of these tasks (e.g., server messages available) may leak native resources unless executed. So, we convert the executor to a "direct" executor during cancellation in order to ensure all call tasks run.

Fixes https://github.com/grpc/grpc-java/issues/12355.

benjaminp avatar Sep 22 '25 16:09 benjaminp

The trouble is I don't think .cancel should be blocking.

benjaminp avatar Sep 22 '25 22:09 benjaminp

Another option might be to dump post-cancellation tasks on whatever executor would have been used for the call if we didn't force ThreadsafeThreadlessExecutor in.

benjaminp avatar Sep 23 '25 02:09 benjaminp

I expect we really want to drain the executor until the Listener.onClose() is delivered.

So you're proposal would be to make BlockingClientCall.cancel look something like this?

  public void cancel(String message, Throwable cause) {
    writeClosed = true;
    call.cancel(message, cause);
    boolean interrupted = Thread.interrupted();
    while (true) {
      try {
        executor.waitAndDrain(x -> x.closeState.get() != null, this);
        break;
      } catch (InterruptedException e) {
        interrupted = true;
      }
    }
    if (interrupted) {
      Thread.currentThread().interrupt();
    }
  }

benjaminp avatar Nov 04 '25 16:11 benjaminp