@@ -21,13 +21,17 @@ import (
21
21
"sort"
22
22
"sync"
23
23
"sync/atomic"
24
+ "time"
24
25
)
25
26
26
27
var (
27
28
driversMu sync.RWMutex
28
29
drivers = make (map [string ]driver.Driver )
29
30
)
30
31
32
+ // nowFunc returns the current time; it's overridden in tests.
33
+ var nowFunc = time .Now
34
+
31
35
// Register makes a database driver available by the provided name.
32
36
// If Register is called twice with the same name or if driver is nil,
33
37
// it panics.
@@ -235,12 +239,14 @@ type DB struct {
235
239
// maybeOpenNewConnections sends on the chan (one send per needed connection)
236
240
// It is closed during db.Close(). The close tells the connectionOpener
237
241
// goroutine to exit.
238
- openerCh chan struct {}
239
- closed bool
240
- dep map [finalCloser ]depSet
241
- lastPut map [* driverConn ]string // stacktrace of last conn's put; debug only
242
- maxIdle int // zero means defaultMaxIdleConns; negative means 0
243
- maxOpen int // <= 0 means unlimited
242
+ openerCh chan struct {}
243
+ closed bool
244
+ dep map [finalCloser ]depSet
245
+ lastPut map [* driverConn ]string // stacktrace of last conn's put; debug only
246
+ maxIdle int // zero means defaultMaxIdleConns; negative means 0
247
+ maxOpen int // <= 0 means unlimited
248
+ maxLifetime time.Duration // maximum amount of time a connection may be reused
249
+ cleanerCh chan struct {}
244
250
}
245
251
246
252
// connReuseStrategy determines how (*DB).conn returns database connections.
@@ -260,7 +266,8 @@ const (
260
266
// interfaces returned via that Conn, such as calls on Tx, Stmt,
261
267
// Result, Rows)
262
268
type driverConn struct {
263
- db * DB
269
+ db * DB
270
+ createdAt time.Time
264
271
265
272
sync.Mutex // guards following
266
273
ci driver.Conn
@@ -284,6 +291,13 @@ func (dc *driverConn) removeOpenStmt(si driver.Stmt) {
284
291
delete (dc .openStmt , si )
285
292
}
286
293
294
+ func (dc * driverConn ) expired (timeout time.Duration ) bool {
295
+ if timeout <= 0 {
296
+ return false
297
+ }
298
+ return dc .createdAt .Add (timeout ).Before (nowFunc ())
299
+ }
300
+
287
301
func (dc * driverConn ) prepareLocked (query string ) (driver.Stmt , error ) {
288
302
si , err := dc .ci .Prepare (query )
289
303
if err == nil {
@@ -506,6 +520,9 @@ func (db *DB) Close() error {
506
520
return nil
507
521
}
508
522
close (db .openerCh )
523
+ if db .cleanerCh != nil {
524
+ close (db .cleanerCh )
525
+ }
509
526
var err error
510
527
fns := make ([]func () error , 0 , len (db .freeConn ))
511
528
for _ , dc := range db .freeConn {
@@ -594,6 +611,84 @@ func (db *DB) SetMaxOpenConns(n int) {
594
611
}
595
612
}
596
613
614
+ // SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
615
+ //
616
+ // Expired connections may be closed lazily before reuse.
617
+ //
618
+ // If d <= 0, connections are reused forever.
619
+ func (db * DB ) SetConnMaxLifetime (d time.Duration ) {
620
+ if d < 0 {
621
+ d = 0
622
+ }
623
+ db .mu .Lock ()
624
+ // wake cleaner up when lifetime is shortened.
625
+ if d > 0 && d < db .maxLifetime && db .cleanerCh != nil {
626
+ select {
627
+ case db .cleanerCh <- struct {}{}:
628
+ default :
629
+ }
630
+ }
631
+ db .maxLifetime = d
632
+ db .startCleanerLocked ()
633
+ db .mu .Unlock ()
634
+ }
635
+
636
+ // startCleanerLocked starts connectionCleaner if needed.
637
+ func (db * DB ) startCleanerLocked () {
638
+ if db .maxLifetime > 0 && db .numOpen > 0 && db .cleanerCh == nil {
639
+ db .cleanerCh = make (chan struct {}, 1 )
640
+ go db .connectionCleaner (db .maxLifetime )
641
+ }
642
+ }
643
+
644
+ func (db * DB ) connectionCleaner (d time.Duration ) {
645
+ const minInterval = time .Second
646
+
647
+ if d < minInterval {
648
+ d = minInterval
649
+ }
650
+ t := time .NewTimer (d )
651
+
652
+ for {
653
+ select {
654
+ case <- t .C :
655
+ case <- db .cleanerCh : // maxLifetime was changed or db was closed.
656
+ }
657
+
658
+ db .mu .Lock ()
659
+ d = db .maxLifetime
660
+ if db .closed || db .numOpen == 0 || d <= 0 {
661
+ db .cleanerCh = nil
662
+ db .mu .Unlock ()
663
+ return
664
+ }
665
+
666
+ expiredSince := nowFunc ().Add (- d )
667
+ var closing []* driverConn
668
+ for i := 0 ; i < len (db .freeConn ); i ++ {
669
+ c := db .freeConn [i ]
670
+ if c .createdAt .Before (expiredSince ) {
671
+ closing = append (closing , c )
672
+ last := len (db .freeConn ) - 1
673
+ db .freeConn [i ] = db .freeConn [last ]
674
+ db .freeConn [last ] = nil
675
+ db .freeConn = db .freeConn [:last ]
676
+ i --
677
+ }
678
+ }
679
+ db .mu .Unlock ()
680
+
681
+ for _ , c := range closing {
682
+ c .Close ()
683
+ }
684
+
685
+ if d < minInterval {
686
+ d = minInterval
687
+ }
688
+ t .Reset (d )
689
+ }
690
+ }
691
+
597
692
// DBStats contains database statistics.
598
693
type DBStats struct {
599
694
// OpenConnections is the number of open connections to the database.
@@ -657,8 +752,9 @@ func (db *DB) openNewConnection() {
657
752
return
658
753
}
659
754
dc := & driverConn {
660
- db : db ,
661
- ci : ci ,
755
+ db : db ,
756
+ createdAt : nowFunc (),
757
+ ci : ci ,
662
758
}
663
759
if db .putConnDBLocked (dc , err ) {
664
760
db .addDepLocked (dc , dc )
@@ -685,6 +781,7 @@ func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) {
685
781
db .mu .Unlock ()
686
782
return nil , errDBClosed
687
783
}
784
+ lifetime := db .maxLifetime
688
785
689
786
// Prefer a free connection, if possible.
690
787
numFree := len (db .freeConn )
@@ -694,6 +791,10 @@ func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) {
694
791
db .freeConn = db .freeConn [:numFree - 1 ]
695
792
conn .inUse = true
696
793
db .mu .Unlock ()
794
+ if conn .expired (lifetime ) {
795
+ conn .Close ()
796
+ return nil , driver .ErrBadConn
797
+ }
697
798
return conn , nil
698
799
}
699
800
@@ -709,6 +810,10 @@ func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) {
709
810
if ! ok {
710
811
return nil , errDBClosed
711
812
}
813
+ if ret .err == nil && ret .conn .expired (lifetime ) {
814
+ ret .conn .Close ()
815
+ return nil , driver .ErrBadConn
816
+ }
712
817
return ret .conn , ret .err
713
818
}
714
819
@@ -724,8 +829,9 @@ func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) {
724
829
}
725
830
db .mu .Lock ()
726
831
dc := & driverConn {
727
- db : db ,
728
- ci : ci ,
832
+ db : db ,
833
+ createdAt : nowFunc (),
834
+ ci : ci ,
729
835
}
730
836
db .addDepLocked (dc , dc )
731
837
dc .inUse = true
@@ -835,6 +941,7 @@ func (db *DB) putConnDBLocked(dc *driverConn, err error) bool {
835
941
return true
836
942
} else if err == nil && ! db .closed && db .maxIdleConnsLocked () > len (db .freeConn ) {
837
943
db .freeConn = append (db .freeConn , dc )
944
+ db .startCleanerLocked ()
838
945
return true
839
946
}
840
947
return false
0 commit comments