@@ -94,6 +94,10 @@ type Transport struct {
94
94
// to mean no limit.
95
95
MaxHeaderListSize uint32
96
96
97
+ // IOTimeout, if non-zero, enables timeouts on read and write to the
98
+ // connection.
99
+ IOTimeout time.Duration
100
+
97
101
// t1, if non-nil, is the standard library Transport using
98
102
// this transport. Its settings are used (but not its
99
103
// RoundTrip method, etc).
@@ -321,7 +325,9 @@ func (noCachedConnError) Error() string { return "http2: no cached c
321
325
// or its equivalent renamed type in net/http2's h2_bundle.go. Both types
322
326
// may coexist in the same running program.
323
327
func isNoCachedConnError (err error ) bool {
324
- _ , ok := err .(interface { IsHTTP2NoCachedConnError () })
328
+ _ , ok := err .(interface {
329
+ IsHTTP2NoCachedConnError ()
330
+ })
325
331
return ok
326
332
}
327
333
@@ -529,6 +535,38 @@ func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
529
535
return t .newClientConn (c , false )
530
536
}
531
537
538
+ type timeoutWriter struct {
539
+ c net.Conn
540
+ timeout time.Duration
541
+ }
542
+
543
+ // Write writes to the underlying connection and manages both read and write
544
+ // deadlines.
545
+ func (w timeoutWriter ) Write (p []byte ) (int , error ) {
546
+ // The read deadline is disabled to allow so the reader doesn't timeout
547
+ // while there are no pending requests.
548
+ // The write deadline is set.
549
+ // The write occurs and the connection is closed on timeout interrupting the
550
+ // read with an error.
551
+ // If the write was successful it sets the deadline for the current read.
552
+ now := time .Now ()
553
+ w .c .SetReadDeadline (time.Time {})
554
+ w .c .SetWriteDeadline (now .Add (w .timeout ))
555
+ n , err := w .c .Write (p )
556
+ if err != nil {
557
+ if ne , ok := err .(net.Error ); ok && ne .Timeout () {
558
+ // The write end of the connection is no longer in a known
559
+ // consistent state, unlock the read loop by closing the connection
560
+ // and force a cleanup.
561
+ w .c .Close ()
562
+ return n , err
563
+ }
564
+ }
565
+ w .c .SetWriteDeadline (time.Time {})
566
+ w .c .SetReadDeadline (time .Now ().Add (w .timeout ))
567
+ return n , err
568
+ }
569
+
532
570
func (t * Transport ) newClientConn (c net.Conn , singleUse bool ) (* ClientConn , error ) {
533
571
cc := & ClientConn {
534
572
t : t ,
@@ -555,9 +593,14 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
555
593
cc .cond = sync .NewCond (& cc .mu )
556
594
cc .flow .add (int32 (initialWindowSize ))
557
595
596
+ var w io.Writer = cc .tconn
597
+ if d := cc .ioTimeout (); d > 0 {
598
+ w = timeoutWriter {c : cc .tconn , timeout : d }
599
+ }
600
+
558
601
// TODO: adjust this writer size to account for frame size +
559
602
// MTU + crypto/tls record padding.
560
- cc .bw = bufio .NewWriter (stickyErrWriter {c , & cc .werr })
603
+ cc .bw = bufio .NewWriter (stickyErrWriter {w , & cc .werr })
561
604
cc .br = bufio .NewReader (c )
562
605
cc .fr = NewFramer (cc .bw , cc .br )
563
606
cc .fr .ReadMetaHeaders = hpack .NewDecoder (initialHeaderTableSize , nil )
@@ -733,6 +776,10 @@ func (cc *ClientConn) responseHeaderTimeout() time.Duration {
733
776
return 0
734
777
}
735
778
779
+ func (cc * ClientConn ) ioTimeout () time.Duration {
780
+ return cc .t .IOTimeout
781
+ }
782
+
736
783
// checkConnHeaders checks whether req has any invalid connection-level headers.
737
784
// per RFC 7540 section 8.1.2.2: Connection-Specific Header Fields.
738
785
// Certain headers are special-cased as okay but not transmitted later.
@@ -1473,12 +1520,23 @@ func (rl *clientConnReadLoop) run() error {
1473
1520
rl .closeWhenIdle = cc .t .disableKeepAlives () || cc .singleUse
1474
1521
gotReply := false // ever saw a HEADERS reply
1475
1522
gotSettings := false
1523
+ timeout := cc .ioTimeout ()
1476
1524
for {
1477
1525
f , err := cc .fr .ReadFrame ()
1478
1526
if err != nil {
1479
1527
cc .vlogf ("http2: Transport readFrame error on conn %p: (%T) %v" , cc , err , err )
1480
1528
}
1481
- if se , ok := err .(StreamError ); ok {
1529
+ if ne , ok := err .(net.Error ); ok && ne .Timeout () {
1530
+ cc .mu .Lock ()
1531
+ idle := len (cc .streams ) == 0
1532
+ cc .mu .Unlock ()
1533
+ if idle {
1534
+ // let the idle timer handle the timeout.
1535
+ cc .tconn .SetReadDeadline (time.Time {})
1536
+ continue
1537
+ }
1538
+ return err
1539
+ } else if se , ok := err .(StreamError ); ok {
1482
1540
if cs := cc .streamByID (se .StreamID , false ); cs != nil {
1483
1541
cs .cc .writeStreamReset (cs .ID , se .Code , err )
1484
1542
cs .cc .forgetStreamID (cs .ID )
@@ -1537,6 +1595,10 @@ func (rl *clientConnReadLoop) run() error {
1537
1595
if rl .closeWhenIdle && gotReply && maybeIdle {
1538
1596
cc .closeIfIdle ()
1539
1597
}
1598
+ if timeout > 0 {
1599
+ // Upon successful read, set timeout on next read.
1600
+ cc .tconn .SetReadDeadline (time .Now ().Add (timeout ))
1601
+ }
1540
1602
}
1541
1603
}
1542
1604
0 commit comments