database/sql: allow simultaneous queries, etc in a Tx

Now that revision 0c029965805f is in, it's easy
to guarantee that we never access a driver.Conn
concurrently, per the database/sql/driver contract,
so we can remove this overlarge mutex.

Fixes #3857

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/7707047
This commit is contained in:
Brad Fitzpatrick 2013-03-18 11:39:00 -07:00
parent 9db0583007
commit a7a803c7b7
2 changed files with 30 additions and 21 deletions

View file

@ -590,7 +590,7 @@ func (db *DB) queryConn(dc *driverConn, releaseConn func(error), query string, a
releaseConn(err) releaseConn(err)
return nil, err return nil, err
} }
// Note: ownership of ci passes to the *Rows, to be freed // Note: ownership of dc passes to the *Rows, to be freed
// with releaseConn. // with releaseConn.
rows := &Rows{ rows := &Rows{
db: db, db: db,
@ -689,15 +689,9 @@ type Tx struct {
// dc is owned exclusively until Commit or Rollback, at which point // dc is owned exclusively until Commit or Rollback, at which point
// it's returned with putConn. // it's returned with putConn.
// TODO(bradfitz): golang.org/issue/3857
dc *driverConn dc *driverConn
txi driver.Tx txi driver.Tx
// cimu is held while somebody is using ci (between grabConn
// and releaseConn)
// TODO(bradfitz): golang.org/issue/3857
cimu sync.Mutex
// done transitions from false to true exactly once, on Commit // done transitions from false to true exactly once, on Commit
// or Rollback. once done, all operations fail with // or Rollback. once done, all operations fail with
// ErrTxDone. // ErrTxDone.
@ -720,14 +714,9 @@ func (tx *Tx) grabConn() (*driverConn, error) {
if tx.done { if tx.done {
return nil, ErrTxDone return nil, ErrTxDone
} }
tx.cimu.Lock()
return tx.dc, nil return tx.dc, nil
} }
func (tx *Tx) releaseConn() {
tx.cimu.Unlock()
}
// Commit commits the transaction. // Commit commits the transaction.
func (tx *Tx) Commit() error { func (tx *Tx) Commit() error {
if tx.done { if tx.done {
@ -774,7 +763,6 @@ func (tx *Tx) Prepare(query string) (*Stmt, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer tx.releaseConn()
dc.Lock() dc.Lock()
si, err := dc.ci.Prepare(query) si, err := dc.ci.Prepare(query)
@ -817,7 +805,6 @@ func (tx *Tx) Stmt(stmt *Stmt) *Stmt {
if err != nil { if err != nil {
return &Stmt{stickyErr: err} return &Stmt{stickyErr: err}
} }
defer tx.releaseConn()
dc.Lock() dc.Lock()
si, err := dc.ci.Prepare(stmt.query) si, err := dc.ci.Prepare(stmt.query)
dc.Unlock() dc.Unlock()
@ -840,7 +827,6 @@ func (tx *Tx) Exec(query string, args ...interface{}) (Result, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer tx.releaseConn()
if execer, ok := dc.ci.(driver.Execer); ok { if execer, ok := dc.ci.(driver.Execer); ok {
dargs, err := driverArgs(nil, args) dargs, err := driverArgs(nil, args)
@ -871,14 +857,12 @@ func (tx *Tx) Exec(query string, args ...interface{}) (Result, error) {
// Query executes a query that returns rows, typically a SELECT. // Query executes a query that returns rows, typically a SELECT.
func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) { func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) {
ci, err := tx.grabConn() dc, err := tx.grabConn()
if err != nil { if err != nil {
return nil, err return nil, err
} }
releaseConn := func(error) {}
releaseConn := func(err error) { tx.releaseConn() } return tx.db.queryConn(dc, releaseConn, query, args)
return tx.db.queryConn(ci, releaseConn, query, args)
} }
// QueryRow executes a query that is expected to return at most one row. // QueryRow executes a query that is expected to return at most one row.
@ -980,7 +964,7 @@ func (s *Stmt) connStmt() (ci *driverConn, releaseConn func(error), si driver.St
if err != nil { if err != nil {
return return
} }
releaseConn = func(error) { s.tx.releaseConn() } releaseConn = func(error) {}
return ci, releaseConn, s.txsi.si, nil return ci, releaseConn, s.txsi.si, nil
} }

View file

@ -736,3 +736,28 @@ func TestIssue4902(t *testing.T) {
t.Logf("stmt = %#v", stmt) t.Logf("stmt = %#v", stmt)
} }
} }
// Issue 3857
// This used to deadlock.
func TestSimultaneousQueries(t *testing.T) {
db := newTestDB(t, "people")
defer closeDB(t, db)
tx, err := db.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Rollback()
r1, err := tx.Query("SELECT|people|name|")
if err != nil {
t.Fatal(err)
}
defer r1.Close()
r2, err := tx.Query("SELECT|people|name|")
if err != nil {
t.Fatal(err)
}
defer r2.Close()
}