我有一个流服务,可以无限期地从服务器流向客户端,直到客户端取消为止。
在服务器端,我有一个线程用数据库中的数据填充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.
}
我想知道为什么服务器没有触发“上下文已取消”回调。
谢谢!
答案 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);
}