@@ -191,6 +191,7 @@ type clientStream struct {
191
191
ID uint32
192
192
resc chan resAndError
193
193
bufPipe pipe // buffered pipe with the flow-controlled response payload
194
+ startedWrite bool // started request body write; guarded by cc.mu
194
195
requestedGzip bool
195
196
on100 func () // optional code to run if get a 100 continue response
196
197
@@ -332,8 +333,10 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
332
333
}
333
334
traceGotConn (req , cc )
334
335
res , err := cc .RoundTrip (req )
335
- if shouldRetryRequest (req , err ) {
336
- continue
336
+ if err != nil {
337
+ if req , err = shouldRetryRequest (req , err ); err == nil {
338
+ continue
339
+ }
337
340
}
338
341
if err != nil {
339
342
t .vlogf ("RoundTrip failure: %v" , err )
@@ -355,12 +358,41 @@ func (t *Transport) CloseIdleConnections() {
355
358
var (
356
359
errClientConnClosed = errors .New ("http2: client conn is closed" )
357
360
errClientConnUnusable = errors .New ("http2: client conn not usable" )
361
+
362
+ errClientConnGotGoAway = errors .New ("http2: Transport received Server's graceful shutdown GOAWAY" )
363
+ errClientConnGotGoAwayAfterSomeReqBody = errors .New ("http2: Transport received Server's graceful shutdown GOAWAY; some request body already written" )
358
364
)
359
365
360
- func shouldRetryRequest (req * http.Request , err error ) bool {
361
- // TODO: retry GET requests (no bodies) more aggressively, if shutdown
362
- // before response.
363
- return err == errClientConnUnusable
366
+ // shouldRetryRequest is called by RoundTrip when a request fails to get
367
+ // response headers. It is always called with a non-nil error.
368
+ // It returns either a request to retry (either the same request, or a
369
+ // modified clone), or an error if the request can't be replayed.
370
+ func shouldRetryRequest (req * http.Request , err error ) (* http.Request , error ) {
371
+ switch err {
372
+ default :
373
+ return nil , err
374
+ case errClientConnUnusable , errClientConnGotGoAway :
375
+ return req , nil
376
+ case errClientConnGotGoAwayAfterSomeReqBody :
377
+ // If the Body is nil (or http.NoBody), it's safe to reuse
378
+ // this request and its Body.
379
+ if req .Body == nil || reqBodyIsNoBody (req .Body ) {
380
+ return req , nil
381
+ }
382
+ // Otherwise we depend on the Request having its GetBody
383
+ // func defined.
384
+ getBody := reqGetBody (req ) // Go 1.8: getBody = req.GetBody
385
+ if getBody == nil {
386
+ return nil , errors .New ("http2: Transport: peer server initiated graceful shutdown after some of Request.Body was written; define Request.GetBody to avoid this error" )
387
+ }
388
+ body , err := getBody ()
389
+ if err != nil {
390
+ return nil , err
391
+ }
392
+ newReq := * req
393
+ newReq .Body = body
394
+ return & newReq , nil
395
+ }
364
396
}
365
397
366
398
func (t * Transport ) dialClientConn (addr string , singleUse bool ) (* ClientConn , error ) {
@@ -513,6 +545,15 @@ func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
513
545
if old != nil && old .ErrCode != ErrCodeNo {
514
546
cc .goAway .ErrCode = old .ErrCode
515
547
}
548
+ last := f .LastStreamID
549
+ for streamID , cs := range cc .streams {
550
+ if streamID > last {
551
+ select {
552
+ case cs .resc <- resAndError {err : errClientConnGotGoAway }:
553
+ default :
554
+ }
555
+ }
556
+ }
516
557
}
517
558
518
559
func (cc * ClientConn ) CanTakeNewRequest () bool {
@@ -773,6 +814,13 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
773
814
cs .abortRequestBodyWrite (errStopReqBodyWrite )
774
815
}
775
816
if re .err != nil {
817
+ if re .err == errClientConnGotGoAway {
818
+ cc .mu .Lock ()
819
+ if cs .startedWrite {
820
+ re .err = errClientConnGotGoAwayAfterSomeReqBody
821
+ }
822
+ cc .mu .Unlock ()
823
+ }
776
824
cc .forgetStreamID (cs .ID )
777
825
return nil , re .err
778
826
}
@@ -2013,6 +2061,9 @@ func (t *Transport) getBodyWriterState(cs *clientStream, body io.Reader) (s body
2013
2061
resc := make (chan error , 1 )
2014
2062
s .resc = resc
2015
2063
s .fn = func () {
2064
+ cs .cc .mu .Lock ()
2065
+ cs .startedWrite = true
2066
+ cs .cc .mu .Unlock ()
2016
2067
resc <- cs .writeRequestBody (body , cs .req .Body )
2017
2068
}
2018
2069
s .delay = t .expectContinueTimeout ()
0 commit comments