mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
database/sql: add SetConnMaxIdleTime
Allow removing a connection from the connection pool after it has been idle for a period of time, without regard to the total lifespan of the connection. Fixes #25232 Change-Id: Icff157b906769a2d2d45c67525e04a72feb8d832 Reviewed-on: https://go-review.googlesource.com/c/go/+/145758 Run-TryBot: Daniel Theophanes <kardianos@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
753011ebc3
commit
6c61a57cfc
2 changed files with 151 additions and 33 deletions
|
|
@ -425,12 +425,14 @@ type DB struct {
|
||||||
closed bool
|
closed bool
|
||||||
dep map[finalCloser]depSet
|
dep map[finalCloser]depSet
|
||||||
lastPut map[*driverConn]string // stacktrace of last conn's put; debug only
|
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
|
maxOpen int // <= 0 means unlimited
|
||||||
maxLifetime time.Duration // maximum amount of time a connection may be reused
|
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{}
|
cleanerCh chan struct{}
|
||||||
waitCount int64 // Total number of connections waited for.
|
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.
|
maxLifetimeClosed int64 // Total number of connections closed due to max free limit.
|
||||||
|
|
||||||
stop func() // stop cancels the connection opener and the session resetter.
|
stop func() // stop cancels the connection opener and the session resetter.
|
||||||
|
|
@ -465,6 +467,7 @@ type driverConn struct {
|
||||||
|
|
||||||
// guarded by db.mu
|
// guarded by db.mu
|
||||||
inUse bool
|
inUse bool
|
||||||
|
returnedAt time.Time // Time the connection was created or returned.
|
||||||
onPut []func() // code (with db.mu held) run when conn is next 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
|
dbmuClosed bool // same as closed, but guarded by db.mu, for removeClosedStmtLocked
|
||||||
}
|
}
|
||||||
|
|
@ -839,7 +842,7 @@ func (db *DB) Close() error {
|
||||||
const defaultMaxIdleConns = 2
|
const defaultMaxIdleConns = 2
|
||||||
|
|
||||||
func (db *DB) maxIdleConnsLocked() int {
|
func (db *DB) maxIdleConnsLocked() int {
|
||||||
n := db.maxIdle
|
n := db.maxIdleCount
|
||||||
switch {
|
switch {
|
||||||
case n == 0:
|
case n == 0:
|
||||||
// TODO(bradfitz): ask driver, if supported, for its default preference
|
// TODO(bradfitz): ask driver, if supported, for its default preference
|
||||||
|
|
@ -851,6 +854,14 @@ func (db *DB) maxIdleConnsLocked() int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// SetMaxIdleConns sets the maximum number of connections in the idle
|
||||||
// connection pool.
|
// connection pool.
|
||||||
//
|
//
|
||||||
|
|
@ -864,14 +875,14 @@ func (db *DB) maxIdleConnsLocked() int {
|
||||||
func (db *DB) SetMaxIdleConns(n int) {
|
func (db *DB) SetMaxIdleConns(n int) {
|
||||||
db.mu.Lock()
|
db.mu.Lock()
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
db.maxIdle = n
|
db.maxIdleCount = n
|
||||||
} else {
|
} else {
|
||||||
// No idle connections.
|
// No idle connections.
|
||||||
db.maxIdle = -1
|
db.maxIdleCount = -1
|
||||||
}
|
}
|
||||||
// Make sure maxIdle doesn't exceed maxOpen
|
// Make sure maxIdle doesn't exceed maxOpen
|
||||||
if db.maxOpen > 0 && db.maxIdleConnsLocked() > db.maxOpen {
|
if db.maxOpen > 0 && db.maxIdleConnsLocked() > db.maxOpen {
|
||||||
db.maxIdle = db.maxOpen
|
db.maxIdleCount = db.maxOpen
|
||||||
}
|
}
|
||||||
var closing []*driverConn
|
var closing []*driverConn
|
||||||
idleCount := len(db.freeConn)
|
idleCount := len(db.freeConn)
|
||||||
|
|
@ -912,13 +923,13 @@ func (db *DB) SetMaxOpenConns(n int) {
|
||||||
//
|
//
|
||||||
// Expired connections may be closed lazily before reuse.
|
// 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) {
|
func (db *DB) SetConnMaxLifetime(d time.Duration) {
|
||||||
if d < 0 {
|
if d < 0 {
|
||||||
d = 0
|
d = 0
|
||||||
}
|
}
|
||||||
db.mu.Lock()
|
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 {
|
if d > 0 && d < db.maxLifetime && db.cleanerCh != nil {
|
||||||
select {
|
select {
|
||||||
case db.cleanerCh <- struct{}{}:
|
case db.cleanerCh <- struct{}{}:
|
||||||
|
|
@ -930,11 +941,34 @@ func (db *DB) SetConnMaxLifetime(d time.Duration) {
|
||||||
db.mu.Unlock()
|
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.
|
// startCleanerLocked starts connectionCleaner if needed.
|
||||||
func (db *DB) startCleanerLocked() {
|
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)
|
db.cleanerCh = make(chan struct{}, 1)
|
||||||
go db.connectionCleaner(db.maxLifetime)
|
go db.connectionCleaner(db.shortestIdleTimeLocked())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -953,15 +987,30 @@ func (db *DB) connectionCleaner(d time.Duration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
db.mu.Lock()
|
db.mu.Lock()
|
||||||
d = db.maxLifetime
|
|
||||||
|
d = db.shortestIdleTimeLocked()
|
||||||
if db.closed || db.numOpen == 0 || d <= 0 {
|
if db.closed || db.numOpen == 0 || d <= 0 {
|
||||||
db.cleanerCh = nil
|
db.cleanerCh = nil
|
||||||
db.mu.Unlock()
|
db.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expiredSince := nowFunc().Add(-d)
|
closing := db.connectionCleanerRunLocked()
|
||||||
var closing []*driverConn
|
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++ {
|
for i := 0; i < len(db.freeConn); i++ {
|
||||||
c := db.freeConn[i]
|
c := db.freeConn[i]
|
||||||
if c.createdAt.Before(expiredSince) {
|
if c.createdAt.Before(expiredSince) {
|
||||||
|
|
@ -974,17 +1023,26 @@ func (db *DB) connectionCleaner(d time.Duration) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.maxLifetimeClosed += int64(len(closing))
|
db.maxLifetimeClosed += int64(len(closing))
|
||||||
db.mu.Unlock()
|
|
||||||
|
|
||||||
for _, c := range closing {
|
|
||||||
c.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if d < minInterval {
|
if db.maxIdleTime > 0 {
|
||||||
d = minInterval
|
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.
|
// DBStats contains database statistics.
|
||||||
|
|
@ -1000,6 +1058,7 @@ type DBStats struct {
|
||||||
WaitCount int64 // The total number of connections waited for.
|
WaitCount int64 // The total number of connections waited for.
|
||||||
WaitDuration time.Duration // The total time blocked waiting for a new connection.
|
WaitDuration time.Duration // The total time blocked waiting for a new connection.
|
||||||
MaxIdleClosed int64 // The total number of connections closed due to SetMaxIdleConns.
|
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.
|
MaxLifetimeClosed int64 // The total number of connections closed due to SetConnMaxLifetime.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1020,6 +1079,7 @@ func (db *DB) Stats() DBStats {
|
||||||
WaitCount: db.waitCount,
|
WaitCount: db.waitCount,
|
||||||
WaitDuration: time.Duration(wait),
|
WaitDuration: time.Duration(wait),
|
||||||
MaxIdleClosed: db.maxIdleClosed,
|
MaxIdleClosed: db.maxIdleClosed,
|
||||||
|
MaxIdleTimeClosed: db.maxIdleTimeClosed,
|
||||||
MaxLifetimeClosed: db.maxLifetimeClosed,
|
MaxLifetimeClosed: db.maxLifetimeClosed,
|
||||||
}
|
}
|
||||||
return stats
|
return stats
|
||||||
|
|
@ -1099,6 +1159,7 @@ func (db *DB) openNewConnection(ctx context.Context) {
|
||||||
dc := &driverConn{
|
dc := &driverConn{
|
||||||
db: db,
|
db: db,
|
||||||
createdAt: nowFunc(),
|
createdAt: nowFunc(),
|
||||||
|
returnedAt: nowFunc(),
|
||||||
ci: ci,
|
ci: ci,
|
||||||
}
|
}
|
||||||
if db.putConnDBLocked(dc, err) {
|
if db.putConnDBLocked(dc, err) {
|
||||||
|
|
@ -1177,7 +1238,7 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn
|
||||||
db.waitCount++
|
db.waitCount++
|
||||||
db.mu.Unlock()
|
db.mu.Unlock()
|
||||||
|
|
||||||
waitStart := time.Now()
|
waitStart := nowFunc()
|
||||||
|
|
||||||
// Timeout the connection request with the context.
|
// Timeout the connection request with the context.
|
||||||
select {
|
select {
|
||||||
|
|
@ -1237,6 +1298,7 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn
|
||||||
dc := &driverConn{
|
dc := &driverConn{
|
||||||
db: db,
|
db: db,
|
||||||
createdAt: nowFunc(),
|
createdAt: nowFunc(),
|
||||||
|
returnedAt: nowFunc(),
|
||||||
ci: ci,
|
ci: ci,
|
||||||
inUse: true,
|
inUse: true,
|
||||||
}
|
}
|
||||||
|
|
@ -1286,6 +1348,7 @@ func (db *DB) putConn(dc *driverConn, err error, resetSession bool) {
|
||||||
db.lastPut[dc] = stack()
|
db.lastPut[dc] = stack()
|
||||||
}
|
}
|
||||||
dc.inUse = false
|
dc.inUse = false
|
||||||
|
dc.returnedAt = nowFunc()
|
||||||
|
|
||||||
for _, fn := range dc.onPut {
|
for _, fn := range dc.onPut {
|
||||||
fn()
|
fn()
|
||||||
|
|
|
||||||
|
|
@ -3593,6 +3593,61 @@ func TestStatsMaxIdleClosedTen(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
type nvcDriver struct {
|
||||||
fakeDriver
|
fakeDriver
|
||||||
skipNamedValueCheck bool
|
skipNamedValueCheck bool
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue