Last active
August 29, 2015 14:15
-
-
Save methane/6e77daac5a8c205ac359 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go | |
index 1ce679d..2ccf7b3 100644 | |
--- a/src/database/sql/sql.go | |
+++ b/src/database/sql/sql.go | |
@@ -21,6 +21,7 @@ import ( | |
"sort" | |
"sync" | |
"sync/atomic" | |
+ "time" | |
) | |
var drivers = make(map[string]driver.Driver) | |
@@ -227,12 +228,14 @@ type DB struct { | |
// maybeOpenNewConnections sends on the chan (one send per needed connection) | |
// It is closed during db.Close(). The close tells the connectionOpener | |
// goroutine to exit. | |
- openerCh chan struct{} | |
- 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 | |
- maxOpen int // <= 0 means unlimited | |
+ openerCh chan struct{} | |
+ 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 | |
+ maxOpen int // <= 0 means unlimited | |
+ reuseTimeout time.Duration // how long connections can be reused since it is created | |
+ reuseTimeoutChanged chan struct{} | |
} | |
// driverConn wraps a driver.Conn with a mutex, to | |
@@ -240,7 +243,8 @@ type DB struct { | |
// interfaces returned via that Conn, such as calls on Tx, Stmt, | |
// Result, Rows) | |
type driverConn struct { | |
- db *DB | |
+ db *DB | |
+ createdUnix int64 | |
sync.Mutex // guards following | |
ci driver.Conn | |
@@ -494,6 +498,10 @@ func (db *DB) Close() error { | |
for _, req := range db.connRequests { | |
close(req) | |
} | |
+ if db.reuseTimeoutChanged != nil { | |
+ close(db.reuseTimeoutChanged) | |
+ db.reuseTimeoutChanged = nil | |
+ } | |
db.mu.Unlock() | |
for _, fn := range fns { | |
err1 := fn() | |
@@ -551,6 +559,67 @@ func (db *DB) SetMaxIdleConns(n int) { | |
} | |
} | |
+func (db *DB) SetMaxReuseTimeout(t time.Duration) { | |
+ if t < 0 { | |
+ t = 0 | |
+ } | |
+ db.mu.Lock() | |
+ if db.closed { | |
+ return | |
+ } | |
+ db.reuseTimeout = t | |
+ if t > 0 && db.reuseTimeoutChanged == nil { | |
+ db.reuseTimeoutChanged = make(chan struct{}, 1) | |
+ go db.expiredConnectionCloser() | |
+ } else { | |
+ db.reuseTimeoutChanged <- struct{}{} | |
+ } | |
+ db.mu.Unlock() | |
+} | |
+ | |
+func (db *DB) expiredConnectionCloser() { | |
+ const maxSleep = time.Minute | |
+ var closing []*driverConn | |
+ for { | |
+ db.mu.Lock() | |
+ t := db.reuseTimeout | |
+ if t <= 0 { | |
+ db.mu.Unlock() | |
+ _, ok := <-db.reuseTimeoutChanged | |
+ if !ok { | |
+ return | |
+ } | |
+ continue | |
+ } | |
+ expireBefore := time.Now().Add(-t).Unix() | |
+ for i := 0; i < len(db.freeConn); i++ { | |
+ c := db.freeConn[i] | |
+ if c.createdUnix < expireBefore { | |
+ closing = append(closing, c) | |
+ last := len(db.freeConn) - 1 | |
+ db.freeConn[i] = db.freeConn[last] | |
+ db.freeConn[last] = nil | |
+ db.freeConn = db.freeConn[:last] | |
+ } | |
+ } | |
+ db.mu.Unlock() | |
+ | |
+ for i, c := range closing { | |
+ closing[i] = nil | |
+ c.Close() | |
+ } | |
+ closing = closing[:0] | |
+ | |
+ select { | |
+ case _, ok := <-db.reuseTimeoutChanged: | |
+ if !ok { | |
+ return | |
+ } | |
+ case <-time.After(time.Second * 7): // Heuristic interval. | |
+ } | |
+ } | |
+} | |
+ | |
// SetMaxOpenConns sets the maximum number of open connections to the database. | |
// | |
// If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than | |
@@ -614,8 +683,9 @@ func (db *DB) openNewConnection() { | |
return | |
} | |
dc := &driverConn{ | |
- db: db, | |
- ci: ci, | |
+ db: db, | |
+ createdUnix: time.Now().Unix(), | |
+ ci: ci, | |
} | |
if db.putConnDBLocked(dc, err) { | |
db.addDepLocked(dc, dc) | |
@@ -655,12 +725,18 @@ func (db *DB) conn() (*driverConn, error) { | |
return ret.conn, ret.err | |
} | |
- if c := len(db.freeConn); c > 0 { | |
- conn := db.freeConn[0] | |
- copy(db.freeConn, db.freeConn[1:]) | |
- db.freeConn = db.freeConn[:c-1] | |
+ expiredAt := time.Now().Add(-db.reuseTimeout).Unix() | |
+ | |
+ for c := len(db.freeConn) - 1; c >= 0; c-- { | |
+ conn := db.freeConn[c] | |
+ db.freeConn = db.freeConn[:c] | |
conn.inUse = true | |
db.mu.Unlock() | |
+ if conn.createdUnix < expiredAt { | |
+ conn.Close() | |
+ db.mu.Lock() | |
+ continue | |
+ } | |
return conn, nil | |
} | |
@@ -675,8 +751,9 @@ func (db *DB) conn() (*driverConn, error) { | |
} | |
db.mu.Lock() | |
dc := &driverConn{ | |
- db: db, | |
- ci: ci, | |
+ db: db, | |
+ createdUnix: time.Now().Unix(), | |
+ ci: ci, | |
} | |
db.addDepLocked(dc, dc) | |
dc.inUse = true | |
@@ -719,6 +796,7 @@ const debugGetPut = false | |
// err is optionally the last error that occurred on this connection. | |
func (db *DB) putConn(dc *driverConn, err error) { | |
db.mu.Lock() | |
+ expired := dc.createdUnix < time.Now().Add(-db.reuseTimeout).Unix() | |
if !dc.inUse { | |
if debugGetPut { | |
fmt.Printf("putConn(%v) DUPLICATE was: %s\n\nPREVIOUS was: %s", dc, stack(), db.lastPut[dc]) | |
@@ -735,8 +813,8 @@ func (db *DB) putConn(dc *driverConn, err error) { | |
} | |
dc.onPut = nil | |
- if err == driver.ErrBadConn { | |
- // Don't reuse bad connections. | |
+ if err == driver.ErrBadConn || expired { | |
+ // Don't reuse bad connections and expired connections. | |
// Since the conn is considered bad and is being discarded, treat it | |
// as closed. Don't decrement the open count here, finalClose will | |
// take care of that. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment