-
Notifications
You must be signed in to change notification settings - Fork 18k
x/net/http2: Race in handler execution results in zero-byte data frame, causing incompatibility with gRPC #56317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@neild can you have a look at this? |
I did a bit more investigation into this issue, and found that the original RCA hypothesis is not entirely correct. This bug seems to be a side effect of This results in the possibility that The solution in my case is to simply omit While the behavior is inconvenient, I think all components are WAI:
|
To recap: gRPC wants a response to either consist of either a HEADERS frame, DATA frames, and a final HEADERS frame with END_STREAM set; or just a HEADERS frame with END_STREAM set. (It calls this second case "trailers only", but from an HTTP/2 perspective this is headers only. There are no trailers.) The Go HTTP/2 server will send initial HEADERS with no END_STREAM set if the headers are flushed before data is written. When The fix is to not set FlushInterval. As @LINKIWI concludes, while this is an unfortunate interaction, everything is working properly: Flushing headers before the handler returns needs to send HEADERS with no END_STREAM (because the server doesn't yet know if data will be written to the stream), and setting a negative Perhaps Otherwise, I don't think there's anything that needs doing here. |
Hi, This is what I get in my grpc reverse proxy.
and my client gets this error
which means the proxy is receiving the grpc response from the upstream, but does not handle it properly when forwarding the response to the client. |
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
In a proprietary HTTP + gRPC reverse proxy, when issuing unary gRPC calls, I observe intermittent occurrences of the error:
The occurrence of this error is not deterministically reproducible, and affects only a small percentage of requests. Usually, a client retry of the RPC alleviates the problem.
See the investigation notes after the survey questions in this issue.
What did you expect to see?
I expect to see no occurrences of this error under regular operation.
What did you see instead?
I see this error affecting 1 - 5% of requests.
Context
I'm working with a proprietary HTTP reverse proxy with built-in support for gRPC over HTTP/2.
Example
The proxy is proprietary, but its core logic is demonstrated below.
Symptom
Clients issuing gRPC calls through the proxy that return gRPC application-level errors intermittently (non-determinstically) observe errors from the grpc-go library
server closed the stream without sending trailers
.GODEBUG=http2debug=2
reveals that the issue manifests only when Go'shttp2.Server
writes aHEADERS
frame with flagEND_HEADERS
followed by a zero-byteDATA
frame with flagEND_STREAM
.The issue does not manifest (i.e. the application-level error is propagated correctly) when Go's
http2.Server
writes aHEADERS
frame with flagsEND_HEADERS | END_STREAM
.Note that there are no trailers included in this message.
Example trace with no errors (RPC returns successfully)
Example trace with error (internal error raised by grpc-go)
RCA
I believe this is due to a race caused by concurrent
http.Handler
execution inhttp2/server.go
.In the case that handler execution completes before headers are written,
rws.handlerDone
is true and Go includesEND_STREAM
in the initialHEADERS
frame. In the case that handler execution is still in-progress when the first write occurs, theHEADERS
frame is written withoutEND_STREAM
, and a subsequent write sends a zero-byte data frame withEND_STREAM
, acting purely as a control message.Ultimately this causes non-determinism where the specific scenario that unary gRPC methods that return errors quickly are disproportionately affected.
According to gRPC specification,
END_STREAM
should be included in the lastHEADERS
frame to indicate termination of the response. In grpc-go, encounteringEND_STREAM
in a data frame is an explicit error case. However, HTTP/2 protocol specification itself doesn't prohibit this.Proposal
A similar (identical?) issue was identified in nghttp2 (see: nghttp2/nghttp2#588). The submitted fix was to include
END_STREAM
in theHEADERS
payload if the body is empty and no trailers exist. I'm not sure if a similar approach is feasible inhttp2.Server
.The text was updated successfully, but these errors were encountered: