diff options
author | Ian Lance Taylor <ian@gcc.gnu.org> | 2015-10-31 00:59:47 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2015-10-31 00:59:47 +0000 |
commit | af146490bb04205107cb23e301ec7a8ff927b5fc (patch) | |
tree | 13beeaed3698c61903fe93fb1ce70bd9b18d4e7f /libgo/go/database/sql | |
parent | 725e1be3406315d9bcc8195d7eef0a7082b3c7cc (diff) | |
download | gcc-af146490bb04205107cb23e301ec7a8ff927b5fc.zip gcc-af146490bb04205107cb23e301ec7a8ff927b5fc.tar.gz gcc-af146490bb04205107cb23e301ec7a8ff927b5fc.tar.bz2 |
runtime: Remove now unnecessary pad field from ParFor.
It is not needed due to the removal of the ctx field.
Reviewed-on: https://go-review.googlesource.com/16525
From-SVN: r229616
Diffstat (limited to 'libgo/go/database/sql')
-rw-r--r-- | libgo/go/database/sql/fakedb_test.go | 23 | ||||
-rw-r--r-- | libgo/go/database/sql/sql.go | 241 | ||||
-rw-r--r-- | libgo/go/database/sql/sql_test.go | 226 |
3 files changed, 332 insertions, 158 deletions
diff --git a/libgo/go/database/sql/fakedb_test.go b/libgo/go/database/sql/fakedb_test.go index a993fd4..8cbbb29 100644 --- a/libgo/go/database/sql/fakedb_test.go +++ b/libgo/go/database/sql/fakedb_test.go @@ -89,7 +89,10 @@ type fakeConn struct { stmtsMade int stmtsClosed int numPrepare int - bad bool + + // bad connection tests; see isBad() + bad bool + stickyBad bool } func (c *fakeConn) incrStat(v *int) { @@ -243,13 +246,15 @@ func (db *fakeDB) columnType(table, column string) (typ string, ok bool) { } func (c *fakeConn) isBad() bool { - // if not simulating bad conn, do nothing - if !c.bad { + if c.stickyBad { + return true + } else if c.bad { + // alternate between bad conn and not bad conn + c.db.badConn = !c.db.badConn + return c.db.badConn + } else { return false } - // alternate between bad conn and not bad conn - c.db.badConn = !c.db.badConn - return c.db.badConn } func (c *fakeConn) Begin() (driver.Tx, error) { @@ -466,7 +471,7 @@ func (c *fakeConn) Prepare(query string) (driver.Stmt, error) { panic("nil c.db; conn = " + fmt.Sprintf("%#v", c)) } - if hookPrepareBadConn != nil && hookPrepareBadConn() { + if c.stickyBad || (hookPrepareBadConn != nil && hookPrepareBadConn()) { return nil, driver.ErrBadConn } @@ -529,7 +534,7 @@ func (s *fakeStmt) Exec(args []driver.Value) (driver.Result, error) { return nil, errClosed } - if hookExecBadConn != nil && hookExecBadConn() { + if s.c.stickyBad || (hookExecBadConn != nil && hookExecBadConn()) { return nil, driver.ErrBadConn } @@ -613,7 +618,7 @@ func (s *fakeStmt) Query(args []driver.Value) (driver.Rows, error) { return nil, errClosed } - if hookQueryBadConn != nil && hookQueryBadConn() { + if s.c.stickyBad || (hookQueryBadConn != nil && hookQueryBadConn()) { return nil, driver.ErrBadConn } diff --git a/libgo/go/database/sql/sql.go b/libgo/go/database/sql/sql.go index 6e6f246..aaa4ea2 100644 --- a/libgo/go/database/sql/sql.go +++ b/libgo/go/database/sql/sql.go @@ -6,10 +6,10 @@ // databases. // // The sql package must be used in conjunction with a database driver. -// See http://golang.org/s/sqldrivers for a list of drivers. +// See https://golang.org/s/sqldrivers for a list of drivers. // // For more usage examples, see the wiki page at -// http://golang.org/s/sqlwiki. +// https://golang.org/s/sqlwiki. package sql import ( @@ -20,14 +20,20 @@ import ( "runtime" "sort" "sync" + "sync/atomic" ) -var drivers = make(map[string]driver.Driver) +var ( + driversMu sync.Mutex + drivers = make(map[string]driver.Driver) +) // Register makes a database driver available by the provided name. // If Register is called twice with the same name or if driver is nil, // it panics. func Register(name string, driver driver.Driver) { + driversMu.Lock() + defer driversMu.Unlock() if driver == nil { panic("sql: Register driver is nil") } @@ -38,12 +44,16 @@ func Register(name string, driver driver.Driver) { } func unregisterAllDrivers() { + driversMu.Lock() + defer driversMu.Unlock() // For tests. drivers = make(map[string]driver.Driver) } // Drivers returns a sorted list of the names of the registered drivers. func Drivers() []string { + driversMu.Lock() + defer driversMu.Unlock() var list []string for name := range drivers { list = append(list, name) @@ -211,6 +221,10 @@ var ErrNoRows = errors.New("sql: no rows in result set") type DB struct { driver driver.Driver dsn string + // numClosed is an atomic counter which represents a total number of + // closed connections. Stmt.openStmt checks it before cleaning closed + // connections in Stmt.css. + numClosed uint64 mu sync.Mutex // protects following fields freeConn []*driverConn @@ -230,6 +244,18 @@ type DB struct { maxOpen int // <= 0 means unlimited } +// connReuseStrategy determines how (*DB).conn returns database connections. +type connReuseStrategy uint8 + +const ( + // alwaysNewConn forces a new connection to the database. + alwaysNewConn connReuseStrategy = iota + // cachedOrNewConn returns a cached connection, if available, else waits + // for one to become available (if MaxOpenConns has been reached) or + // creates a new database connection. + cachedOrNewConn +) + // driverConn wraps a driver.Conn with a mutex, to // be held during all calls into the Conn. (including any calls onto // interfaces returned via that Conn, such as calls on Tx, Stmt, @@ -246,7 +272,7 @@ type driverConn struct { // guarded by db.mu 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 + dbmuClosed bool // same as closed, but guarded by db.mu, for removeClosedStmtLocked } func (dc *driverConn) releaseConn(err error) { @@ -329,6 +355,7 @@ func (dc *driverConn) finalClose() error { dc.db.maybeOpenNewConnections() dc.db.mu.Unlock() + atomic.AddUint64(&dc.db.numClosed, 1) return err } @@ -427,7 +454,7 @@ var connectionRequestQueueSize = 1000000 // // Most users will open a database via a driver-specific connection // helper function that returns a *DB. No database drivers are included -// in the Go standard library. See http://golang.org/s/sqldrivers for +// in the Go standard library. See https://golang.org/s/sqldrivers for // a list of third-party drivers. // // Open may just validate its arguments without creating a connection @@ -439,7 +466,9 @@ var connectionRequestQueueSize = 1000000 // function should be called just once. It is rarely necessary to // close a DB. func Open(driverName, dataSourceName string) (*DB, error) { + driversMu.Lock() driveri, ok := drivers[driverName] + driversMu.Unlock() if !ok { return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName) } @@ -459,7 +488,7 @@ func (db *DB) Ping() error { // TODO(bradfitz): give drivers an optional hook to implement // this in a more efficient or more reliable way, if they // have one. - dc, err := db.conn() + dc, err := db.conn(cachedOrNewConn) if err != nil { return err } @@ -566,6 +595,22 @@ func (db *DB) SetMaxOpenConns(n int) { } } +// DBStats contains database statistics. +type DBStats struct { + // OpenConnections is the number of open connections to the database. + OpenConnections int +} + +// Stats returns database statistics. +func (db *DB) Stats() DBStats { + db.mu.Lock() + stats := DBStats{ + OpenConnections: db.numOpen, + } + db.mu.Unlock() + return stats +} + // 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. @@ -629,36 +674,37 @@ type connRequest struct { var errDBClosed = errors.New("sql: database is closed") -// conn returns a newly-opened or cached *driverConn -func (db *DB) conn() (*driverConn, error) { +// conn returns a newly-opened or cached *driverConn. +func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) { db.mu.Lock() if db.closed { db.mu.Unlock() return nil, errDBClosed } - // If db.maxOpen > 0 and the number of open connections is over the limit - // and there are no free connection, make a request and wait. - if db.maxOpen > 0 && db.numOpen >= db.maxOpen && len(db.freeConn) == 0 { + // Prefer a free connection, if possible. + numFree := len(db.freeConn) + if strategy == cachedOrNewConn && numFree > 0 { + conn := db.freeConn[0] + copy(db.freeConn, db.freeConn[1:]) + db.freeConn = db.freeConn[:numFree-1] + conn.inUse = true + db.mu.Unlock() + return conn, nil + } + + // Out of free connections or we were asked not to use one. If we're not + // allowed to open any more connections, make a request and wait. + if db.maxOpen > 0 && db.numOpen >= db.maxOpen { // Make the connRequest channel. It's buffered so that the // connectionOpener doesn't block while waiting for the req to be read. req := make(chan connRequest, 1) db.connRequests = append(db.connRequests, req) - db.maybeOpenNewConnections() db.mu.Unlock() ret := <-req 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] - conn.inUse = true - db.mu.Unlock() - return conn, nil - } - db.numOpen++ // optimistically db.mu.Unlock() ci, err := db.driver.Open(db.dsn) @@ -684,42 +730,6 @@ var ( errConnBusy = errors.New("database/sql: internal sentinel error: conn is busy") ) -// connIfFree returns (wanted, nil) if wanted is still a valid conn and -// isn't in use. -// -// The error is errConnClosed if the connection if the requested connection -// is invalid because it's been closed. -// -// The error is errConnBusy if the connection is in use. -func (db *DB) connIfFree(wanted *driverConn) (*driverConn, error) { - db.mu.Lock() - defer db.mu.Unlock() - if wanted.dbmuClosed { - return nil, errConnClosed - } - if wanted.inUse { - return nil, errConnBusy - } - idx := -1 - for ii, v := range db.freeConn { - if v == wanted { - idx = ii - break - } - } - if idx >= 0 { - db.freeConn = append(db.freeConn[:idx], db.freeConn[idx+1:]...) - wanted.inUse = true - return wanted, nil - } - // TODO(bradfitz): shouldn't get here. After Go 1.1, change this to: - // panic("connIfFree call requested a non-closed, non-busy, non-free conn") - // Which passes all the tests, but I'm too paranoid to include this - // late in Go 1.1. - // Instead, treat it like a busy connection: - return nil, errConnBusy -} - // putConnHook is a hook for testing. var putConnHook func(*DB, *driverConn) @@ -797,6 +807,9 @@ func (db *DB) putConn(dc *driverConn, err error) { // If a connRequest was fulfilled 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.maxOpen > 0 && db.numOpen > db.maxOpen { + return false + } if c := len(db.connRequests); c > 0 { req := db.connRequests[0] // This copy is O(n) but in practice faster than a linked list. @@ -820,32 +833,38 @@ func (db *DB) putConnDBLocked(dc *driverConn, err error) bool { } // maxBadConnRetries is the number of maximum retries if the driver returns -// driver.ErrBadConn to signal a broken connection. -const maxBadConnRetries = 10 +// driver.ErrBadConn to signal a broken connection before forcing a new +// connection to be opened. +const maxBadConnRetries = 2 // Prepare creates a prepared statement for later queries or executions. // Multiple queries or executions may be run concurrently from the // returned statement. +// The caller must call the statement's Close method +// when the statement is no longer needed. func (db *DB) Prepare(query string) (*Stmt, error) { var stmt *Stmt var err error for i := 0; i < maxBadConnRetries; i++ { - stmt, err = db.prepare(query) + stmt, err = db.prepare(query, cachedOrNewConn) if err != driver.ErrBadConn { break } } + if err == driver.ErrBadConn { + return db.prepare(query, alwaysNewConn) + } return stmt, err } -func (db *DB) prepare(query string) (*Stmt, error) { +func (db *DB) prepare(query string, strategy connReuseStrategy) (*Stmt, error) { // TODO: check if db.driver supports an optional // driver.Preparer interface and call that instead, if so, // otherwise we make a prepared statement that's bound // to a connection, and to execute this prepared statement // we either need to use this connection (if it's free), else // get a new connection + re-prepare + execute on that one. - dc, err := db.conn() + dc, err := db.conn(strategy) if err != nil { return nil, err } @@ -857,9 +876,10 @@ func (db *DB) prepare(query string) (*Stmt, error) { return nil, err } stmt := &Stmt{ - db: db, - query: query, - css: []connStmt{{dc, si}}, + db: db, + query: query, + css: []connStmt{{dc, si}}, + lastNumClosed: atomic.LoadUint64(&db.numClosed), } db.addDep(stmt, stmt) db.putConn(dc, nil) @@ -872,16 +892,19 @@ func (db *DB) Exec(query string, args ...interface{}) (Result, error) { var res Result var err error for i := 0; i < maxBadConnRetries; i++ { - res, err = db.exec(query, args) + res, err = db.exec(query, args, cachedOrNewConn) if err != driver.ErrBadConn { break } } + if err == driver.ErrBadConn { + return db.exec(query, args, alwaysNewConn) + } return res, err } -func (db *DB) exec(query string, args []interface{}) (res Result, err error) { - dc, err := db.conn() +func (db *DB) exec(query string, args []interface{}, strategy connReuseStrategy) (res Result, err error) { + dc, err := db.conn(strategy) if err != nil { return nil, err } @@ -921,16 +944,19 @@ func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { var rows *Rows var err error for i := 0; i < maxBadConnRetries; i++ { - rows, err = db.query(query, args) + rows, err = db.query(query, args, cachedOrNewConn) if err != driver.ErrBadConn { break } } + if err == driver.ErrBadConn { + return db.query(query, args, alwaysNewConn) + } return rows, err } -func (db *DB) query(query string, args []interface{}) (*Rows, error) { - ci, err := db.conn() +func (db *DB) query(query string, args []interface{}, strategy connReuseStrategy) (*Rows, error) { + ci, err := db.conn(strategy) if err != nil { return nil, err } @@ -1009,16 +1035,19 @@ func (db *DB) Begin() (*Tx, error) { var tx *Tx var err error for i := 0; i < maxBadConnRetries; i++ { - tx, err = db.begin() + tx, err = db.begin(cachedOrNewConn) if err != driver.ErrBadConn { break } } + if err == driver.ErrBadConn { + return db.begin(alwaysNewConn) + } return tx, err } -func (db *DB) begin() (tx *Tx, err error) { - dc, err := db.conn() +func (db *DB) begin(strategy connReuseStrategy) (tx *Tx, err error) { + dc, err := db.conn(strategy) if err != nil { return nil, err } @@ -1047,6 +1076,10 @@ func (db *DB) Driver() driver.Driver { // // After a call to Commit or Rollback, all operations on the // transaction fail with ErrTxDone. +// +// The statements prepared for a transaction by calling +// the transaction's Prepare or Stmt methods are closed +// by the call to Commit or Rollback. type Tx struct { db *DB @@ -1182,6 +1215,9 @@ func (tx *Tx) Prepare(query string) (*Stmt, error) { // tx, err := db.Begin() // ... // res, err := tx.Stmt(updateMoney).Exec(123.45, 98293203) +// +// The returned statement operates within the transaction and can no longer +// be used once the transaction has been committed or rolled back. func (tx *Tx) Stmt(stmt *Stmt) *Stmt { // TODO(bradfitz): optimize this. Currently this re-prepares // each time. This is fine for now to illustrate the API but @@ -1273,7 +1309,8 @@ type connStmt struct { si driver.Stmt } -// Stmt is a prepared statement. Stmt is safe for concurrent use by multiple goroutines. +// Stmt is a prepared statement. +// A Stmt is safe for concurrent use by multiple goroutines. type Stmt struct { // Immutable: db *DB // where we came from @@ -1294,6 +1331,10 @@ type Stmt struct { // used if tx == nil and one is found that has idle // connections. If tx != nil, txsi is always used. css []connStmt + + // lastNumClosed is copied from db.numClosed when Stmt is created + // without tx and closed connections in css are removed. + lastNumClosed uint64 } // Exec executes a prepared statement with the given arguments and @@ -1347,6 +1388,32 @@ func resultFromStatement(ds driverStmt, args ...interface{}) (Result, error) { return driverResult{ds.Locker, resi}, nil } +// removeClosedStmtLocked removes closed conns in s.css. +// +// To avoid lock contention on DB.mu, we do it only when +// s.db.numClosed - s.lastNum is large enough. +func (s *Stmt) removeClosedStmtLocked() { + t := len(s.css)/2 + 1 + if t > 10 { + t = 10 + } + dbClosed := atomic.LoadUint64(&s.db.numClosed) + if dbClosed-s.lastNumClosed < uint64(t) { + return + } + + s.db.mu.Lock() + for i := 0; i < len(s.css); i++ { + if s.css[i].dc.dbmuClosed { + s.css[i] = s.css[len(s.css)-1] + s.css = s.css[:len(s.css)-1] + i-- + } + } + s.db.mu.Unlock() + s.lastNumClosed = dbClosed +} + // connStmt returns a free driver connection on which to execute the // statement, a function to call to release the connection, and a // statement bound to that connection. @@ -1373,35 +1440,15 @@ func (s *Stmt) connStmt() (ci *driverConn, releaseConn func(error), si driver.St return ci, releaseConn, s.txsi.si, nil } - for i := 0; i < len(s.css); i++ { - v := s.css[i] - _, err := s.db.connIfFree(v.dc) - if err == nil { - s.mu.Unlock() - return v.dc, v.dc.releaseConn, v.si, nil - } - if err == errConnClosed { - // Lazily remove dead conn from our freelist. - s.css[i] = s.css[len(s.css)-1] - s.css = s.css[:len(s.css)-1] - i-- - } - - } + s.removeClosedStmtLocked() s.mu.Unlock() - // If all connections are busy, either wait for one to become available (if - // we've already hit the maximum number of open connections) or create a - // new one. - // // TODO(bradfitz): or always wait for one? make configurable later? - dc, err := s.db.conn() + dc, err := s.db.conn(cachedOrNewConn) if err != nil { return nil, nil, nil, err } - // Do another pass over the list to see whether this statement has - // already been prepared on the connection assigned to us. s.mu.Lock() for _, v := range s.css { if v.dc == dc { diff --git a/libgo/go/database/sql/sql_test.go b/libgo/go/database/sql/sql_test.go index 34efdf2..432a641 100644 --- a/libgo/go/database/sql/sql_test.go +++ b/libgo/go/database/sql/sql_test.go @@ -497,7 +497,7 @@ func TestTxStmt(t *testing.T) { } } -// Issue: http://golang.org/issue/2784 +// Issue: https://golang.org/issue/2784 // This test didn't fail before because we got lucky with the fakedb driver. // It was failing, and now not, in github.com/bradfitz/go-sql-test func TestTxQuery(t *testing.T) { @@ -1070,6 +1070,57 @@ func TestMaxOpenConns(t *testing.T) { } } +// Issue 9453: tests that SetMaxOpenConns can be lowered at runtime +// and affects the subsequent release of connections. +func TestMaxOpenConnsOnBusy(t *testing.T) { + 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) + + db.SetMaxOpenConns(3) + + conn0, err := db.conn(cachedOrNewConn) + if err != nil { + t.Fatalf("db open conn fail: %v", err) + } + + conn1, err := db.conn(cachedOrNewConn) + if err != nil { + t.Fatalf("db open conn fail: %v", err) + } + + conn2, err := db.conn(cachedOrNewConn) + if err != nil { + t.Fatalf("db open conn fail: %v", err) + } + + if g, w := db.numOpen, 3; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } + + db.SetMaxOpenConns(2) + if g, w := db.numOpen, 3; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } + + conn0.releaseConn(nil) + conn1.releaseConn(nil) + if g, w := db.numOpen, 2; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } + + conn2.releaseConn(nil) + if g, w := db.numOpen, 2; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } +} + func TestSingleOpenConn(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) @@ -1093,6 +1144,26 @@ func TestSingleOpenConn(t *testing.T) { } } +func TestStats(t *testing.T) { + db := newTestDB(t, "people") + stats := db.Stats() + if got := stats.OpenConnections; got != 1 { + t.Errorf("stats.OpenConnections = %d; want 1", got) + } + + tx, err := db.Begin() + if err != nil { + t.Fatal(err) + } + tx.Commit() + + closeDB(t, db) + stats = db.Stats() + if got := stats.OpenConnections; got != 0 { + t.Errorf("stats.OpenConnections = %d; want 0", got) + } +} + // golang.org/issue/5323 func TestStmtCloseDeps(t *testing.T) { if testing.Short() { @@ -1314,7 +1385,80 @@ func TestStmtCloseOrder(t *testing.T) { } } -// golang.org/issue/5781 +// Test cases where there's more than maxBadConnRetries bad connections in the +// pool (issue 8834) +func TestManyErrBadConn(t *testing.T) { + manyErrBadConnSetup := func() *DB { + db := newTestDB(t, "people") + + nconn := maxBadConnRetries + 1 + db.SetMaxIdleConns(nconn) + db.SetMaxOpenConns(nconn) + // open enough connections + func() { + for i := 0; i < nconn; i++ { + rows, err := db.Query("SELECT|people|age,name|") + if err != nil { + t.Fatal(err) + } + defer rows.Close() + } + }() + + if db.numOpen != nconn { + t.Fatalf("unexpected numOpen %d (was expecting %d)", db.numOpen, nconn) + } else if len(db.freeConn) != nconn { + t.Fatalf("unexpected len(db.freeConn) %d (was expecting %d)", len(db.freeConn), nconn) + } + for _, conn := range db.freeConn { + conn.ci.(*fakeConn).stickyBad = true + } + return db + } + + // Query + db := manyErrBadConnSetup() + defer closeDB(t, db) + rows, err := db.Query("SELECT|people|age,name|") + if err != nil { + t.Fatal(err) + } + if err = rows.Close(); err != nil { + t.Fatal(err) + } + + // Exec + db = manyErrBadConnSetup() + defer closeDB(t, db) + _, err = db.Exec("INSERT|people|name=Julia,age=19") + if err != nil { + t.Fatal(err) + } + + // Begin + db = manyErrBadConnSetup() + defer closeDB(t, db) + tx, err := db.Begin() + if err != nil { + t.Fatal(err) + } + if err = tx.Rollback(); err != nil { + t.Fatal(err) + } + + // Prepare + db = manyErrBadConnSetup() + defer closeDB(t, db) + stmt, err := db.Prepare("SELECT|people|age,name|") + if err != nil { + t.Fatal(err) + } + if err = stmt.Close(); err != nil { + t.Fatal(err) + } +} + +// golang.org/issue/5718 func TestErrBadConnReconnect(t *testing.T) { db := newTestDB(t, "foo") defer closeDB(t, db) @@ -1764,56 +1908,6 @@ func doConcurrentTest(t testing.TB, ct concurrentTest) { wg.Wait() } -func manyConcurrentQueries(t testing.TB) { - maxProcs, numReqs := 16, 500 - if testing.Short() { - maxProcs, numReqs = 4, 50 - } - defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs)) - - db := newTestDB(t, "people") - defer closeDB(t, db) - - stmt, err := db.Prepare("SELECT|people|name|") - if err != nil { - t.Fatal(err) - } - defer stmt.Close() - - 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 { - rows, err := stmt.Query() - if err != nil { - t.Errorf("error on query: %v", err) - wg.Done() - continue - } - - var name string - for rows.Next() { - rows.Scan(&name) - } - rows.Close() - - wg.Done() - } - }() - } - - for i := 0; i < numReqs; i++ { - reqs <- true - } - - wg.Wait() -} - func TestIssue6081(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) @@ -1985,3 +2079,31 @@ func BenchmarkConcurrentRandom(b *testing.B) { doConcurrentTest(b, ct) } } + +func BenchmarkManyConcurrentQueries(b *testing.B) { + b.ReportAllocs() + // To see lock contention in Go 1.4, 16~ cores and 128~ goroutines are required. + const parallelism = 16 + + db := newTestDB(b, "magicquery") + defer closeDB(b, db) + db.SetMaxIdleConns(runtime.GOMAXPROCS(0) * parallelism) + + stmt, err := db.Prepare("SELECT|magicquery|op|op=?,millis=?") + if err != nil { + b.Fatal(err) + } + defer stmt.Close() + + b.SetParallelism(parallelism) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + rows, err := stmt.Query("sleep", 1) + if err != nil { + b.Error(err) + return + } + rows.Close() + } + }) +} |