Conversation
… with Code.Unavailable When a peer (typically a proxy such as Envoy) resets a stream with RST_STREAM code NO_ERROR before the response completed, Node's http2 client (pre nodejs/node#63249) surfaces the truncation as a clean, error-free end-of-stream. The protocol layer then fails parsing the truncated body with misleading errors: "protocol error: incomplete envelope" (invalid_argument) or "protocol error: missing status" (internal). Track whether a request that declared "TE: trailers" (as gRPC requests always do) received its terminating trailers or a trailers-only response, and reject with Code.Unavailable from the close handler when the stream ended with rstCode 0 without either. Verified: package tests 113/113 (5 new failure-mode tests fail without the fix, 4 new non-regression tests), conformance 6258/6258 client and 4718/4718 server. Signed-off-by: Robin Wieruch <hello@rwieruch.com>
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.
Note from myself, because the issue/PR were crafted by Fable 5: If you do not accept such Issues/PRs, feel free to close both. However, our team ran into this issue five days ago and I investigate the issue yesterday with Fable 5 and a backend peer from our team who has control over Envoy.
Fixes #1751.
When a peer (typically a proxy such as Envoy) resets a stream with
RST_STREAM code=0 (NO_ERROR)before the response completed, Node's http2 client (pre nodejs/node#63249) surfaces the truncation as a clean, error-free end-of-stream withrstCode 0. The protocol layer then fails parsing the truncated body with misleading wire-format errors —[invalid_argument] protocol error: incomplete envelope(cut mid-message) or[internal] protocol error: missing status(cut before trailers) — blaming the server and reading as non-retryable, when the actual failure is a transport-level abort. See #1751 for the full evidence (Envoy edge logs, instrumented client traces, controls).What this PR does
In
h2Request()'s close handler: when the request declaredTE: trailers(as gRPC requests always do), and the stream closes withrstCode 0without a'trailers'event and without a trailers-only response (grpc-statusin the initial HEADERS), reject withCode.Unavailable:This mirrors grpc-js, which reports the same case as
Received RST_STREAM with code 0 (Call ended without gRPC status)and ignores NO_ERROR resets only when the status already arrived.Design notes
TE: trailerssignal: pre-#63249, no public Node API distinguishes this truncation generically — the'trailers'event is the only deterministic signal (stream.closed-at-'end'is a timing artifact,endAfterHeadersdoes not help). The TE header is how the request itself declares that trailers must terminate the response, so the guard is scoped precisely to protocols that need it. If you'd prefer an explicit option plumbed down from the transports instead of reading the request header, happy to rework.TE: trailersrequests the new guard also catches the pre-response close (reject instead of hang), while requests withoutTE: trailerskeep today's behavior in that case, which remains Reject unary calls when the http/2 stream closes with NO_ERROR before the response #1747's scope. The two compose cleanly if both land.Testing
createTransportfromprotocol-grpcwith real error-message assertions); 4 new non-regression tests (post-trailers reset succeeds, trailers-only response succeeds, trailerless non-TE response succeeds, HTTP error response without trailers keeps its HTTP status error).Unavailableon the identical failing streams (details in connect-node: RST_STREAM(NO_ERROR) mid-response yields misleadingincomplete envelope/missing statusinstead of a transport error #1751).