aboutsummaryrefslogtreecommitdiff
path: root/src/database/sql/sql.go
diff options
context:
space:
mode:
authorChris Hines <chris.cs.guy@gmail.com>2015-09-14 03:44:56 -0400
committerBrad Fitzpatrick <bradfitz@golang.org>2015-10-16 15:17:03 +0000
commit6de40099c88048c95df40c873e89b0e31f70ac24 (patch)
treed5fde34e8e15ef68a5aaea1320f49265275e3cbf /src/database/sql/sql.go
parent19aa4209aebce5deaf485268e210ed3fc29cacd5 (diff)
downloadgo-6de40099c88048c95df40c873e89b0e31f70ac24.tar.xz
database/sql: avoid deadlock waiting for connections
Previously with db.maxOpen > 0, db.maxOpen+n failed connection attempts started concurrently could result in a deadlock. DB.conn and DB.openNewConnection did not trigger the DB.connectionOpener go routine after a failed connection attempt. This omission could leave go routines waiting for DB.connectionOpener forever. In addition the logic to track the state of the pool was inconsistent. db.numOpen was sometimes incremented optimistically and sometimes not. This change harmonizes the logic and eliminates the db.pendingOpens variable, making the logic easier to understand and maintain. Fixes #10886 Change-Id: I983c4921a3dacfbd531c3d7f8d2da8a592e9922a Reviewed-on: https://go-review.googlesource.com/14547 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
Diffstat (limited to 'src/database/sql/sql.go')
-rw-r--r--src/database/sql/sql.go24
1 files changed, 16 insertions, 8 deletions
diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go
index fbb0e594a5..f3fed953ad 100644
--- a/src/database/sql/sql.go
+++ b/src/database/sql/sql.go
@@ -229,8 +229,7 @@ type DB struct {
mu sync.Mutex // protects following fields
freeConn []*driverConn
connRequests []chan connRequest
- numOpen int
- pendingOpens int
+ numOpen int // number of opened and pending open connections
// Used to signal the need for new connections
// a goroutine running connectionOpener() reads on this chan and
// maybeOpenNewConnections sends on the chan (one send per needed connection)
@@ -615,15 +614,15 @@ func (db *DB) Stats() DBStats {
// If there are connRequests and the connection limit hasn't been reached,
// then tell the connectionOpener to open new connections.
func (db *DB) maybeOpenNewConnections() {
- numRequests := len(db.connRequests) - db.pendingOpens
+ numRequests := len(db.connRequests)
if db.maxOpen > 0 {
- numCanOpen := db.maxOpen - (db.numOpen + db.pendingOpens)
+ numCanOpen := db.maxOpen - db.numOpen
if numRequests > numCanOpen {
numRequests = numCanOpen
}
}
for numRequests > 0 {
- db.pendingOpens++
+ db.numOpen++ // optimistically
numRequests--
db.openerCh <- struct{}{}
}
@@ -638,6 +637,9 @@ func (db *DB) connectionOpener() {
// Open one new connection
func (db *DB) openNewConnection() {
+ // maybeOpenNewConnctions has already executed db.numOpen++ before it sent
+ // on db.openerCh. This function must execute db.numOpen-- if the
+ // connection fails or is closed before returning.
ci, err := db.driver.Open(db.dsn)
db.mu.Lock()
defer db.mu.Unlock()
@@ -645,11 +647,13 @@ func (db *DB) openNewConnection() {
if err == nil {
ci.Close()
}
+ db.numOpen--
return
}
- db.pendingOpens--
if err != nil {
+ db.numOpen--
db.putConnDBLocked(nil, err)
+ db.maybeOpenNewConnections()
return
}
dc := &driverConn{
@@ -658,8 +662,8 @@ func (db *DB) openNewConnection() {
}
if db.putConnDBLocked(dc, err) {
db.addDepLocked(dc, dc)
- db.numOpen++
} else {
+ db.numOpen--
ci.Close()
}
}
@@ -701,7 +705,10 @@ func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) {
req := make(chan connRequest, 1)
db.connRequests = append(db.connRequests, req)
db.mu.Unlock()
- ret := <-req
+ ret, ok := <-req
+ if !ok {
+ return nil, errDBClosed
+ }
return ret.conn, ret.err
}
@@ -711,6 +718,7 @@ func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) {
if err != nil {
db.mu.Lock()
db.numOpen-- // correct for earlier optimism
+ db.maybeOpenNewConnections()
db.mu.Unlock()
return nil, err
}