Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@methane
Last active August 29, 2015 14:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save methane/6e77daac5a8c205ac359 to your computer and use it in GitHub Desktop.
Save methane/6e77daac5a8c205ac359 to your computer and use it in GitHub Desktop.
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