客户端取消服务调用时不会触发gRPC服务的Context CancellationListener

时间:2018-09-12 15:45:19

标签: grpc grpc-java

我有一个流服务,可以无限期地从服务器流向客户端,直到客户端取消为止。

在服务器端,我有一个线程用数据库中的数据填充ehcache。

Ehcache提供有关缓存事件的回调,例如,何时添加项目,何时删除项目等。我只关心在将元素放入缓存时通知客户端,因此当客户端连接到我的gRPC服务时,我在缓存中注册了notifyElementPut()回调,该回调具有对已连接客户端StreamObserver的引用:

public class GrpcAwareCacheEventListener extends CacheEventListenerAdapter {


  private StreamObserver<FooUpdateResponse> responseObserver;

  public GrpcAwareCacheEventListener(
      StreamObserver<FooUpdateResponse> responseObserver) {
    this.responseObserver = responseObserver;
  }


  @Override
  public void notifyElementPut(Ehcache cache, Element element) throws CacheException {

    Foo foo = (Foo) element.getObjectValue();
    if (foo != null) {
      responseObserver.onNext(
          FooResponse.newBuilder().setFoo(foo).build());
    }
  }
}

我的流式foo服务如下:

    public void streamFooUpdates(Empty request,
              StreamObserver<FooResponse> responseObserver) {

            final CacheEventListener eventListener = new GrpcAwareCacheEventListener(responseObserver);
            fooCache.getCacheEventNotificationService().registerListener(eventListener);
            Context.current().withCancellation().addListener(new CancellationListener() {

              public void cancelled(Context context) {
    log.info("inside context cancelled callback");      
  fooCache.getCacheEventNotificationService().unregisterListener(eventListener);
              }

            }, ForkJoinPool.commonPool());



          }

这一切正常,只要连接客户端,便通知客户端所有foo更新。

但是,在客户端断开连接或显式取消呼叫之后,我希望服务器的Context的取消侦听器将触发,并在缓存中注销回调。

情况并非如此,无论客户端是关闭通道还是显式取消呼叫。 (我希望服务器端取消的上下文会为这两个事件触发)。我想知道我在客户端的取消语义是否不正确,这是我的客户代码,取自一个测试用例:

Channel channel = ManagedChannelBuilder.forAddress("localhost", 25001)
        .usePlaintext().build();

    FooServiceGrpc.FooService stub = FooServiceGrpc
        .newStub(channel);


    ClientCallStreamObserver<FooResponse> cancellableObserver = new ClientCallStreamObserver<FooResponse>(){
      public void onNext(FooResponse response) {
        log.info("received foo: {}", response.getFoo());
      }

      public void onError(Throwable throwable) {

      }

      public void onCompleted() {

      }

      public boolean isReady() {
        return false;
      }

      public void setOnReadyHandler(Runnable runnable) {

      }

      public void disableAutoInboundFlowControl() {

      }

      public void request(int i) {

      }

      public void setMessageCompression(boolean b) {

      }

      public void cancel(@Nullable String s, @Nullable Throwable throwable) {

      }
    };

    stub.streamFooUpdates(Empty.newBuilder().build(), cancellableObserver);
    Thread.sleep(10000); // sleep 10 seconds while messages are received.
    cancellableObserver.cancel("cancelling from test", null); //explicit cancel
    ((ManagedChannel) chan).shutdown().awaitTermination(5, TimeUnit.SECONDS); //shutdown as well, for good measure.

    Thread.sleep(7000); //channel should be shutdown by now.

  }

我想知道为什么服务器没有触发“上下文已取消”回调。

谢谢!

1 个答案:

答案 0 :(得分:3)

您没有正确取消客户端呼叫。 StreamObserver第二个参数上的stub.streamFooUpdates()是您的回调。您不应该在该StreamObserver上拨打任何电话。

有两种方法可以从客户端取消呼叫。

选项1:将ClientResponseObserver作为第二个参数,实现beforeStart(),这会给您一个ClientCallStreamObserver,您可以在其中调用cancel()

选项2:在stub.streamFooUpdates()内运行CancellableContext,然后取消Context以取消呼叫。请注意,CancellableContext必须始终被取消,这就是finally块的作用。

CancellableContext withCancellation = Context.current().withCancellation();
try {
  withCancellation.run(() -> {
      stub.streamFooUpdates(...);
      Thread.sleep(10000);
      withCancellation.cancel(null);
  });
} finally {
  withCancellation.cancel(null);
}