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