Skip to content

Commit 3eb9327

Browse files
committed
http2: do not retry RoundTrip after peer sends a stream protocol error
Years back, our HTTP/2 server implementation had a stream accounting bug that would cause it to improperly report a PROTOCOL_ERROR. In response to this, we modified our Transport to retry RoundTrip when a RST_STREAM with PROTOCOL_ERROR was received from a peer. At this point, this retry logic had outlived its usefulness. Instead, it might cause issues, e.g. a client that sends a malformed request will keep retrying repeatedly, despite there being zero chance for the request to actually succeed. Fixes golang/go#77843 Change-Id: Ic043723e3535f68f91db33d8f6bcd7fc2dbce856 Reviewed-on: https://go-review.googlesource.com/c/net/+/750720 Reviewed-by: Nicholas Husin <husin@google.com> Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 60b3f6f commit 3eb9327

2 files changed

Lines changed: 15 additions & 37 deletions

File tree

http2/transport.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -712,10 +712,6 @@ func canRetryError(err error) bool {
712712
return true
713713
}
714714
if se, ok := err.(StreamError); ok {
715-
if se.Code == ErrCodeProtocol && se.Cause == errFromPeer {
716-
// See golang/go#47635, golang/go#42777
717-
return true
718-
}
719715
return se.Code == ErrCodeRefusedStream
720716
}
721717
return false

http2/transport_test.go

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4838,15 +4838,15 @@ func TestTransportCloseRequestBody(t *testing.T) {
48384838
}
48394839
}
48404840

4841-
func TestTransportRetriesOnStreamProtocolError(t *testing.T) {
4842-
synctestTest(t, testTransportRetriesOnStreamProtocolError)
4841+
func TestTransportNoRetryOnStreamProtocolError(t *testing.T) {
4842+
synctestTest(t, testTransportNoRetryOnStreamProtocolError)
48434843
}
4844-
func testTransportRetriesOnStreamProtocolError(t testing.TB) {
4845-
// This test verifies that
4844+
func testTransportNoRetryOnStreamProtocolError(t testing.TB) {
4845+
// This test verifies that:
4846+
// - a request that fails with ErrCodeProtocol is not retried. See
4847+
// go.dev/issue/77843.
48464848
// - receiving a protocol error on a connection does not interfere with
4847-
// other requests in flight on that connection;
4848-
// - the connection is not reused for further requests; and
4849-
// - the failed request is retried on a new connecection.
4849+
// other requests in flight on that connection.
48504850
tt := newTestTransport(t)
48514851

48524852
// Start two requests. The first is a long request
@@ -4866,44 +4866,26 @@ func testTransportRetriesOnStreamProtocolError(t testing.TB) {
48664866
tc1.writeSettings()
48674867
tc1.wantFrameType(FrameSettings) // settings ACK
48684868

4869-
// Request #2(a): The short request.
4869+
// Request #2: The short request.
48704870
req2, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
48714871
rt2 := tt.roundTrip(req2)
48724872
tc1.wantHeaders(wantHeader{
48734873
streamID: 3,
48744874
endStream: true,
48754875
})
48764876

4877-
// Request #2(a) fails with ErrCodeProtocol.
4877+
// Request #2 fails with ErrCodeProtocol.
48784878
tc1.writeRSTStream(3, ErrCodeProtocol)
48794879
if rt1.done() {
48804880
t.Fatalf("After protocol error on RoundTrip #2, RoundTrip #1 is done; want still in progress")
48814881
}
4882-
if rt2.done() {
4883-
t.Fatalf("After protocol error on RoundTrip #2, RoundTrip #2 is done; want still in progress")
4882+
if !rt2.done() {
4883+
t.Fatalf("After protocol error on RoundTrip #2, RoundTrip #2 is in progress; want done")
4884+
}
4885+
// Request #2 should not be retried.
4886+
if tt.hasConn() {
4887+
t.Fatalf("After protocol error on RoundTrip #2, RoundTrip #2 is unexpectedly retried")
48844888
}
4885-
4886-
// Request #2(b): The short request is retried on a new connection.
4887-
tc2 := tt.getConn()
4888-
tc2.wantFrameType(FrameSettings)
4889-
tc2.wantFrameType(FrameWindowUpdate)
4890-
tc2.wantHeaders(wantHeader{
4891-
streamID: 1,
4892-
endStream: true,
4893-
})
4894-
tc2.writeSettings()
4895-
tc2.wantFrameType(FrameSettings) // settings ACK
4896-
4897-
// Request #2(b) succeeds.
4898-
tc2.writeHeaders(HeadersFrameParam{
4899-
StreamID: 1,
4900-
EndHeaders: true,
4901-
EndStream: true,
4902-
BlockFragment: tc1.makeHeaderBlockFragment(
4903-
":status", "201",
4904-
),
4905-
})
4906-
rt2.wantStatus(201)
49074889

49084890
// Request #1 succeeds.
49094891
tc1.writeHeaders(HeadersFrameParam{

0 commit comments

Comments
 (0)