-
Notifications
You must be signed in to change notification settings - Fork 18k
x/net/http2: return flow control when stream closes with buffered data #16481
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
Your patch looks like you're just sending infinite flow control, giving them new tokens whenever they send new stuff, without any user acknowledgement. Do you have a more complete repro or instructions? |
Also, can you try Go 1.7? @adg changed some flow control stuff recently. |
The logic is this: when server receives a data frame, it should send WindowUpdate(len(data) ) for this client connection, even if the stream has error or closed and data can not be delivered to the handler. When data is read by handler, then it should send WindowUpdate for this stream. I used the latest golang.org/x/net/http2. |
But that doesn't look like what your patch does. You seem to be always sending flow control, not just for closed streams. So would a better summary of the problem be: "the server doesn't refresh connection-level flow control when clients send DATA frames to closed streams" ? |
You summary is accurate. The patch does not always send flow control, it separates sending connection-level flow control and stream level: That being said, I am not trying to propose a fix, just trying to provide more info to help find the issue. |
It seems transport has similar issue: the client (or transport) doesn't refresh connection-level flow control when server sends DATA frames to closed streams". In transport.go, sending flow control for connection-level and stream-level are both in Read(). I think connection-level flow control should be sent whenever a data frame is received, stream-level flow control should be sent when data is read by handler. |
No, that doesn't sound right. If that were done, clients could repeatedly open new streams (which start with a default flow control) and write an unbounded amount. I think we need to verify buffered flow control (from server-read but handler-unread bytes) is always returned on stream close, or when writing to a stream not accepting data. I've been traveling the past 20 hours (to an HTTP workshop, as it happens) so I'll look at this after I've had some sleep and get a free moment. If you or somebody else wants to write stand-alone repro unit tests in the x/net/http2 style, using its existing test framework, that would be helpful. |
This is actually very easy to reproduce: type funcReader func([]byte) (n int, err error)
func (f funcReader) Read(p []byte) (n int, err error) { return f(p) }
func TestUnreadFlowControlReturned(t *testing.T) {
unblock := make(chan bool, 1)
defer close(unblock)
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
<-unblock
}, optOnlyServer)
defer st.Close()
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
defer tr.CloseIdleConnections()
for i := 0; i < 10; i++ {
println(i)
body := io.MultiReader(
io.LimitReader(neverEnding('A'), 16<<10),
funcReader(func([]byte) (n int, err error) {
unblock <- true
return 0, io.EOF
}),
)
req, _ := http.NewRequest("POST", st.ts.URL, body)
res, err := tr.RoundTrip(req)
if err != nil {
t.Fatal(err)
}
res.Body.Close()
println(i, "done")
}
} As feared, it hangs after 3 iterations, blocked on waiting for flow control to write the 4th body:
This is pretty bad and I'm surprised nobody has encountered this earlier. Thanks for reporting it. @adg, @broady, this isn't a regression from Go 1.6, but it's pretty nasty and I think the fix will be easy. |
Thanks for the update and for finding the issue #16498. After fixing these issues, it would be nice to test the fair share of bandwidth both ways. For example, with a fix rate connection, open one stream, verify it utilizes all the bandwidth, then open another stream, each should use 1/2. Then keep 2 steams open and stop transferring on one steam, the other one should use the full bandwidth again. Then open more steams and so on so forth. Also verify no steam blocks the connection level frames. SSH implements streams with flow control, which could be referenced. |
CL https://golang.org/cl/25231 mentions this issue. |
Is that a regression from 1.5? |
@broady, yes. The bug was introduced in Go 1.6, so arguably it's not Go 1.7 material since it's not a 1.6->1.7 regression, but it's a pretty bad case (things fail to work) and there's no real workaround. I'm surprised nobody has reported it previously. It would only take effect for certain traffic patterns where things were aborted without reading bodies. |
Thanks for your quick response (test and confirm the issue, find #16498, With the fix, there are still uncovered cases. For example, if the The basic idea of windows based flow control is: sender send n bytes of For connection level, whenever a data frame is processed, it is For steam level, the data is "consumed" only when the handler read it, |
I'm +1 to get this into 1.7, since it's a regression. I don't think there necessarily needs to be a regression from the preceding major version to warrant inclusion at this point. What do you think, @adg? |
No, with the CL I sent out, that's exactly one of the cases that's tested and fixed. If you have a test program to demonstrate otherwise I'd be interested.
I'm not sure what you mean by this. HTTP/2 defines both connection-level and stream-level flow control, and rules for each. Incoming DATA frame bytes deduct against both. What do you think we're doing wrong specifically? |
Still need to bundle it in to std. |
CL https://golang.org/cl/25282 mentions this issue. |
Here is a test to show one stream can still block the whole connection: |
@wujieliulan, the behavior for your h2test.tar.gz test program looks correct per the spec with the default of conn-level flow control and stream-level flow control being equal. You have one stream exhausting the conn-level flow control and blocking forever, so it makes sense that the other one can't proceed. Maybe we could have different default flow control values, having more for the conn so a single bad stream can't have such impact. Can you file a separate bug for that? This one is now closed and we don't reuse bugs. Feel free to reference this one in your new bug report. |
From rfc7540: The test is an extreme case, but in real world, a steam with a slow consumer (for example, the handler can't consume data fast enough), that steam will block all other steams in the same connection, even if the connection is wide open and have lots of available bandwidth. Since this bug is closed, let me file another one. Thanks. |
CL https://golang.org/cl/25382 mentions this issue. |
http://httpwg.org/specs/rfc7540.html#rfc.section.6.1 says: > The entire DATA frame payload is included in flow control, including > the Pad Length and Padding fields if present. But we were just ignoring the padding and pad length, which could lead to clients and servers getting out of sync and deadlock if peers used padding. (Go never does, so it didn't affect Go<->Go) In the process, fix a lingering bug from golang/go#16481 where we didn't account for flow control returned to peers in the stream's inflow, despite sending the peer a WINDOW_UPDATE. Fixes golang/go#16556 Updates golang/go#16481 Change-Id: If7150fa8f0da92a60f34af9c3f754a0346526ece Reviewed-on: https://go-review.googlesource.com/25382 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Andrew Gerrand <adg@golang.org>
CL https://golang.org/cl/25388 mentions this issue. |
Updates bundled http2 to x/net/http2 rev 28d1bd4f for: http2: make Transport work around mod_h2 bug https://golang.org/cl/25362 http2: don't ignore DATA padding in flow control https://golang.org/cl/25382 Updates #16519 Updates #16556 Updates #16481 Change-Id: I51f5696e977c91bdb2d80d2d56b8a78e3222da3f Reviewed-on: https://go-review.googlesource.com/25388 Reviewed-by: Chris Broadfoot <cbro@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
For both the server and the transport, return connection-level flow control in two cases: 1) when a stream is closed with buffered data not read by the user, or 2) when a DATA frame arrives but there the stream has since been closed. Fixes golang/go#16481 Change-Id: Ic7404180ed04a2903e8fd6e9599a907f88b4f72e Reviewed-on: https://go-review.googlesource.com/25231 Reviewed-by: Andrew Gerrand <adg@golang.org>
Updates x/net/http2 to git rev 6a513af for: http2: return flow control for closed streams https://golang.org/cl/25231 http2: make Transport prefer HTTP response header recv before body write error https://golang.org/cl/24984 http2: make Transport treat "Connection: close" the same as Request.Close https://golang.org/cl/24982 Fixes golang/go#16481 Change-Id: Iaddb166387ca2df1cfbbf09a166f8605578bec49 Reviewed-on: https://go-review.googlesource.com/25282 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Andrew Gerrand <adg@golang.org>
I ran into a case where my http2 transport cannot send any data to my http2 server with the existing connection. The issue is client sent some data in a stream, when server receives it, there was a stream error (or stream closed) and the data received was discarded., server never got a chance to send windowupdate for this client connection, client can no longer send more data, even for new streams.
I did some simple changes and this issue is fixed, but I am not sure if this is the right way to fix. Could you advice? Is there a similar issue for server sending to client?
The text was updated successfully, but these errors were encountered: