@@ -189,6 +189,10 @@ type Transport struct {
189
189
// uncompressed.
190
190
DisableCompression bool
191
191
192
+ // MaxConnLifespan controls how long a connection is allowed
193
+ // to be reused before it must be closed. Zero means no limit.
194
+ MaxConnLifespan time.Duration
195
+
192
196
// MaxIdleConns controls the maximum number of idle (keep-alive)
193
197
// connections across all hosts. Zero means no limit.
194
198
MaxIdleConns int
@@ -316,6 +320,7 @@ func (t *Transport) Clone() *Transport {
316
320
TLSHandshakeTimeout : t .TLSHandshakeTimeout ,
317
321
DisableKeepAlives : t .DisableKeepAlives ,
318
322
DisableCompression : t .DisableCompression ,
323
+ MaxConnLifespan : t .MaxConnLifespan ,
319
324
MaxIdleConns : t .MaxIdleConns ,
320
325
MaxIdleConnsPerHost : t .MaxIdleConnsPerHost ,
321
326
MaxConnsPerHost : t .MaxConnsPerHost ,
@@ -984,14 +989,22 @@ func (t *Transport) tryPutIdleConn(pconn *persistConn) error {
984
989
t .removeIdleConnLocked (oldest )
985
990
}
986
991
992
+ ttl , hasTtl := pconn .timeToLive ()
993
+
987
994
// Set idle timer, but only for HTTP/1 (pconn.alt == nil).
988
995
// The HTTP/2 implementation manages the idle timer itself
989
996
// (see idleConnTimeout in h2_bundle.go).
990
- if t .IdleConnTimeout > 0 && pconn .alt == nil {
997
+ if (hasTtl || t .IdleConnTimeout > 0 ) && pconn .alt == nil {
998
+
999
+ timeout := t .IdleConnTimeout
1000
+ if hasTtl && (timeout <= 0 || ttl < timeout ) {
1001
+ timeout = ttl
1002
+ }
1003
+
991
1004
if pconn .idleTimer != nil {
992
- pconn .idleTimer .Reset (t . IdleConnTimeout )
1005
+ pconn .idleTimer .Reset (timeout )
993
1006
} else {
994
- pconn .idleTimer = time .AfterFunc (t . IdleConnTimeout , pconn .closeConnIfStillIdle )
1007
+ pconn .idleTimer = time .AfterFunc (timeout , pconn .closeConnIfStillIdle )
995
1008
}
996
1009
}
997
1010
pconn .idleAt = time .Now ()
@@ -1021,9 +1034,10 @@ func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
1021
1034
// If IdleConnTimeout is set, calculate the oldest
1022
1035
// persistConn.idleAt time we're willing to use a cached idle
1023
1036
// conn.
1037
+ now := time .Now ()
1024
1038
var oldTime time.Time
1025
1039
if t .IdleConnTimeout > 0 {
1026
- oldTime = time . Now () .Add (- t .IdleConnTimeout )
1040
+ oldTime = now .Add (- t .IdleConnTimeout )
1027
1041
}
1028
1042
1029
1043
// Look for most recently-used idle connection.
@@ -1036,7 +1050,8 @@ func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
1036
1050
// See whether this connection has been idle too long, considering
1037
1051
// only the wall time (the Round(0)), in case this is a laptop or VM
1038
1052
// coming out of suspend with previously cached idle connections.
1039
- tooOld := ! oldTime .IsZero () && pconn .idleAt .Round (0 ).Before (oldTime )
1053
+ tooOld := (! oldTime .IsZero () && pconn .idleAt .Round (0 ).Before (oldTime )) || (! pconn .reuseDeadline .IsZero () && pconn .reuseDeadline .Round (0 ).Before (now ))
1054
+
1040
1055
if tooOld {
1041
1056
// Async cleanup. Launch in its own goroutine (as if a
1042
1057
// time.AfterFunc called it); it acquires idleMu, which we're
@@ -1617,6 +1632,11 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *pers
1617
1632
}
1618
1633
}
1619
1634
1635
+ var reuseDeadline time.Time
1636
+ if t .MaxConnLifespan > 0 {
1637
+ reuseDeadline = time .Now ().Add (t .MaxConnLifespan )
1638
+ }
1639
+
1620
1640
// Proxy setup.
1621
1641
switch {
1622
1642
case cm .proxyURL == nil :
@@ -1737,10 +1757,11 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *pers
1737
1757
// pconn.conn was closed by next (http2configureTransports.upgradeFn).
1738
1758
return nil , e .RoundTripErr ()
1739
1759
}
1740
- return & persistConn {t : t , cacheKey : pconn .cacheKey , alt : alt }, nil
1760
+ return & persistConn {t : t , cacheKey : pconn .cacheKey , alt : alt , reuseDeadline : reuseDeadline }, nil
1741
1761
}
1742
1762
}
1743
1763
1764
+ pconn .reuseDeadline = reuseDeadline
1744
1765
pconn .br = bufio .NewReaderSize (pconn , t .readBufferSize ())
1745
1766
pconn .bw = bufio .NewWriterSize (persistConnWriter {pconn }, t .writeBufferSize ())
1746
1767
@@ -1893,6 +1914,8 @@ type persistConn struct {
1893
1914
1894
1915
writeLoopDone chan struct {} // closed when write loop ends
1895
1916
1917
+ reuseDeadline time.Time // time when this connection can no longer be reused
1918
+
1896
1919
// Both guarded by Transport.idleMu:
1897
1920
idleAt time.Time // time it last become idle
1898
1921
idleTimer * time.Timer // holding an AfterFunc to close it
@@ -1909,6 +1932,30 @@ type persistConn struct {
1909
1932
mutateHeaderFunc func (Header )
1910
1933
}
1911
1934
1935
+ // timeToLive checks if a persistent connection has been initialized
1936
+ // from a transport with MaxConnLifespan > 0 and returns the time
1937
+ // remaining for this connection to be reusable. The second response
1938
+ // would be true in this case.
1939
+ //
1940
+ // If the connection has a zero-value reuseDeadline set then
1941
+ // it returns (0, false)
1942
+ //
1943
+ // The returned duration will never be less than zero and the connection's
1944
+ // idle time is NOT taken into account.
1945
+ func (pc * persistConn ) timeToLive () (time.Duration , bool ) {
1946
+
1947
+ if pc .reuseDeadline .IsZero () {
1948
+ return 0 , false
1949
+ }
1950
+
1951
+ ttl := time .Until (pc .reuseDeadline )
1952
+ if ttl < 0 {
1953
+ return 0 , true
1954
+ }
1955
+
1956
+ return ttl , true
1957
+ }
1958
+
1912
1959
func (pc * persistConn ) maxHeaderResponseSize () int64 {
1913
1960
if v := pc .t .MaxResponseHeaderBytes ; v != 0 {
1914
1961
return v
0 commit comments