diff options
author | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2013-11-06 19:49:01 +0000 |
---|---|---|
committer | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2013-11-06 19:49:01 +0000 |
commit | 0ce10ea1348e9afd5d0eec6bca986bfe58bac5ac (patch) | |
tree | 39530b071991b2326f881b2a30a2d82d6c133fd6 /libgo/go/database/sql | |
parent | 57a8bf1b0c6057ccbacb0cf79eb84d1985c2c1fe (diff) | |
download | ppe42-gcc-0ce10ea1348e9afd5d0eec6bca986bfe58bac5ac.tar.gz ppe42-gcc-0ce10ea1348e9afd5d0eec6bca986bfe58bac5ac.zip |
libgo: Update to October 24 version of master library.
git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@204466 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'libgo/go/database/sql')
-rw-r--r-- | libgo/go/database/sql/convert_test.go | 4 | ||||
-rw-r--r-- | libgo/go/database/sql/driver/types_test.go | 4 | ||||
-rw-r--r-- | libgo/go/database/sql/fakedb_test.go | 50 | ||||
-rw-r--r-- | libgo/go/database/sql/sql.go | 371 | ||||
-rw-r--r-- | libgo/go/database/sql/sql_test.go | 759 |
5 files changed, 1055 insertions, 133 deletions
diff --git a/libgo/go/database/sql/convert_test.go b/libgo/go/database/sql/convert_test.go index 950e24fc3a8..a39c2c54fba 100644 --- a/libgo/go/database/sql/convert_test.go +++ b/libgo/go/database/sql/convert_test.go @@ -267,14 +267,14 @@ func TestValueConverters(t *testing.T) { goterr = err.Error() } if goterr != tt.err { - t.Errorf("test %d: %s(%T(%v)) error = %q; want error = %q", + t.Errorf("test %d: %T(%T(%v)) error = %q; want error = %q", i, tt.c, tt.in, tt.in, goterr, tt.err) } if tt.err != "" { continue } if !reflect.DeepEqual(out, tt.out) { - t.Errorf("test %d: %s(%T(%v)) = %v (%T); want %v (%T)", + t.Errorf("test %d: %T(%T(%v)) = %v (%T); want %v (%T)", i, tt.c, tt.in, tt.in, out, out, tt.out, tt.out) } } diff --git a/libgo/go/database/sql/driver/types_test.go b/libgo/go/database/sql/driver/types_test.go index ab82bca7166..1ce0ff06541 100644 --- a/libgo/go/database/sql/driver/types_test.go +++ b/libgo/go/database/sql/driver/types_test.go @@ -51,14 +51,14 @@ func TestValueConverters(t *testing.T) { goterr = err.Error() } if goterr != tt.err { - t.Errorf("test %d: %s(%T(%v)) error = %q; want error = %q", + t.Errorf("test %d: %T(%T(%v)) error = %q; want error = %q", i, tt.c, tt.in, tt.in, goterr, tt.err) } if tt.err != "" { continue } if !reflect.DeepEqual(out, tt.out) { - t.Errorf("test %d: %s(%T(%v)) = %v (%T); want %v (%T)", + t.Errorf("test %d: %T(%T(%v)) = %v (%T); want %v (%T)", i, tt.c, tt.in, tt.in, out, out, tt.out, tt.out) } } diff --git a/libgo/go/database/sql/fakedb_test.go b/libgo/go/database/sql/fakedb_test.go index d900e2cebe8..a8adfdd9424 100644 --- a/libgo/go/database/sql/fakedb_test.go +++ b/libgo/go/database/sql/fakedb_test.go @@ -38,6 +38,8 @@ type fakeDriver struct { mu sync.Mutex // guards 3 following fields openCount int // conn opens closeCount int // conn closes + waitCh chan struct{} + waitingCh chan struct{} dbs map[string]*fakeDB } @@ -146,6 +148,12 @@ func (d *fakeDriver) Open(dsn string) (driver.Conn, error) { if len(parts) >= 2 && parts[1] == "badConn" { conn.bad = true } + if d.waitCh != nil { + d.waitingCh <- struct{}{} + <-d.waitCh + d.waitCh = nil + d.waitingCh = nil + } return conn, nil } @@ -447,6 +455,10 @@ func (c *fakeConn) Prepare(query string) (driver.Stmt, error) { return c.prepareCreate(stmt, parts) case "INSERT": return c.prepareInsert(stmt, parts) + case "NOSERT": + // Do all the prep-work like for an INSERT but don't actually insert the row. + // Used for some of the concurrent tests. + return c.prepareInsert(stmt, parts) default: stmt.Close() return nil, errf("unsupported command type %q", cmd) @@ -497,13 +509,20 @@ func (s *fakeStmt) Exec(args []driver.Value) (driver.Result, error) { } return driver.ResultNoRows, nil case "INSERT": - return s.execInsert(args) + return s.execInsert(args, true) + case "NOSERT": + // Do all the prep-work like for an INSERT but don't actually insert the row. + // Used for some of the concurrent tests. + return s.execInsert(args, false) } fmt.Printf("EXEC statement, cmd=%q: %#v\n", s.cmd, s) return nil, fmt.Errorf("unimplemented statement Exec command type of %q", s.cmd) } -func (s *fakeStmt) execInsert(args []driver.Value) (driver.Result, error) { +// When doInsert is true, add the row to the table. +// When doInsert is false do prep-work and error checking, but don't +// actually add the row to the table. +func (s *fakeStmt) execInsert(args []driver.Value, doInsert bool) (driver.Result, error) { db := s.c.db if len(args) != s.placeholders { panic("error in pkg db; should only get here if size is correct") @@ -518,7 +537,10 @@ func (s *fakeStmt) execInsert(args []driver.Value) (driver.Result, error) { t.mu.Lock() defer t.mu.Unlock() - cols := make([]interface{}, len(t.colname)) + var cols []interface{} + if doInsert { + cols = make([]interface{}, len(t.colname)) + } argPos := 0 for n, colname := range s.colName { colidx := t.columnIndex(colname) @@ -532,10 +554,14 @@ func (s *fakeStmt) execInsert(args []driver.Value) (driver.Result, error) { } else { val = s.colValue[n] } - cols[colidx] = val + if doInsert { + cols[colidx] = val + } } - t.rows = append(t.rows, &row{cols: cols}) + if doInsert { + t.rows = append(t.rows, &row{cols: cols}) + } return driver.RowsAffected(1), nil } @@ -608,9 +634,10 @@ rows: } cursor := &rowsCursor{ - pos: -1, - rows: mrows, - cols: s.colName, + pos: -1, + rows: mrows, + cols: s.colName, + errPos: -1, } return cursor, nil } @@ -635,6 +662,10 @@ type rowsCursor struct { rows []*row closed bool + // errPos and err are for making Next return early with error. + errPos int + err error + // a clone of slices to give out to clients, indexed by the // the original slice's first byte address. we clone them // just so we're able to corrupt them on close. @@ -660,6 +691,9 @@ func (rc *rowsCursor) Next(dest []driver.Value) error { return errors.New("fakedb: cursor is closed") } rc.pos++ + if rc.pos == rc.errPos { + return rc.err + } if rc.pos >= len(rc.rows) { return io.EOF // per interface spec } diff --git a/libgo/go/database/sql/sql.go b/libgo/go/database/sql/sql.go index a80782bfedc..f7b4f8cdab8 100644 --- a/libgo/go/database/sql/sql.go +++ b/libgo/go/database/sql/sql.go @@ -7,9 +7,13 @@ // // The sql package must be used in conjunction with a database driver. // See http://golang.org/s/sqldrivers for a list of drivers. +// +// For more usage examples, see the wiki page at +// http://golang.org/s/sqlwiki. package sql import ( + "container/list" "database/sql/driver" "errors" "fmt" @@ -192,12 +196,22 @@ type DB struct { driver driver.Driver dsn string - mu sync.Mutex // protects following fields - freeConn []*driverConn + mu sync.Mutex // protects following fields + freeConn *list.List // of *driverConn + connRequests *list.List // of connRequest + numOpen int + pendingOpens int + // Used to sygnal the need for new connections + // a goroutine running connectionOpener() reads on this chan and + // 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 } // driverConn wraps a driver.Conn with a mutex, to @@ -217,6 +231,13 @@ type driverConn struct { inUse bool onPut []func() // code (with db.mu held) run when conn is next returned dbmuClosed bool // same as closed, but guarded by db.mu, for connIfFree + // This is the Element returned by db.freeConn.PushFront(conn). + // It's used by connIfFree to remove the conn from the freeConn list. + listElem *list.Element +} + +func (dc *driverConn) releaseConn(err error) { + dc.db.putConn(dc, err) } func (dc *driverConn) removeOpenStmt(si driver.Stmt) { @@ -250,15 +271,14 @@ func (dc *driverConn) prepareLocked(query string) (driver.Stmt, error) { } // the dc.db's Mutex is held. -func (dc *driverConn) closeDBLocked() error { +func (dc *driverConn) closeDBLocked() func() error { dc.Lock() + defer dc.Unlock() if dc.closed { - dc.Unlock() - return errors.New("sql: duplicate driverConn close") + return func() error { return errors.New("sql: duplicate driverConn close") } } dc.closed = true - dc.Unlock() // not defer; removeDep finalClose calls may need to lock - return dc.db.removeDepLocked(dc, dc)() + return dc.db.removeDepLocked(dc, dc) } func (dc *driverConn) Close() error { @@ -289,8 +309,13 @@ func (dc *driverConn) finalClose() error { err := dc.ci.Close() dc.ci = nil dc.finalClosed = true - dc.Unlock() + + dc.db.mu.Lock() + dc.db.numOpen-- + dc.db.maybeOpenNewConnections() + dc.db.mu.Unlock() + return err } @@ -353,26 +378,36 @@ func (db *DB) removeDep(x finalCloser, dep interface{}) error { func (db *DB) removeDepLocked(x finalCloser, dep interface{}) func() error { //println(fmt.Sprintf("removeDep(%T %p, %T %p)", x, x, dep, dep)) - done := false - xdep := db.dep[x] - if xdep != nil { - delete(xdep, dep) - if len(xdep) == 0 { - delete(db.dep, x) - done = true - } + xdep, ok := db.dep[x] + if !ok { + panic(fmt.Sprintf("unpaired removeDep: no deps for %T", x)) } - if !done { + l0 := len(xdep) + delete(xdep, dep) + + switch len(xdep) { + case l0: + // Nothing removed. Shouldn't happen. + panic(fmt.Sprintf("unpaired removeDep: no %T dep on %T", dep, x)) + case 0: + // No more dependencies. + delete(db.dep, x) + return x.finalClose + default: + // Dependencies remain. return func() error { return nil } } - return func() error { - //println(fmt.Sprintf("calling final close on %T %v (%#v)", x, x, x)) - return x.finalClose() - } } +// This is the size of the connectionOpener request chan (dn.openerCh). +// This value should be larger than the maximum typical value +// used for db.maxOpen. If maxOpen is significantly larger than +// connectionRequestQueueSize then it is possible for ALL calls into the *DB +// to block until the connectionOpener can satify the backlog of requests. +var connectionRequestQueueSize = 1000000 + // Open opens a database specified by its database driver name and a // driver-specific data source name, usually consisting of at least a // database name and connection information. @@ -391,10 +426,14 @@ func Open(driverName, dataSourceName string) (*DB, error) { return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName) } db := &DB{ - driver: driveri, - dsn: dataSourceName, - lastPut: make(map[*driverConn]string), - } + driver: driveri, + dsn: dataSourceName, + openerCh: make(chan struct{}, connectionRequestQueueSize), + lastPut: make(map[*driverConn]string), + } + db.freeConn = list.New() + db.connRequests = list.New() + go db.connectionOpener() return db, nil } @@ -415,16 +454,32 @@ func (db *DB) Ping() error { // Close closes the database, releasing any open resources. func (db *DB) Close() error { db.mu.Lock() - defer db.mu.Unlock() + if db.closed { // Make DB.Close idempotent + db.mu.Unlock() + return nil + } + close(db.openerCh) var err error - for _, dc := range db.freeConn { - err1 := dc.closeDBLocked() + fns := make([]func() error, 0, db.freeConn.Len()) + for db.freeConn.Front() != nil { + dc := db.freeConn.Front().Value.(*driverConn) + dc.listElem = nil + fns = append(fns, dc.closeDBLocked()) + db.freeConn.Remove(db.freeConn.Front()) + } + db.closed = true + for db.connRequests.Front() != nil { + req := db.connRequests.Front().Value.(connRequest) + db.connRequests.Remove(db.connRequests.Front()) + close(req) + } + db.mu.Unlock() + for _, fn := range fns { + err1 := fn() if err1 != nil { err = err1 } } - db.freeConn = nil - db.closed = true return err } @@ -446,50 +501,168 @@ func (db *DB) maxIdleConnsLocked() int { // SetMaxIdleConns sets the maximum number of connections in the idle // connection pool. // +// If MaxOpenConns is greater than 0 but less than the new MaxIdleConns +// then the new MaxIdleConns will be reduced to match the MaxOpenConns limit +// // If n <= 0, no idle connections are retained. func (db *DB) SetMaxIdleConns(n int) { db.mu.Lock() - defer db.mu.Unlock() if n > 0 { db.maxIdle = n } else { // No idle connections. db.maxIdle = -1 } - for len(db.freeConn) > 0 && len(db.freeConn) > n { - nfree := len(db.freeConn) - dc := db.freeConn[nfree-1] - db.freeConn[nfree-1] = nil - db.freeConn = db.freeConn[:nfree-1] - go dc.Close() + // Make sure maxIdle doesn't exceed maxOpen + if db.maxOpen > 0 && db.maxIdleConnsLocked() > db.maxOpen { + db.maxIdle = db.maxOpen + } + var closing []*driverConn + for db.freeConn.Len() > db.maxIdleConnsLocked() { + dc := db.freeConn.Back().Value.(*driverConn) + dc.listElem = nil + db.freeConn.Remove(db.freeConn.Back()) + closing = append(closing, dc) + } + db.mu.Unlock() + for _, c := range closing { + c.Close() } } +// SetMaxOpenConns sets the maximum number of open connections to the database. +// +// If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than +// MaxIdleConns, then MaxIdleConns will be reduced to match the new +// MaxOpenConns limit +// +// If n <= 0, then there is no limit on the number of open connections. +// The default is 0 (unlimited). +func (db *DB) SetMaxOpenConns(n int) { + db.mu.Lock() + db.maxOpen = n + if n < 0 { + db.maxOpen = 0 + } + syncMaxIdle := db.maxOpen > 0 && db.maxIdleConnsLocked() > db.maxOpen + db.mu.Unlock() + if syncMaxIdle { + db.SetMaxIdleConns(n) + } +} + +// Assumes db.mu is locked. +// 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 := db.connRequests.Len() - db.pendingOpens + if db.maxOpen > 0 { + numCanOpen := db.maxOpen - (db.numOpen + db.pendingOpens) + if numRequests > numCanOpen { + numRequests = numCanOpen + } + } + for numRequests > 0 { + db.pendingOpens++ + numRequests-- + db.openerCh <- struct{}{} + } +} + +// Runs in a seperate goroutine, opens new connections when requested. +func (db *DB) connectionOpener() { + for _ = range db.openerCh { + db.openNewConnection() + } +} + +// Open one new connection +func (db *DB) openNewConnection() { + ci, err := db.driver.Open(db.dsn) + db.mu.Lock() + defer db.mu.Unlock() + if db.closed { + if err == nil { + ci.Close() + } + return + } + db.pendingOpens-- + if err != nil { + db.putConnDBLocked(nil, err) + return + } + dc := &driverConn{ + db: db, + ci: ci, + } + if db.putConnDBLocked(dc, err) { + db.addDepLocked(dc, dc) + db.numOpen++ + } else { + ci.Close() + } +} + +// connRequest represents one request for a new connection +// When there are no idle connections available, DB.conn will create +// a new connRequest and put it on the db.connRequests list. +type connRequest chan<- interface{} // takes either a *driverConn or an error + +var errDBClosed = errors.New("sql: database is closed") + // conn returns a newly-opened or cached *driverConn func (db *DB) conn() (*driverConn, error) { db.mu.Lock() if db.closed { db.mu.Unlock() - return nil, errors.New("sql: database is closed") + return nil, errDBClosed + } + + // If db.maxOpen > 0 and the number of open connections is over the limit + // or there are no free connection, then make a request and wait. + if db.maxOpen > 0 && (db.numOpen >= db.maxOpen || db.freeConn.Len() == 0) { + // Make the connRequest channel. It's buffered so that the + // connectionOpener doesn't block while waiting for the req to be read. + ch := make(chan interface{}, 1) + req := connRequest(ch) + db.connRequests.PushBack(req) + db.maybeOpenNewConnections() + db.mu.Unlock() + ret, ok := <-ch + if !ok { + return nil, errDBClosed + } + switch ret.(type) { + case *driverConn: + return ret.(*driverConn), nil + case error: + return nil, ret.(error) + default: + panic("sql: Unexpected type passed through connRequest.ch") + } } - if n := len(db.freeConn); n > 0 { - conn := db.freeConn[n-1] - db.freeConn = db.freeConn[:n-1] + + if f := db.freeConn.Front(); f != nil { + conn := f.Value.(*driverConn) + conn.listElem = nil + db.freeConn.Remove(f) conn.inUse = true db.mu.Unlock() return conn, nil } - db.mu.Unlock() + db.mu.Unlock() ci, err := db.driver.Open(db.dsn) if err != nil { return nil, err } + db.mu.Lock() + db.numOpen++ dc := &driverConn{ db: db, ci: ci, } - db.mu.Lock() db.addDepLocked(dc, dc) dc.inUse = true db.mu.Unlock() @@ -511,18 +684,15 @@ var ( func (db *DB) connIfFree(wanted *driverConn) (*driverConn, error) { db.mu.Lock() defer db.mu.Unlock() - if wanted.inUse { - return nil, errConnBusy - } if wanted.dbmuClosed { return nil, errConnClosed } - for i, conn := range db.freeConn { - if conn != wanted { - continue - } - db.freeConn[i] = db.freeConn[len(db.freeConn)-1] - db.freeConn = db.freeConn[:len(db.freeConn)-1] + if wanted.inUse { + return nil, errConnBusy + } + if wanted.listElem != nil { + db.freeConn.Remove(wanted.listElem) + wanted.listElem = nil wanted.inUse = true return wanted, nil } @@ -582,20 +752,50 @@ func (db *DB) putConn(dc *driverConn, err error) { if err == driver.ErrBadConn { // Don't reuse bad 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. + db.maybeOpenNewConnections() db.mu.Unlock() + dc.Close() return } if putConnHook != nil { putConnHook(db, dc) } - if n := len(db.freeConn); !db.closed && n < db.maxIdleConnsLocked() { - db.freeConn = append(db.freeConn, dc) - db.mu.Unlock() - return - } + added := db.putConnDBLocked(dc, nil) db.mu.Unlock() - dc.Close() + if !added { + dc.Close() + } +} + +// Satisfy a connRequest or put the driverConn in the idle pool and return true +// or return false. +// putConnDBLocked will satisfy a connRequest if there is one, or it will +// return the *driverConn to the freeConn list if err != nil and the idle +// connection limit would not be reached. +// If err != nil, the value of dc is ignored. +// If err == nil, then dc must not equal nil. +// If a connRequest was fullfilled or the *driverConn was placed in the +// freeConn list, then true is returned, otherwise false is returned. +func (db *DB) putConnDBLocked(dc *driverConn, err error) bool { + if db.connRequests.Len() > 0 { + req := db.connRequests.Front().Value.(connRequest) + db.connRequests.Remove(db.connRequests.Front()) + if err != nil { + req <- err + } else { + dc.inUse = true + req <- dc + } + return true + } else if err == nil && !db.closed && db.maxIdleConnsLocked() > 0 && db.maxIdleConnsLocked() > db.freeConn.Len() { + dc.listElem = db.freeConn.PushFront(dc) + return true + } + return false } // Prepare creates a prepared statement for later queries or executions. @@ -710,9 +910,7 @@ func (db *DB) query(query string, args []interface{}) (*Rows, error) { return nil, err } - releaseConn := func(err error) { db.putConn(ci, err) } - - return db.queryConn(ci, releaseConn, query, args) + return db.queryConn(ci, ci.releaseConn, query, args) } // queryConn executes a query on the given connection. @@ -754,10 +952,10 @@ func (db *DB) queryConn(dc *driverConn, releaseConn func(error), query string, a ds := driverStmt{dc, si} rowsi, err := rowsiFromStatement(ds, args...) if err != nil { - releaseConn(err) dc.Lock() si.Close() dc.Unlock() + releaseConn(err) return nil, err } @@ -1154,8 +1352,7 @@ func (s *Stmt) connStmt() (ci *driverConn, releaseConn func(error), si driver.St } conn := cs.dc - releaseConn = func(err error) { s.db.putConn(conn, err) } - return conn, releaseConn, cs.si, nil + return conn, conn.releaseConn, cs.si, nil } // Query executes a prepared query statement with the given arguments @@ -1245,27 +1442,32 @@ func (s *Stmt) Close() error { return s.stickyErr } s.mu.Lock() - defer s.mu.Unlock() if s.closed { + s.mu.Unlock() return nil } s.closed = true if s.tx != nil { s.txsi.Close() + s.mu.Unlock() return nil } + s.mu.Unlock() return s.db.removeDep(s, s) } func (s *Stmt) finalClose() error { - for _, v := range s.css { - s.db.noteUnusedDriverStatement(v.dc, v.si) - v.dc.removeOpenStmt(v.si) - s.db.removeDep(v.dc, s) + s.mu.Lock() + defer s.mu.Unlock() + if s.css != nil { + for _, v := range s.css { + s.db.noteUnusedDriverStatement(v.dc, v.si) + v.dc.removeOpenStmt(v.si) + } + s.css = nil } - s.css = nil return nil } @@ -1289,7 +1491,7 @@ type Rows struct { closed bool lastcols []driver.Value - lasterr error + lasterr error // non-nil only if closed is true closeStmt driver.Stmt // if non-nil, statement to Close on close } @@ -1301,20 +1503,19 @@ func (rs *Rows) Next() bool { if rs.closed { return false } - if rs.lasterr != nil { - return false - } if rs.lastcols == nil { rs.lastcols = make([]driver.Value, len(rs.rowsi.Columns())) } rs.lasterr = rs.rowsi.Next(rs.lastcols) - if rs.lasterr == io.EOF { + if rs.lasterr != nil { rs.Close() + return false } - return rs.lasterr == nil + return true } // Err returns the error, if any, that was encountered during iteration. +// Err may be called after an explicit or implicit Close. func (rs *Rows) Err() error { if rs.lasterr == io.EOF { return nil @@ -1349,10 +1550,7 @@ func (rs *Rows) Columns() ([]string, error) { // is of type []byte, a copy is made and the caller owns the result. func (rs *Rows) Scan(dest ...interface{}) error { if rs.closed { - return errors.New("sql: Rows closed") - } - if rs.lasterr != nil { - return rs.lasterr + return errors.New("sql: Rows are closed") } if rs.lastcols == nil { return errors.New("sql: Scan called without calling Next") @@ -1369,15 +1567,20 @@ func (rs *Rows) Scan(dest ...interface{}) error { return nil } -// Close closes the Rows, preventing further enumeration. If the -// end is encountered, the Rows are closed automatically. Close -// is idempotent. +var rowsCloseHook func(*Rows, *error) + +// Close closes the Rows, preventing further enumeration. If Next returns +// false, the Rows are closed automatically and it will suffice to check the +// result of Err. Close is idempotent and does not affect the result of Err. func (rs *Rows) Close() error { if rs.closed { return nil } rs.closed = true err := rs.rowsi.Close() + if fn := rowsCloseHook; fn != nil { + fn(rs, &err) + } if rs.closeStmt != nil { rs.closeStmt.Close() } @@ -1414,13 +1617,13 @@ func (r *Row) Scan(dest ...interface{}) error { // from Next will not be modified again." (for instance, if // they were obtained from the network anyway) But for now we // don't care. + defer r.rows.Close() for _, dp := range dest { if _, ok := dp.(*RawBytes); ok { return errors.New("sql: RawBytes isn't allowed on Row.Scan") } } - defer r.rows.Close() if !r.rows.Next() { return ErrNoRows } diff --git a/libgo/go/database/sql/sql_test.go b/libgo/go/database/sql/sql_test.go index e6cc667fa97..093c0d64cac 100644 --- a/libgo/go/database/sql/sql_test.go +++ b/libgo/go/database/sql/sql_test.go @@ -5,7 +5,10 @@ package sql import ( + "database/sql/driver" + "errors" "fmt" + "math/rand" "reflect" "runtime" "strings" @@ -21,14 +24,12 @@ func init() { } freedFrom := make(map[dbConn]string) putConnHook = func(db *DB, c *driverConn) { - for _, oc := range db.freeConn { - if oc == c { - // print before panic, as panic may get lost due to conflicting panic - // (all goroutines asleep) elsewhere, since we might not unlock - // the mutex in freeConn here. - println("double free of conn. conflicts are:\nA) " + freedFrom[dbConn{db, c}] + "\n\nand\nB) " + stack()) - panic("double free of conn.") - } + if c.listElem != nil { + // print before panic, as panic may get lost due to conflicting panic + // (all goroutines asleep) elsewhere, since we might not unlock + // the mutex in freeConn here. + println("double free of conn. conflicts are:\nA) " + freedFrom[dbConn{db, c}] + "\n\nand\nB) " + stack()) + panic("double free of conn.") } freedFrom[dbConn{db, c}] = stack() } @@ -38,15 +39,7 @@ const fakeDBName = "foo" var chrisBirthday = time.Unix(123456789, 0) -type testOrBench interface { - Fatalf(string, ...interface{}) - Errorf(string, ...interface{}) - Fatal(...interface{}) - Error(...interface{}) - Logf(string, ...interface{}) -} - -func newTestDB(t testOrBench, name string) *DB { +func newTestDB(t testing.TB, name string) *DB { db, err := Open("test", fakeDBName) if err != nil { t.Fatalf("Open: %v", err) @@ -68,14 +61,14 @@ func newTestDB(t testOrBench, name string) *DB { return db } -func exec(t testOrBench, db *DB, query string, args ...interface{}) { +func exec(t testing.TB, db *DB, query string, args ...interface{}) { _, err := db.Exec(query, args...) if err != nil { t.Fatalf("Exec of %q: %v", query, err) } } -func closeDB(t testOrBench, db *DB) { +func closeDB(t testing.TB, db *DB) { if e := recover(); e != nil { fmt.Printf("Panic: %v\n", e) panic(e) @@ -86,29 +79,36 @@ func closeDB(t testOrBench, db *DB) { t.Errorf("Error closing fakeConn: %v", err) } }) - for i, dc := range db.freeConn { + for node, i := db.freeConn.Front(), 0; node != nil; node, i = node.Next(), i+1 { + dc := node.Value.(*driverConn) if n := len(dc.openStmt); n > 0 { // Just a sanity check. This is legal in // general, but if we make the tests clean up // their statements first, then we can safely // verify this is always zero here, and any // other value is a leak. - t.Errorf("while closing db, freeConn %d/%d had %d open stmts; want 0", i, len(db.freeConn), n) + t.Errorf("while closing db, freeConn %d/%d had %d open stmts; want 0", i, db.freeConn.Len(), n) } } err := db.Close() if err != nil { t.Fatalf("error closing DB: %v", err) } + db.mu.Lock() + count := db.numOpen + db.mu.Unlock() + if count != 0 { + t.Fatalf("%d connections still open after closing DB", db.numOpen) + } } // numPrepares assumes that db has exactly 1 idle conn and returns // its count of calls to Prepare func numPrepares(t *testing.T, db *DB) int { - if n := len(db.freeConn); n != 1 { + if n := db.freeConn.Len(); n != 1 { t.Fatalf("free conns = %d; want 1", n) } - return db.freeConn[0].ci.(*fakeConn).numPrepare + return (db.freeConn.Front().Value.(*driverConn)).ci.(*fakeConn).numPrepare } func (db *DB) numDeps() int { @@ -133,7 +133,7 @@ func (db *DB) numDepsPollUntil(want int, d time.Duration) int { func (db *DB) numFreeConns() int { db.mu.Lock() defer db.mu.Unlock() - return len(db.freeConn) + return db.freeConn.Len() } func (db *DB) dumpDeps(t *testing.T) { @@ -252,6 +252,9 @@ func TestRowsColumns(t *testing.T) { if !reflect.DeepEqual(cols, want) { t.Errorf("got %#v; want %#v", cols, want) } + if err := rows.Close(); err != nil { + t.Errorf("error closing rows: %s", err) + } } func TestQueryRow(t *testing.T) { @@ -648,10 +651,10 @@ func TestQueryRowClosingStmt(t *testing.T) { if err != nil { t.Fatal(err) } - if len(db.freeConn) != 1 { + if db.freeConn.Len() != 1 { t.Fatalf("expected 1 free conn") } - fakeConn := db.freeConn[0].ci.(*fakeConn) + fakeConn := (db.freeConn.Front().Value.(*driverConn)).ci.(*fakeConn) if made, closed := fakeConn.stmtsMade, fakeConn.stmtsClosed; made != closed { t.Errorf("statement close mismatch: made %d, closed %d", made, closed) } @@ -847,13 +850,13 @@ func TestMaxIdleConns(t *testing.T) { t.Fatal(err) } tx.Commit() - if got := len(db.freeConn); got != 1 { + if got := db.freeConn.Len(); got != 1 { t.Errorf("freeConns = %d; want 1", got) } db.SetMaxIdleConns(0) - if got := len(db.freeConn); got != 0 { + if got := db.freeConn.Len(); got != 0 { t.Errorf("freeConns after set to zero = %d; want 0", got) } @@ -862,11 +865,146 @@ func TestMaxIdleConns(t *testing.T) { t.Fatal(err) } tx.Commit() - if got := len(db.freeConn); got != 0 { + if got := db.freeConn.Len(); got != 0 { t.Errorf("freeConns = %d; want 0", got) } } +func TestMaxOpenConns(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + defer setHookpostCloseConn(nil) + setHookpostCloseConn(func(_ *fakeConn, err error) { + if err != nil { + t.Errorf("Error closing fakeConn: %v", err) + } + }) + + db := newTestDB(t, "magicquery") + defer closeDB(t, db) + + driver := db.driver.(*fakeDriver) + + // Force the number of open connections to 0 so we can get an accurate + // count for the test + db.SetMaxIdleConns(0) + + if g, w := db.numFreeConns(), 0; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } + + if n := db.numDepsPollUntil(0, time.Second); n > 0 { + t.Errorf("number of dependencies = %d; expected 0", n) + db.dumpDeps(t) + } + + driver.mu.Lock() + opens0 := driver.openCount + closes0 := driver.closeCount + driver.mu.Unlock() + + db.SetMaxIdleConns(10) + db.SetMaxOpenConns(10) + + stmt, err := db.Prepare("SELECT|magicquery|op|op=?,millis=?") + if err != nil { + t.Fatal(err) + } + + // Start 50 parallel slow queries. + const ( + nquery = 50 + sleepMillis = 25 + nbatch = 2 + ) + var wg sync.WaitGroup + for batch := 0; batch < nbatch; batch++ { + for i := 0; i < nquery; i++ { + wg.Add(1) + go func() { + defer wg.Done() + var op string + if err := stmt.QueryRow("sleep", sleepMillis).Scan(&op); err != nil && err != ErrNoRows { + t.Error(err) + } + }() + } + // Sleep for twice the expected length of time for the + // batch of 50 queries above to finish before starting + // the next round. + time.Sleep(2 * sleepMillis * time.Millisecond) + } + wg.Wait() + + if g, w := db.numFreeConns(), 10; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } + + if n := db.numDepsPollUntil(20, time.Second); n > 20 { + t.Errorf("number of dependencies = %d; expected <= 20", n) + db.dumpDeps(t) + } + + driver.mu.Lock() + opens := driver.openCount - opens0 + closes := driver.closeCount - closes0 + driver.mu.Unlock() + + if opens > 10 { + t.Logf("open calls = %d", opens) + t.Logf("close calls = %d", closes) + t.Errorf("db connections opened = %d; want <= 10", opens) + db.dumpDeps(t) + } + + if err := stmt.Close(); err != nil { + t.Fatal(err) + } + + if g, w := db.numFreeConns(), 10; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } + + if n := db.numDepsPollUntil(10, time.Second); n > 10 { + t.Errorf("number of dependencies = %d; expected <= 10", n) + db.dumpDeps(t) + } + + db.SetMaxOpenConns(5) + + if g, w := db.numFreeConns(), 5; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } + + if n := db.numDepsPollUntil(5, time.Second); n > 5 { + t.Errorf("number of dependencies = %d; expected 0", n) + db.dumpDeps(t) + } + + db.SetMaxOpenConns(0) + + if g, w := db.numFreeConns(), 5; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } + + if n := db.numDepsPollUntil(5, time.Second); n > 5 { + t.Errorf("number of dependencies = %d; expected 0", n) + db.dumpDeps(t) + } + + db.SetMaxIdleConns(0) + + if g, w := db.numFreeConns(), 0; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } + + if n := db.numDepsPollUntil(0, time.Second); n > 0 { + t.Errorf("number of dependencies = %d; expected 0", n) + db.dumpDeps(t) + } +} + // golang.org/issue/5323 func TestStmtCloseDeps(t *testing.T) { if testing.Short() { @@ -932,8 +1070,8 @@ func TestStmtCloseDeps(t *testing.T) { driver.mu.Lock() opens := driver.openCount - opens0 closes := driver.closeCount - closes0 - driver.mu.Unlock() openDelta := (driver.openCount - driver.closeCount) - openDelta0 + driver.mu.Unlock() if openDelta > 2 { t.Logf("open calls = %d", opens) @@ -991,10 +1129,10 @@ func TestCloseConnBeforeStmts(t *testing.T) { t.Fatal(err) } - if len(db.freeConn) != 1 { - t.Fatalf("expected 1 freeConn; got %d", len(db.freeConn)) + if db.freeConn.Len() != 1 { + t.Fatalf("expected 1 freeConn; got %d", db.freeConn.Len()) } - dc := db.freeConn[0] + dc := db.freeConn.Front().Value.(*driverConn) if dc.closed { t.Errorf("conn shouldn't be closed") } @@ -1046,7 +1184,393 @@ func TestRowsCloseOrder(t *testing.T) { } } -func manyConcurrentQueries(t testOrBench) { +func TestRowsImplicitClose(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + rows, err := db.Query("SELECT|people|age,name|") + if err != nil { + t.Fatal(err) + } + + want, fail := 2, errors.New("fail") + r := rows.rowsi.(*rowsCursor) + r.errPos, r.err = want, fail + + got := 0 + for rows.Next() { + got++ + } + if got != want { + t.Errorf("got %d rows, want %d", got, want) + } + if err := rows.Err(); err != fail { + t.Errorf("got error %v, want %v", err, fail) + } + if !r.closed { + t.Errorf("r.closed is false, want true") + } +} + +func TestStmtCloseOrder(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + db.SetMaxIdleConns(0) + setStrictFakeConnClose(t) + defer setStrictFakeConnClose(nil) + + _, err := db.Query("SELECT|non_existent|name|") + if err == nil { + t.Fatal("Quering non-existent table should fail") + } +} + +type concurrentTest interface { + init(t testing.TB, db *DB) + finish(t testing.TB) + test(t testing.TB) error +} + +type concurrentDBQueryTest struct { + db *DB +} + +func (c *concurrentDBQueryTest) init(t testing.TB, db *DB) { + c.db = db +} + +func (c *concurrentDBQueryTest) finish(t testing.TB) { + c.db = nil +} + +func (c *concurrentDBQueryTest) test(t testing.TB) error { + rows, err := c.db.Query("SELECT|people|name|") + if err != nil { + t.Error(err) + return err + } + var name string + for rows.Next() { + rows.Scan(&name) + } + rows.Close() + return nil +} + +type concurrentDBExecTest struct { + db *DB +} + +func (c *concurrentDBExecTest) init(t testing.TB, db *DB) { + c.db = db +} + +func (c *concurrentDBExecTest) finish(t testing.TB) { + c.db = nil +} + +func (c *concurrentDBExecTest) test(t testing.TB) error { + _, err := c.db.Exec("NOSERT|people|name=Chris,age=?,photo=CPHOTO,bdate=?", 3, chrisBirthday) + if err != nil { + t.Error(err) + return err + } + return nil +} + +type concurrentStmtQueryTest struct { + db *DB + stmt *Stmt +} + +func (c *concurrentStmtQueryTest) init(t testing.TB, db *DB) { + c.db = db + var err error + c.stmt, err = db.Prepare("SELECT|people|name|") + if err != nil { + t.Fatal(err) + } +} + +func (c *concurrentStmtQueryTest) finish(t testing.TB) { + if c.stmt != nil { + c.stmt.Close() + c.stmt = nil + } + c.db = nil +} + +func (c *concurrentStmtQueryTest) test(t testing.TB) error { + rows, err := c.stmt.Query() + if err != nil { + t.Errorf("error on query: %v", err) + return err + } + + var name string + for rows.Next() { + rows.Scan(&name) + } + rows.Close() + return nil +} + +type concurrentStmtExecTest struct { + db *DB + stmt *Stmt +} + +func (c *concurrentStmtExecTest) init(t testing.TB, db *DB) { + c.db = db + var err error + c.stmt, err = db.Prepare("NOSERT|people|name=Chris,age=?,photo=CPHOTO,bdate=?") + if err != nil { + t.Fatal(err) + } +} + +func (c *concurrentStmtExecTest) finish(t testing.TB) { + if c.stmt != nil { + c.stmt.Close() + c.stmt = nil + } + c.db = nil +} + +func (c *concurrentStmtExecTest) test(t testing.TB) error { + _, err := c.stmt.Exec(3, chrisBirthday) + if err != nil { + t.Errorf("error on exec: %v", err) + return err + } + return nil +} + +type concurrentTxQueryTest struct { + db *DB + tx *Tx +} + +func (c *concurrentTxQueryTest) init(t testing.TB, db *DB) { + c.db = db + var err error + c.tx, err = c.db.Begin() + if err != nil { + t.Fatal(err) + } +} + +func (c *concurrentTxQueryTest) finish(t testing.TB) { + if c.tx != nil { + c.tx.Rollback() + c.tx = nil + } + c.db = nil +} + +func (c *concurrentTxQueryTest) test(t testing.TB) error { + rows, err := c.db.Query("SELECT|people|name|") + if err != nil { + t.Error(err) + return err + } + var name string + for rows.Next() { + rows.Scan(&name) + } + rows.Close() + return nil +} + +type concurrentTxExecTest struct { + db *DB + tx *Tx +} + +func (c *concurrentTxExecTest) init(t testing.TB, db *DB) { + c.db = db + var err error + c.tx, err = c.db.Begin() + if err != nil { + t.Fatal(err) + } +} + +func (c *concurrentTxExecTest) finish(t testing.TB) { + if c.tx != nil { + c.tx.Rollback() + c.tx = nil + } + c.db = nil +} + +func (c *concurrentTxExecTest) test(t testing.TB) error { + _, err := c.tx.Exec("NOSERT|people|name=Chris,age=?,photo=CPHOTO,bdate=?", 3, chrisBirthday) + if err != nil { + t.Error(err) + return err + } + return nil +} + +type concurrentTxStmtQueryTest struct { + db *DB + tx *Tx + stmt *Stmt +} + +func (c *concurrentTxStmtQueryTest) init(t testing.TB, db *DB) { + c.db = db + var err error + c.tx, err = c.db.Begin() + if err != nil { + t.Fatal(err) + } + c.stmt, err = c.tx.Prepare("SELECT|people|name|") + if err != nil { + t.Fatal(err) + } +} + +func (c *concurrentTxStmtQueryTest) finish(t testing.TB) { + if c.stmt != nil { + c.stmt.Close() + c.stmt = nil + } + if c.tx != nil { + c.tx.Rollback() + c.tx = nil + } + c.db = nil +} + +func (c *concurrentTxStmtQueryTest) test(t testing.TB) error { + rows, err := c.stmt.Query() + if err != nil { + t.Errorf("error on query: %v", err) + return err + } + + var name string + for rows.Next() { + rows.Scan(&name) + } + rows.Close() + return nil +} + +type concurrentTxStmtExecTest struct { + db *DB + tx *Tx + stmt *Stmt +} + +func (c *concurrentTxStmtExecTest) init(t testing.TB, db *DB) { + c.db = db + var err error + c.tx, err = c.db.Begin() + if err != nil { + t.Fatal(err) + } + c.stmt, err = c.tx.Prepare("NOSERT|people|name=Chris,age=?,photo=CPHOTO,bdate=?") + if err != nil { + t.Fatal(err) + } +} + +func (c *concurrentTxStmtExecTest) finish(t testing.TB) { + if c.stmt != nil { + c.stmt.Close() + c.stmt = nil + } + if c.tx != nil { + c.tx.Rollback() + c.tx = nil + } + c.db = nil +} + +func (c *concurrentTxStmtExecTest) test(t testing.TB) error { + _, err := c.stmt.Exec(3, chrisBirthday) + if err != nil { + t.Errorf("error on exec: %v", err) + return err + } + return nil +} + +type concurrentRandomTest struct { + tests []concurrentTest +} + +func (c *concurrentRandomTest) init(t testing.TB, db *DB) { + c.tests = []concurrentTest{ + new(concurrentDBQueryTest), + new(concurrentDBExecTest), + new(concurrentStmtQueryTest), + new(concurrentStmtExecTest), + new(concurrentTxQueryTest), + new(concurrentTxExecTest), + new(concurrentTxStmtQueryTest), + new(concurrentTxStmtExecTest), + } + for _, ct := range c.tests { + ct.init(t, db) + } +} + +func (c *concurrentRandomTest) finish(t testing.TB) { + for _, ct := range c.tests { + ct.finish(t) + } +} + +func (c *concurrentRandomTest) test(t testing.TB) error { + ct := c.tests[rand.Intn(len(c.tests))] + return ct.test(t) +} + +func doConcurrentTest(t testing.TB, ct concurrentTest) { + maxProcs, numReqs := 1, 500 + if testing.Short() { + maxProcs, numReqs = 4, 50 + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs)) + + db := newTestDB(t, "people") + defer closeDB(t, db) + + ct.init(t, db) + defer ct.finish(t) + + var wg sync.WaitGroup + wg.Add(numReqs) + + reqs := make(chan bool) + defer close(reqs) + + for i := 0; i < maxProcs*2; i++ { + go func() { + for _ = range reqs { + err := ct.test(t) + if err != nil { + wg.Done() + continue + } + wg.Done() + } + }() + } + + for i := 0; i < numReqs; i++ { + reqs <- true + } + + wg.Wait() +} + +func manyConcurrentQueries(t testing.TB) { maxProcs, numReqs := 16, 500 if testing.Short() { maxProcs, numReqs = 4, 50 @@ -1096,13 +1620,174 @@ func manyConcurrentQueries(t testOrBench) { wg.Wait() } +func TestIssue6081(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + drv := db.driver.(*fakeDriver) + drv.mu.Lock() + opens0 := drv.openCount + closes0 := drv.closeCount + drv.mu.Unlock() + + stmt, err := db.Prepare("SELECT|people|name|") + if err != nil { + t.Fatal(err) + } + rowsCloseHook = func(rows *Rows, err *error) { + *err = driver.ErrBadConn + } + defer func() { rowsCloseHook = nil }() + for i := 0; i < 10; i++ { + rows, err := stmt.Query() + if err != nil { + t.Fatal(err) + } + rows.Close() + } + if n := len(stmt.css); n > 1 { + t.Errorf("len(css slice) = %d; want <= 1", n) + } + stmt.Close() + if n := len(stmt.css); n != 0 { + t.Errorf("len(css slice) after Close = %d; want 0", n) + } + + drv.mu.Lock() + opens := drv.openCount - opens0 + closes := drv.closeCount - closes0 + drv.mu.Unlock() + if opens < 9 { + t.Errorf("opens = %d; want >= 9", opens) + } + if closes < 9 { + t.Errorf("closes = %d; want >= 9", closes) + } +} + func TestConcurrency(t *testing.T) { - manyConcurrentQueries(t) + doConcurrentTest(t, new(concurrentDBQueryTest)) + doConcurrentTest(t, new(concurrentDBExecTest)) + doConcurrentTest(t, new(concurrentStmtQueryTest)) + doConcurrentTest(t, new(concurrentStmtExecTest)) + doConcurrentTest(t, new(concurrentTxQueryTest)) + doConcurrentTest(t, new(concurrentTxExecTest)) + doConcurrentTest(t, new(concurrentTxStmtQueryTest)) + doConcurrentTest(t, new(concurrentTxStmtExecTest)) + 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) { + b.ReportAllocs() + ct := new(concurrentDBExecTest) + for i := 0; i < b.N; i++ { + doConcurrentTest(b, ct) + } +} + +func BenchmarkConcurrentStmtQuery(b *testing.B) { + b.ReportAllocs() + ct := new(concurrentStmtQueryTest) + for i := 0; i < b.N; i++ { + doConcurrentTest(b, ct) + } +} + +func BenchmarkConcurrentStmtExec(b *testing.B) { + b.ReportAllocs() + ct := new(concurrentStmtExecTest) + for i := 0; i < b.N; i++ { + doConcurrentTest(b, ct) + } +} + +func BenchmarkConcurrentTxQuery(b *testing.B) { + b.ReportAllocs() + ct := new(concurrentTxQueryTest) + for i := 0; i < b.N; i++ { + doConcurrentTest(b, ct) + } +} + +func BenchmarkConcurrentTxExec(b *testing.B) { + b.ReportAllocs() + ct := new(concurrentTxExecTest) + for i := 0; i < b.N; i++ { + doConcurrentTest(b, ct) + } +} + +func BenchmarkConcurrentTxStmtQuery(b *testing.B) { + b.ReportAllocs() + ct := new(concurrentTxStmtQueryTest) + for i := 0; i < b.N; i++ { + doConcurrentTest(b, ct) + } +} + +func BenchmarkConcurrentTxStmtExec(b *testing.B) { + b.ReportAllocs() + ct := new(concurrentTxStmtExecTest) + for i := 0; i < b.N; i++ { + doConcurrentTest(b, ct) + } } -func BenchmarkConcurrency(b *testing.B) { +func BenchmarkConcurrentRandom(b *testing.B) { b.ReportAllocs() + ct := new(concurrentRandomTest) for i := 0; i < b.N; i++ { - manyConcurrentQueries(b) + doConcurrentTest(b, ct) } } |