Skip to content

tonic/client: support immediate cancellation of streams without sending End-of-Stream - #2791

Draft
arjan-bal wants to merge 7 commits into
grpc:masterfrom
arjan-bal:cancellation-tonic
Draft

tonic/client: support immediate cancellation of streams without sending End-of-Stream#2791
arjan-bal wants to merge 7 commits into
grpc:masterfrom
arjan-bal:cancellation-tonic

Conversation

@arjan-bal

Copy link
Copy Markdown
Contributor

Motivation

Tonic's client streaming and bidirectional (bidi) streaming APIs do not currently support immediate request cancellation. Because outbound request streams are infallible, completing or dropping a stream automatically transmits a DATA frame with an End-of-Stream (EOS) flag, which signals a normal, graceful termination. An RST_STREAM frame is only sent by the client after the request stream terminates and the corresponding response stream is subsequently dropped.

This delayed teardown creates a problem: the server may interpret the EOS flag as a graceful finish and process or commit incomplete payloads before it ever receives the RST_STREAM frame. To prevent potential data corruption, Tonic requires a mechanism to abort and tear down the HTTP/2 stream immediately without sending a misleading EOS flag.

Solution

Introduce a paired CancellationHandle and CancellationListener mechanism to coordinate immediate, client-side cancellation of outbound request streams.

Core Changes

  • CancellationHandle (User-Facing): Users can create a handle and insert it into the gRPC request Extensions container. Invoking .cancel() on this handle triggers an immediate, non-graceful abort.
  • CancellationListener (Internal): Extracted from the request extensions by Tonic's client dispatcher to actively track the cancellation state of the active stream.
  • Lock-Free State (Concurrency Guard): Coordinated via a shared CancellationState utilizing AtomicBool and AtomicWaker. This allows lock-free, wake-ups and state checks across task boundaries without relying on mutexes.
  • Immediate Abort in Encoder (HTTP/2 Frame Interceptor): EncodeBody::poll_frame actively checks the CancellationListener. If cancellation is flagged, it immediately returns an error mapped with H2Reason::CANCEL. This bypasses the empty DATA frame with EOS and immediately triggers an HTTP/2 level RST_STREAM.

Alternative

Use the Encoder API to inject an error causing hyper to cancel the stream: #2788

This seems like a mis-use of the codec API and causes the RST_STREAM code to be internal.

@arjan-bal
arjan-bal marked this pull request as draft August 4, 2026 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant