mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
database/sql: Fix connection leak and potential deadlock
CL 10726044 introduced a race condition which causes connections to be leaked under certain circumstances. If SetMaxOpenConns is used, the application eventually deadlocks. Otherwise, the number of open connections just keep growing indefinitely. Fixes #6593 R=golang-dev, bradfitz, tad.glines, bketelsen CC=golang-dev https://golang.org/cl/14611045
This commit is contained in:
parent
478f4b6754
commit
37db880469
3 changed files with 63 additions and 3 deletions
|
|
@ -38,6 +38,8 @@ type fakeDriver struct {
|
||||||
mu sync.Mutex // guards 3 following fields
|
mu sync.Mutex // guards 3 following fields
|
||||||
openCount int // conn opens
|
openCount int // conn opens
|
||||||
closeCount int // conn closes
|
closeCount int // conn closes
|
||||||
|
waitCh chan struct{}
|
||||||
|
waitingCh chan struct{}
|
||||||
dbs map[string]*fakeDB
|
dbs map[string]*fakeDB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,6 +148,10 @@ func (d *fakeDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
if len(parts) >= 2 && parts[1] == "badConn" {
|
if len(parts) >= 2 && parts[1] == "badConn" {
|
||||||
conn.bad = true
|
conn.bad = true
|
||||||
}
|
}
|
||||||
|
if d.waitCh != nil {
|
||||||
|
d.waitingCh <- struct{}{}
|
||||||
|
<-d.waitCh
|
||||||
|
}
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -593,9 +593,12 @@ func (db *DB) openNewConnection() {
|
||||||
db: db,
|
db: db,
|
||||||
ci: ci,
|
ci: ci,
|
||||||
}
|
}
|
||||||
db.addDepLocked(dc, dc)
|
if db.putConnDBLocked(dc, err) {
|
||||||
db.numOpen++
|
db.addDepLocked(dc, dc)
|
||||||
db.putConnDBLocked(dc, err)
|
db.numOpen++
|
||||||
|
} else {
|
||||||
|
ci.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// connRequest represents one request for a new connection
|
// connRequest represents one request for a new connection
|
||||||
|
|
|
||||||
|
|
@ -1677,6 +1677,57 @@ func TestConcurrency(t *testing.T) {
|
||||||
doConcurrentTest(t, new(concurrentRandomTest))
|
doConcurrentTest(t, new(concurrentRandomTest))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConnectionLeak(t *testing.T) {
|
||||||
|
db := newTestDB(t, "people")
|
||||||
|
defer closeDB(t, db)
|
||||||
|
// Start by opening defaultMaxIdleConns
|
||||||
|
rows := make([]*Rows, defaultMaxIdleConns)
|
||||||
|
// We need to SetMaxOpenConns > MaxIdleConns, so the DB can open
|
||||||
|
// a new connection and we can fill the idle queue with the released
|
||||||
|
// connections.
|
||||||
|
db.SetMaxOpenConns(len(rows) + 1)
|
||||||
|
for ii := range rows {
|
||||||
|
r, err := db.Query("SELECT|people|name|")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
r.Next()
|
||||||
|
if err := r.Err(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rows[ii] = r
|
||||||
|
}
|
||||||
|
// Now we have defaultMaxIdleConns busy connections. Open
|
||||||
|
// a new one, but wait until the busy connections are released
|
||||||
|
// before returning control to DB.
|
||||||
|
drv := db.driver.(*fakeDriver)
|
||||||
|
drv.waitCh = make(chan struct{}, 1)
|
||||||
|
drv.waitingCh = make(chan struct{}, 1)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
r, err := db.Query("SELECT|people|name|")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
r.Close()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
// Wait until the goroutine we've just created has started waiting.
|
||||||
|
<-drv.waitingCh
|
||||||
|
// Now close the busy connections. This provides a connection for
|
||||||
|
// the blocked goroutine and then fills up the idle queue.
|
||||||
|
for _, v := range rows {
|
||||||
|
v.Close()
|
||||||
|
}
|
||||||
|
// At this point we give the new connection to DB. This connection is
|
||||||
|
// now useless, since the idle queue is full and there are no pending
|
||||||
|
// requests. DB should deal with this situation without leaking the
|
||||||
|
// connection.
|
||||||
|
drv.waitCh <- struct{}{}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkConcurrentDBExec(b *testing.B) {
|
func BenchmarkConcurrentDBExec(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
ct := new(concurrentDBExecTest)
|
ct := new(concurrentDBExecTest)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue