fix(tonic-web): don't drop an already-parsed trailer when it arrives alongside data in one poll#2727
Open
a5x10 wants to merge 1 commit into
Open
Conversation
… poll_frame GrpcWebCall::poll_frame's client+Decode branch correctly parses and stashes a trailer into self.trailers when it arrives in the same poll_frame call as preceding data (e.g. a fully-buffered response, or any transport that happens to deliver a small response in one read). But the immediately following poll_frame call - required by the http_body::Body contract before the caller treats the stream as ended - finds the accumulation buffer empty, takes the FindTrailers::Done(0) arm, and returns None unconditionally, without ever checking self.trailers. The already-parsed trailer is silently dropped; a tonic client sees "missing grpc-status trailer, stream was terminated without a final status" even though the server sent a perfectly well-formed response. Fix mirrors the FindTrailers::Trailer(len) arm's own existing fallback a few lines above: check self.trailers.take() before returning None. Found via a real production failure (esteem CLI's grpc-web client against a real deployment) - root-caused offline with a byte-exact capture of an actual 232-byte production response, confirmed the bytes are a perfectly well-formed grpc-web reply, and isolated the bug with a minimal, network-free repro (included as the new regression test) before touching this crate at all.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
GrpcWebCall::poll_frame's client-side decode branch (tonic-web/src/call.rs) correctly parses and stashes a trailer frame intoself.trailerswhen it arrives in the samepoll_framecall as preceding data — e.g. a fully-buffered response, or any transport that happens to deliver a small response in one read.But the immediately-following
poll_framecall (required by thehttp_body::Bodycontract before the caller treats the stream as ended) finds the accumulation buffer empty, takes theFindTrailers::Done(0)arm, and returnsNoneunconditionally, without ever checkingself.trailers:The already-parsed trailer is silently dropped. A
tonicclient sees:even though the server sent a perfectly well-formed grpc-web response.
Compare the
FindTrailers::Trailer(len)arm a few lines above, which does correctly fall back to checkingself.trailerswhen its own leading data is empty —Done(0)has no equivalent fallback. That asymmetry is the actual bug, not a buffering/timing edge case.Repro
Found via a real production failure (a
tonic-based CLI's grpc-web client against a real deployment). Root-caused offline: captured the actual bytes of a real 232-byte production response, confirmed by hand that they're a perfectly well-formed grpc-web reply (one data frame immediately followed by a complete trailer frame), then reproduced the drop with a minimal, network-free test — included in this PR ascall::tests::trailer_not_dropped_when_buffered_with_data. The new test fails without this fix and passes with it (verified both ways before opening this PR).Fix
One arm, mirroring the
FindTrailers::Trailer(len)arm's own existing fallback: checkself.trailers.take()before returningNone.Full
tonic-webtest suite (22 passed, 1 pre-existing#[ignore]d unrelated test, 0 failed) andcargo fmt --checkboth clean on this branch.