closed bool
dep map[finalCloser]depSet
lastPut map[*driverConn]string // stacktrace of last conn's put; debug only
- maxIdle int // zero means defaultMaxIdleConns; negative means 0
+ maxIdleCount int // zero means defaultMaxIdleConns; negative means 0
maxOpen int // <= 0 means unlimited
maxLifetime time.Duration // maximum amount of time a connection may be reused
+ maxIdleTime time.Duration // maximum amount of time a connection may be idle before being closed
cleanerCh chan struct{}
waitCount int64 // Total number of connections waited for.
- maxIdleClosed int64 // Total number of connections closed due to idle.
+ maxIdleClosed int64 // Total number of connections closed due to idle count.
+ maxIdleTimeClosed int64 // Total number of connections closed due to idle time.
maxLifetimeClosed int64 // Total number of connections closed due to max free limit.
stop func() // stop cancels the connection opener and the session resetter.
// guarded by db.mu
inUse bool
- onPut []func() // code (with db.mu held) run when conn is next returned
- dbmuClosed bool // same as closed, but guarded by db.mu, for removeClosedStmtLocked
+ returnedAt time.Time // Time the connection was created or returned.
+ onPut []func() // code (with db.mu held) run when conn is next returned
+ dbmuClosed bool // same as closed, but guarded by db.mu, for removeClosedStmtLocked
}
func (dc *driverConn) releaseConn(err error) {
const defaultMaxIdleConns = 2
func (db *DB) maxIdleConnsLocked() int {
- n := db.maxIdle
+ n := db.maxIdleCount
switch {
case n == 0:
// TODO(bradfitz): ask driver, if supported, for its default preference
}
}
+func (db *DB) shortestIdleTimeLocked() time.Duration {
+ min := db.maxIdleTime
+ if min > db.maxLifetime {
+ min = db.maxLifetime
+ }
+ return min
+}
+
// SetMaxIdleConns sets the maximum number of connections in the idle
// connection pool.
//
func (db *DB) SetMaxIdleConns(n int) {
db.mu.Lock()
if n > 0 {
- db.maxIdle = n
+ db.maxIdleCount = n
} else {
// No idle connections.
- db.maxIdle = -1
+ db.maxIdleCount = -1
}
// Make sure maxIdle doesn't exceed maxOpen
if db.maxOpen > 0 && db.maxIdleConnsLocked() > db.maxOpen {
- db.maxIdle = db.maxOpen
+ db.maxIdleCount = db.maxOpen
}
var closing []*driverConn
idleCount := len(db.freeConn)
//
// Expired connections may be closed lazily before reuse.
//
-// If d <= 0, connections are reused forever.
+// If d <= 0, connections are not closed due to a connection's age.
func (db *DB) SetConnMaxLifetime(d time.Duration) {
if d < 0 {
d = 0
}
db.mu.Lock()
- // wake cleaner up when lifetime is shortened.
+ // Wake cleaner up when lifetime is shortened.
if d > 0 && d < db.maxLifetime && db.cleanerCh != nil {
select {
case db.cleanerCh <- struct{}{}:
db.mu.Unlock()
}
+// SetConnMaxIdleTime sets the maximum amount of time a connection may be idle.
+//
+// Expired connections may be closed lazily before reuse.
+//
+// If d <= 0, connections are not closed due to a connection's idle time.
+func (db *DB) SetConnMaxIdleTime(d time.Duration) {
+ if d < 0 {
+ d = 0
+ }
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ // Wake cleaner up when idle time is shortened.
+ if d > 0 && d < db.maxIdleTime && db.cleanerCh != nil {
+ select {
+ case db.cleanerCh <- struct{}{}:
+ default:
+ }
+ }
+ db.maxIdleTime = d
+ db.startCleanerLocked()
+}
+
// startCleanerLocked starts connectionCleaner if needed.
func (db *DB) startCleanerLocked() {
- if db.maxLifetime > 0 && db.numOpen > 0 && db.cleanerCh == nil {
+ if (db.maxLifetime > 0 || db.maxIdleTime > 0) && db.numOpen > 0 && db.cleanerCh == nil {
db.cleanerCh = make(chan struct{}, 1)
- go db.connectionCleaner(db.maxLifetime)
+ go db.connectionCleaner(db.shortestIdleTimeLocked())
}
}
}
db.mu.Lock()
- d = db.maxLifetime
+
+ d = db.shortestIdleTimeLocked()
if db.closed || db.numOpen == 0 || d <= 0 {
db.cleanerCh = nil
db.mu.Unlock()
return
}
- expiredSince := nowFunc().Add(-d)
- var closing []*driverConn
+ closing := db.connectionCleanerRunLocked()
+ db.mu.Unlock()
+ for _, c := range closing {
+ c.Close()
+ }
+
+ if d < minInterval {
+ d = minInterval
+ }
+ t.Reset(d)
+ }
+}
+
+func (db *DB) connectionCleanerRunLocked() (closing []*driverConn) {
+ if db.maxLifetime > 0 {
+ expiredSince := nowFunc().Add(-db.maxLifetime)
for i := 0; i < len(db.freeConn); i++ {
c := db.freeConn[i]
if c.createdAt.Before(expiredSince) {
}
}
db.maxLifetimeClosed += int64(len(closing))
- db.mu.Unlock()
-
- for _, c := range closing {
- c.Close()
- }
+ }
- if d < minInterval {
- d = minInterval
+ if db.maxIdleTime > 0 {
+ expiredSince := nowFunc().Add(-db.maxIdleTime)
+ var expiredCount int64
+ for i := 0; i < len(db.freeConn); i++ {
+ c := db.freeConn[i]
+ if db.maxIdleTime > 0 && c.returnedAt.Before(expiredSince) {
+ closing = append(closing, c)
+ expiredCount++
+ last := len(db.freeConn) - 1
+ db.freeConn[i] = db.freeConn[last]
+ db.freeConn[last] = nil
+ db.freeConn = db.freeConn[:last]
+ i--
+ }
}
- t.Reset(d)
+ db.maxIdleTimeClosed += expiredCount
}
+ return
}
// DBStats contains database statistics.
WaitCount int64 // The total number of connections waited for.
WaitDuration time.Duration // The total time blocked waiting for a new connection.
MaxIdleClosed int64 // The total number of connections closed due to SetMaxIdleConns.
+ MaxIdleTimeClosed int64 // The total number of connections closed due to SetConnMaxIdleTime.
MaxLifetimeClosed int64 // The total number of connections closed due to SetConnMaxLifetime.
}
WaitCount: db.waitCount,
WaitDuration: time.Duration(wait),
MaxIdleClosed: db.maxIdleClosed,
+ MaxIdleTimeClosed: db.maxIdleTimeClosed,
MaxLifetimeClosed: db.maxLifetimeClosed,
}
return stats
return
}
dc := &driverConn{
- db: db,
- createdAt: nowFunc(),
- ci: ci,
+ db: db,
+ createdAt: nowFunc(),
+ returnedAt: nowFunc(),
+ ci: ci,
}
if db.putConnDBLocked(dc, err) {
db.addDepLocked(dc, dc)
db.waitCount++
db.mu.Unlock()
- waitStart := time.Now()
+ waitStart := nowFunc()
// Timeout the connection request with the context.
select {
}
db.mu.Lock()
dc := &driverConn{
- db: db,
- createdAt: nowFunc(),
- ci: ci,
- inUse: true,
+ db: db,
+ createdAt: nowFunc(),
+ returnedAt: nowFunc(),
+ ci: ci,
+ inUse: true,
}
db.addDepLocked(dc, dc)
db.mu.Unlock()
db.lastPut[dc] = stack()
}
dc.inUse = false
+ dc.returnedAt = nowFunc()
for _, fn := range dc.onPut {
fn()
}
}
+func TestMaxIdleTime(t *testing.T) {
+ list := []struct {
+ wantMaxIdleTime time.Duration
+ wantIdleClosed int64
+ timeOffset time.Duration
+ }{
+ {time.Nanosecond, 1, 10 * time.Millisecond},
+ {time.Hour, 0, 10 * time.Millisecond},
+ }
+ baseTime := time.Unix(0, 0)
+ defer func() {
+ nowFunc = time.Now
+ }()
+ for _, item := range list {
+ nowFunc = func() time.Time {
+ return baseTime
+ }
+ t.Run(fmt.Sprintf("%v", item.wantMaxIdleTime), func(t *testing.T) {
+ db := newTestDB(t, "people")
+ defer closeDB(t, db)
+
+ db.SetMaxOpenConns(1)
+ db.SetMaxIdleConns(1)
+ db.SetConnMaxIdleTime(item.wantMaxIdleTime)
+ db.SetConnMaxLifetime(0)
+
+ preMaxIdleClosed := db.Stats().MaxIdleTimeClosed
+
+ if err := db.Ping(); err != nil {
+ t.Fatal(err)
+ }
+
+ nowFunc = func() time.Time {
+ return baseTime.Add(item.timeOffset)
+ }
+
+ db.mu.Lock()
+ closing := db.connectionCleanerRunLocked()
+ db.mu.Unlock()
+ for _, c := range closing {
+ c.Close()
+ }
+ if g, w := int64(len(closing)), item.wantIdleClosed; g != w {
+ t.Errorf("got: %d; want %d closed conns", g, w)
+ }
+
+ st := db.Stats()
+ maxIdleClosed := st.MaxIdleTimeClosed - preMaxIdleClosed
+ if g, w := maxIdleClosed, item.wantIdleClosed; g != w {
+ t.Errorf(" got: %d; want %d max idle closed conns", g, w)
+ }
+ })
+ }
+}
+
type nvcDriver struct {
fakeDriver
skipNamedValueCheck bool