@@ -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 ,
@@ -987,14 +992,22 @@ func (t *Transport) tryPutIdleConn(pconn *persistConn) error {
987
992
t .removeIdleConnLocked (oldest )
988
993
}
989
994
995
+ ttl , hasTtl := pconn .timeToLive ()
996
+
990
997
// Set idle timer, but only for HTTP/1 (pconn.alt == nil).
991
998
// The HTTP/2 implementation manages the idle timer itself
992
999
// (see idleConnTimeout in h2_bundle.go).
993
- if t .IdleConnTimeout > 0 && pconn .alt == nil {
1000
+ if (hasTtl || t .IdleConnTimeout > 0 ) && pconn .alt == nil {
1001
+
1002
+ timeout := t .IdleConnTimeout
1003
+ if hasTtl && (timeout <= 0 || ttl < timeout ) {
1004
+ timeout = ttl
1005
+ }
1006
+
994
1007
if pconn .idleTimer != nil {
995
- pconn .idleTimer .Reset (t . IdleConnTimeout )
1008
+ pconn .idleTimer .Reset (timeout )
996
1009
} else {
997
- pconn .idleTimer = time .AfterFunc (t . IdleConnTimeout , pconn .closeConnIfStillIdle )
1010
+ pconn .idleTimer = time .AfterFunc (timeout , pconn .closeConnIfStillIdle )
998
1011
}
999
1012
}
1000
1013
pconn .idleAt = time .Now ()
@@ -1024,9 +1037,10 @@ func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
1024
1037
// If IdleConnTimeout is set, calculate the oldest
1025
1038
// persistConn.idleAt time we're willing to use a cached idle
1026
1039
// conn.
1040
+ now := time .Now ()
1027
1041
var oldTime time.Time
1028
1042
if t .IdleConnTimeout > 0 {
1029
- oldTime = time . Now () .Add (- t .IdleConnTimeout )
1043
+ oldTime = now .Add (- t .IdleConnTimeout )
1030
1044
}
1031
1045
1032
1046
// Look for most recently-used idle connection.
@@ -1039,7 +1053,8 @@ func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
1039
1053
// See whether this connection has been idle too long, considering
1040
1054
// only the wall time (the Round(0)), in case this is a laptop or VM
1041
1055
// coming out of suspend with previously cached idle connections.
1042
- tooOld := ! oldTime .IsZero () && pconn .idleAt .Round (0 ).Before (oldTime )
1056
+ tooOld := (! oldTime .IsZero () && pconn .idleAt .Round (0 ).Before (oldTime )) || (! pconn .reuseDeadline .IsZero () && pconn .reuseDeadline .Round (0 ).Before (now ))
1057
+
1043
1058
if tooOld {
1044
1059
// Async cleanup. Launch in its own goroutine (as if a
1045
1060
// time.AfterFunc called it); it acquires idleMu, which we're
@@ -1620,6 +1635,11 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *pers
1620
1635
}
1621
1636
}
1622
1637
1638
+ var reuseDeadline time.Time
1639
+ if t .MaxConnLifespan > 0 {
1640
+ reuseDeadline = time .Now ().Add (t .MaxConnLifespan )
1641
+ }
1642
+
1623
1643
// Proxy setup.
1624
1644
switch {
1625
1645
case cm .proxyURL == nil :
@@ -1740,10 +1760,11 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *pers
1740
1760
// pconn.conn was closed by next (http2configureTransports.upgradeFn).
1741
1761
return nil , e .RoundTripErr ()
1742
1762
}
1743
- return & persistConn {t : t , cacheKey : pconn .cacheKey , alt : alt }, nil
1763
+ return & persistConn {t : t , cacheKey : pconn .cacheKey , alt : alt , reuseDeadline : reuseDeadline }, nil
1744
1764
}
1745
1765
}
1746
1766
1767
+ pconn .reuseDeadline = reuseDeadline
1747
1768
pconn .br = bufio .NewReaderSize (pconn , t .readBufferSize ())
1748
1769
pconn .bw = bufio .NewWriterSize (persistConnWriter {pconn }, t .writeBufferSize ())
1749
1770
@@ -1895,6 +1916,8 @@ type persistConn struct {
1895
1916
1896
1917
writeLoopDone chan struct {} // closed when write loop ends
1897
1918
1919
+ reuseDeadline time.Time // time when this connection can no longer be reused
1920
+
1898
1921
// Both guarded by Transport.idleMu:
1899
1922
idleAt time.Time // time it last become idle
1900
1923
idleTimer * time.Timer // holding an AfterFunc to close it
@@ -1911,6 +1934,30 @@ type persistConn struct {
1911
1934
mutateHeaderFunc func (Header )
1912
1935
}
1913
1936
1937
+ // timeToLive checks if a persistent connection has been initialized
1938
+ // from a transport with MaxConnLifespan > 0 and returns the time
1939
+ // remaining for this connection to be reusable. The second response
1940
+ // would be true in this case.
1941
+ //
1942
+ // If the connection has a zero-value reuseDeadline set then
1943
+ // it returns (0, false)
1944
+ //
1945
+ // The returned duration will never be less than zero and the connection's
1946
+ // idle time is NOT taken into account.
1947
+ func (pc * persistConn ) timeToLive () (time.Duration , bool ) {
1948
+
1949
+ if pc .reuseDeadline .IsZero () {
1950
+ return 0 , false
1951
+ }
1952
+
1953
+ ttl := time .Until (pc .reuseDeadline )
1954
+ if ttl < 0 {
1955
+ return 0 , true
1956
+ }
1957
+
1958
+ return ttl , true
1959
+ }
1960
+
1914
1961
func (pc * persistConn ) maxHeaderResponseSize () int64 {
1915
1962
if v := pc .t .MaxResponseHeaderBytes ; v != 0 {
1916
1963
return v
0 commit comments