@@ -14,6 +14,7 @@ import (
14
14
"crypto/rand"
15
15
"crypto/tls"
16
16
"errors"
17
+ "expvar"
17
18
"fmt"
18
19
"io"
19
20
"io/ioutil"
@@ -36,6 +37,8 @@ import (
36
37
"github.com/tailscale/net/idna"
37
38
)
38
39
40
+ var http2ClientGotProtoError = expvar .NewInt ("http2client_got_protocol_error" )
41
+
39
42
const (
40
43
// transportDefaultConnFlow is how many connection-level flow control
41
44
// tokens we give the server at start-up, past the default 64k.
@@ -244,6 +247,7 @@ type ClientConn struct {
244
247
cond * sync.Cond // hold mu; broadcast on flow/closed changes
245
248
flow flow // our conn-level flow control quota (cs.flow is per stream)
246
249
inflow flow // peer's conn-level flow control
250
+ doNotReuse bool
247
251
closing bool
248
252
closed bool
249
253
wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back
@@ -558,6 +562,10 @@ func canRetryError(err error) bool {
558
562
return true
559
563
}
560
564
if se , ok := err .(StreamError ); ok {
565
+ if se .Code == ErrCodeProtocol && se .Cause == errFromPeer {
566
+ // See golang/go#47635, golang/go#42777
567
+ return true
568
+ }
561
569
return se .Code == ErrCodeRefusedStream
562
570
}
563
571
return false
@@ -709,6 +717,13 @@ func (cc *ClientConn) healthCheck() {
709
717
}
710
718
}
711
719
720
+ // SetDoNotReuse marks cc as not reusable for future HTTP requests.
721
+ func (cc * ClientConn ) SetDoNotReuse () {
722
+ cc .mu .Lock ()
723
+ defer cc .mu .Unlock ()
724
+ cc .doNotReuse = true
725
+ }
726
+
712
727
func (cc * ClientConn ) setGoAway (f * GoAwayFrame ) {
713
728
cc .mu .Lock ()
714
729
defer cc .mu .Unlock ()
@@ -771,6 +786,7 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
771
786
}
772
787
773
788
st .canTakeNewRequest = cc .goAway == nil && ! cc .closed && ! cc .closing && maxConcurrentOkay &&
789
+ ! cc .doNotReuse &&
774
790
int64 (cc .nextStreamID )+ 2 * int64 (cc .pendingRequests ) < math .MaxInt32 &&
775
791
! cc .tooIdleLocked ()
776
792
st .freshConn = cc .nextStreamID == 1 && st .canTakeNewRequest
@@ -2420,10 +2436,15 @@ func (rl *clientConnReadLoop) processResetStream(f *RSTStreamFrame) error {
2420
2436
// which closes this, so there
2421
2437
// isn't a race.
2422
2438
default :
2423
- err := streamError (cs .ID , f .ErrCode )
2424
- cs .resetErr = err
2439
+ serr := streamError (cs .ID , f .ErrCode )
2440
+ if f .ErrCode == ErrCodeProtocol {
2441
+ rl .cc .SetDoNotReuse ()
2442
+ http2ClientGotProtoError .Add (1 )
2443
+ serr .Cause = errFromPeer
2444
+ }
2445
+ cs .resetErr = serr
2425
2446
close (cs .peerReset )
2426
- cs .bufPipe .CloseWithError (err )
2447
+ cs .bufPipe .CloseWithError (serr )
2427
2448
cs .cc .cond .Broadcast () // wake up checkResetOrDone via clientStream.awaitFlowControl
2428
2449
}
2429
2450
return nil
0 commit comments