vert.x
vert.x copied to clipboard
gRPC server does not get notified about cancellation on client side during non-streaming requests
Version
4.5.14
Context
I'm using Quarkus and Mutiny to implement gRPC services and clients. I noticed that a Uni or Multi subscription cancellation does not get forwarded to the server. To solve this, I created a PR on Quarkus. My tests fail if Vert.x gRPC server support is enabled on Quarkus (quarkus.grpc.server.use-separate-server=false) when the request is a Uni. I found that the Http2ServerRequest.handleReset() method is responsible for this because the reset exception is not forwarded to the event handler since the request has already ended.
Shouldn't this event still be forwarded? Otherwise, the server has no way to get notified about cancellation events from the client. In reactive programming, it is crucial for the server to be aware of cancellation events to manage resources effectively and maintain proper communication flow.
Do you have a reproducer?
See the integration tests on my PR on Quarkus. The O2NCancellationTest and VertxCancellationTest need to be enabled, as they currently fail due to this issue.
can you elaborate more on the flow that should happen ? it does not seem clear to given that this involves using gRPC stub machinery
@vietj Sorry for my late response. I'm having a hard time to understand what's going on inside Vert.x. I've been using Vert.x indirectly via Quarkus only.
I've created a small example app. It's a copy from the original gRPC cancellation example reduced to the minimum. The example includes two servers, one using the gRPC libs and one using the Vert.x libs. If you start the gRPC server and then the client, you can see that the server sees the cancellation event. This doesn't work the same way using the Vert.x server.
I think the example app shared by @alerosmile https://github.com/alerosmile/vert.x-grpc-cancellation-test is demonstrating exactly the problem we are interested in.
Run mvn verify to generate the java code corresponding to the proto.
Start one of the servers:
CancellationServerCancellationServerVertx
I was not really able to understand how the CancellationClient is illustrating the problem, but with a regular grpc client I triggered 2 calls:
- The first one: I waited long enough for the response to come back
- The second one: I cancelled at client side. (Operation cancelled, You have cancelled the execution of the method. Cancelled on client)
Echo/UnaryEcho
{
"message": "hello"
}
Results:
Logs with the CancellationServer:
Server started, listening on 50051
Unary RPC started: hello
Unary RPC completed
Unary RPC started: hello
Unary RPC cancelled
Logs with the CancellationServerVertx:
Jul 24, 2025 9:25:04 AM io.vertx.launcher.application.VertxApplication
INFO: Succeeded in deploying verticle
Unary RPC started: hello
Unary RPC completed
Unary RPC started: hello
Unary RPC completed
@jmini The CancellationClient just starts a single request and then cancels the call after 500ms. The example is a copy from the original gRPC cancellation example reduced to the minimum. What do you think is wrong with it?
Nothing wrong, just I do not see the expected logs in the server.
This is what I see in the log of the CancellationClient run:
Yelling: Async client or bust!
RPC failed: Status{code=CANCELLED, description=That's enough. I'm bored, cause=null}
Process finished with exit code 0
But server-side I see nothing… So I am not sure it works.
My test with the requests sent and cancelled from an external tools gave me better results and the expected outputs at server-side (especially illustrating what does not work with the vertx implementation for the same call)
Next step is to start investigating what is going on (probably some really low level stuff), probably debugging this code-base https://github.com/eclipse-vertx/vertx-grpc
If you have any pointers, this is appreciated.
@jmini You probably have to change the active console (especially if you start it inside Eclipse).
No, I don't have any clues except the ones from my initial comment. Oddly enough, the example app behaves somehow different in that Http2ServerRequest.handleReset() is not called at all.
@vietj Could you please have a look into this?
@alerosmile will do