diff options
author | Ian Lance Taylor <iant@golang.org> | 2020-07-27 22:27:54 -0700 |
---|---|---|
committer | Ian Lance Taylor <iant@golang.org> | 2020-08-01 11:21:40 -0700 |
commit | f75af8c1464e948b5e166cf5ab09ebf0d82fc253 (patch) | |
tree | 3ba3299859b504bdeb477727471216bd094a0191 /libgo/go/database | |
parent | 75a23e59031fe673fc3b2e60fd1fe5f4c70ecb85 (diff) | |
download | gcc-f75af8c1464e948b5e166cf5ab09ebf0d82fc253.tar.gz |
libgo: update to go1.15rc1
Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/245157
Diffstat (limited to 'libgo/go/database')
-rw-r--r-- | libgo/go/database/sql/driver/driver.go | 52 | ||||
-rw-r--r-- | libgo/go/database/sql/fakedb_test.go | 2 | ||||
-rw-r--r-- | libgo/go/database/sql/sql.go | 164 | ||||
-rw-r--r-- | libgo/go/database/sql/sql_test.go | 79 |
4 files changed, 251 insertions, 46 deletions
diff --git a/libgo/go/database/sql/driver/driver.go b/libgo/go/database/sql/driver/driver.go index a0ba7ecf694..5bbcf20db2f 100644 --- a/libgo/go/database/sql/driver/driver.go +++ b/libgo/go/database/sql/driver/driver.go @@ -6,6 +6,35 @@ // drivers as used by package sql. // // Most code should use package sql. +// +// The driver interface has evolved over time. Drivers should implement +// Connector and DriverContext interfaces. +// The Connector.Connect and Driver.Open methods should never return ErrBadConn. +// ErrBadConn should only be returned from Validator, SessionResetter, or +// a query method if the connection is already in an invalid (e.g. closed) state. +// +// All Conn implementations should implement the following interfaces: +// Pinger, SessionResetter, and Validator. +// +// If named parameters or context are supported, the driver's Conn should implement: +// ExecerContext, QueryerContext, ConnPrepareContext, and ConnBeginTx. +// +// To support custom data types, implement NamedValueChecker. NamedValueChecker +// also allows queries to accept per-query options as a parameter by returning +// ErrRemoveArgument from CheckNamedValue. +// +// If multiple result sets are supported, Rows should implement RowsNextResultSet. +// If the driver knows how to describe the types present in the returned result +// it should implement the following interfaces: RowsColumnTypeScanType, +// RowsColumnTypeDatabaseTypeName, RowsColumnTypeLength, RowsColumnTypeNullable, +// and RowsColumnTypePrecisionScale. A given row value may also return a Rows +// type, which may represent a database cursor value. +// +// Before a connection is returned to the connection pool after use, IsValid is +// called if implemented. Before a connection is reused for another query, +// ResetSession is called if implemented. If a connection is never returned to the +// connection pool but immediately reused, then ResetSession is called prior to +// reuse but IsValid is not called. package driver import ( @@ -67,7 +96,7 @@ type Driver interface { // If a Driver implements DriverContext, then sql.DB will call // OpenConnector to obtain a Connector and then invoke -// that Connector's Conn method to obtain each needed connection, +// that Connector's Connect method to obtain each needed connection, // instead of invoking the Driver's Open method for each connection. // The two-step sequence allows drivers to parse the name just once // and also provides access to per-Conn contexts. @@ -94,7 +123,9 @@ type Connector interface { // // The provided context.Context is for dialing purposes only // (see net.DialContext) and should not be stored or used for - // other purposes. + // other purposes. A default timeout should still be used + // when dialing as a connection pool may call Connect + // asynchronously to any query. // // The returned connection is only used by one goroutine at a // time. @@ -205,6 +236,9 @@ type Conn interface { // connections and only calls Close when there's a surplus of // idle connections, it shouldn't be necessary for drivers to // do their own connection caching. + // + // Drivers must ensure all network calls made by Close + // do not block indefinitely (e.g. apply a timeout). Close() error // Begin starts and returns a new transaction. @@ -261,6 +295,17 @@ type SessionResetter interface { ResetSession(ctx context.Context) error } +// Validator may be implemented by Conn to allow drivers to +// signal if a connection is valid or if it should be discarded. +// +// If implemented, drivers may return the underlying error from queries, +// even if the connection should be discarded by the connection pool. +type Validator interface { + // IsValid is called prior to placing the connection into the + // connection pool. The connection will be discarded if false is returned. + IsValid() bool +} + // Result is the result of a query execution. type Result interface { // LastInsertId returns the database's auto-generated ID @@ -280,6 +325,9 @@ type Stmt interface { // // As of Go 1.1, a Stmt will not be closed if it's in use // by any queries. + // + // Drivers must ensure all network calls made by Close + // do not block indefinitely (e.g. apply a timeout). Close() error // NumInput returns the number of placeholder parameters. diff --git a/libgo/go/database/sql/fakedb_test.go b/libgo/go/database/sql/fakedb_test.go index 0ec72d409d3..7605a2a6d23 100644 --- a/libgo/go/database/sql/fakedb_test.go +++ b/libgo/go/database/sql/fakedb_test.go @@ -397,7 +397,7 @@ func (c *fakeConn) ResetSession(ctx context.Context) error { return nil } -var _ validator = (*fakeConn)(nil) +var _ driver.Validator = (*fakeConn)(nil) func (c *fakeConn) IsValid() bool { return !c.isBad() diff --git a/libgo/go/database/sql/sql.go b/libgo/go/database/sql/sql.go index a0b7ca8f087..b3d0653f5c6 100644 --- a/libgo/go/database/sql/sql.go +++ b/libgo/go/database/sql/sql.go @@ -424,13 +424,15 @@ type DB 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 + maxIdleCount int // zero means defaultMaxIdleConns; negative means 0 maxOpen int // <= 0 means unlimited maxLifetime time.Duration // maximum amount of time a connection may be reused + maxIdleTime time.Duration // maximum amount of time a connection may be idle before being closed cleanerCh chan struct{} waitCount int64 // Total number of connections waited for. - maxIdleClosed int64 // Total number of connections closed due to idle. - maxLifetimeClosed int64 // Total number of connections closed due to max free limit. + maxIdleClosed int64 // Total number of connections closed due to idle count. + maxIdleTimeClosed int64 // Total number of connections closed due to idle time. + maxLifetimeClosed int64 // Total number of connections closed due to max connection lifetime limit. stop func() // stop cancels the connection opener and the session resetter. } @@ -464,8 +466,9 @@ 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 removeClosedStmtLocked + returnedAt time.Time // Time the connection was created or returned. + onPut []func() // code (with db.mu held) run when conn is next returned + dbmuClosed bool // same as closed, but guarded by db.mu, for removeClosedStmtLocked } func (dc *driverConn) releaseConn(err error) { @@ -500,11 +503,6 @@ func (dc *driverConn) resetSession(ctx context.Context) error { return nil } -// validator was introduced for Go1.15, but backported to Go1.14. -type validator interface { - IsValid() bool -} - // validateConnection checks if the connection is valid and can // still be used. It also marks the session for reset if required. func (dc *driverConn) validateConnection(needsReset bool) bool { @@ -514,7 +512,7 @@ func (dc *driverConn) validateConnection(needsReset bool) bool { if needsReset { dc.needReset = true } - if cv, ok := dc.ci.(validator); ok { + if cv, ok := dc.ci.(driver.Validator); ok { return cv.IsValid() } return true @@ -858,7 +856,7 @@ func (db *DB) Close() error { const defaultMaxIdleConns = 2 func (db *DB) maxIdleConnsLocked() int { - n := db.maxIdle + n := db.maxIdleCount switch { case n == 0: // TODO(bradfitz): ask driver, if supported, for its default preference @@ -870,6 +868,14 @@ func (db *DB) maxIdleConnsLocked() int { } } +func (db *DB) shortestIdleTimeLocked() time.Duration { + min := db.maxIdleTime + if min > db.maxLifetime { + min = db.maxLifetime + } + return min +} + // SetMaxIdleConns sets the maximum number of connections in the idle // connection pool. // @@ -883,14 +889,14 @@ func (db *DB) maxIdleConnsLocked() int { func (db *DB) SetMaxIdleConns(n int) { db.mu.Lock() if n > 0 { - db.maxIdle = n + db.maxIdleCount = n } else { // No idle connections. - db.maxIdle = -1 + db.maxIdleCount = -1 } // Make sure maxIdle doesn't exceed maxOpen if db.maxOpen > 0 && db.maxIdleConnsLocked() > db.maxOpen { - db.maxIdle = db.maxOpen + db.maxIdleCount = db.maxOpen } var closing []*driverConn idleCount := len(db.freeConn) @@ -931,13 +937,13 @@ func (db *DB) SetMaxOpenConns(n int) { // // Expired connections may be closed lazily before reuse. // -// If d <= 0, connections are reused forever. +// If d <= 0, connections are not closed due to a connection's age. func (db *DB) SetConnMaxLifetime(d time.Duration) { if d < 0 { d = 0 } db.mu.Lock() - // wake cleaner up when lifetime is shortened. + // Wake cleaner up when lifetime is shortened. if d > 0 && d < db.maxLifetime && db.cleanerCh != nil { select { case db.cleanerCh <- struct{}{}: @@ -949,11 +955,34 @@ func (db *DB) SetConnMaxLifetime(d time.Duration) { db.mu.Unlock() } +// SetConnMaxIdleTime sets the maximum amount of time a connection may be idle. +// +// Expired connections may be closed lazily before reuse. +// +// If d <= 0, connections are not closed due to a connection's idle time. +func (db *DB) SetConnMaxIdleTime(d time.Duration) { + if d < 0 { + d = 0 + } + db.mu.Lock() + defer db.mu.Unlock() + + // Wake cleaner up when idle time is shortened. + if d > 0 && d < db.maxIdleTime && db.cleanerCh != nil { + select { + case db.cleanerCh <- struct{}{}: + default: + } + } + db.maxIdleTime = d + db.startCleanerLocked() +} + // startCleanerLocked starts connectionCleaner if needed. func (db *DB) startCleanerLocked() { - if db.maxLifetime > 0 && db.numOpen > 0 && db.cleanerCh == nil { + if (db.maxLifetime > 0 || db.maxIdleTime > 0) && db.numOpen > 0 && db.cleanerCh == nil { db.cleanerCh = make(chan struct{}, 1) - go db.connectionCleaner(db.maxLifetime) + go db.connectionCleaner(db.shortestIdleTimeLocked()) } } @@ -972,15 +1001,30 @@ func (db *DB) connectionCleaner(d time.Duration) { } db.mu.Lock() - d = db.maxLifetime + + d = db.shortestIdleTimeLocked() if db.closed || db.numOpen == 0 || d <= 0 { db.cleanerCh = nil db.mu.Unlock() return } - expiredSince := nowFunc().Add(-d) - var closing []*driverConn + closing := db.connectionCleanerRunLocked() + db.mu.Unlock() + for _, c := range closing { + c.Close() + } + + if d < minInterval { + d = minInterval + } + t.Reset(d) + } +} + +func (db *DB) connectionCleanerRunLocked() (closing []*driverConn) { + if db.maxLifetime > 0 { + expiredSince := nowFunc().Add(-db.maxLifetime) for i := 0; i < len(db.freeConn); i++ { c := db.freeConn[i] if c.createdAt.Before(expiredSince) { @@ -993,17 +1037,26 @@ func (db *DB) connectionCleaner(d time.Duration) { } } db.maxLifetimeClosed += int64(len(closing)) - db.mu.Unlock() - - for _, c := range closing { - c.Close() - } + } - if d < minInterval { - d = minInterval + if db.maxIdleTime > 0 { + expiredSince := nowFunc().Add(-db.maxIdleTime) + var expiredCount int64 + for i := 0; i < len(db.freeConn); i++ { + c := db.freeConn[i] + if db.maxIdleTime > 0 && c.returnedAt.Before(expiredSince) { + closing = append(closing, c) + expiredCount++ + last := len(db.freeConn) - 1 + db.freeConn[i] = db.freeConn[last] + db.freeConn[last] = nil + db.freeConn = db.freeConn[:last] + i-- + } } - t.Reset(d) + db.maxIdleTimeClosed += expiredCount } + return } // DBStats contains database statistics. @@ -1019,6 +1072,7 @@ type DBStats struct { WaitCount int64 // The total number of connections waited for. WaitDuration time.Duration // The total time blocked waiting for a new connection. MaxIdleClosed int64 // The total number of connections closed due to SetMaxIdleConns. + MaxIdleTimeClosed int64 // The total number of connections closed due to SetConnMaxIdleTime. MaxLifetimeClosed int64 // The total number of connections closed due to SetConnMaxLifetime. } @@ -1039,6 +1093,7 @@ func (db *DB) Stats() DBStats { WaitCount: db.waitCount, WaitDuration: time.Duration(wait), MaxIdleClosed: db.maxIdleClosed, + MaxIdleTimeClosed: db.maxIdleTimeClosed, MaxLifetimeClosed: db.maxLifetimeClosed, } return stats @@ -1099,9 +1154,10 @@ func (db *DB) openNewConnection(ctx context.Context) { return } dc := &driverConn{ - db: db, - createdAt: nowFunc(), - ci: ci, + db: db, + createdAt: nowFunc(), + returnedAt: nowFunc(), + ci: ci, } if db.putConnDBLocked(dc, err) { db.addDepLocked(dc, dc) @@ -1152,11 +1208,13 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn copy(db.freeConn, db.freeConn[1:]) db.freeConn = db.freeConn[:numFree-1] conn.inUse = true - db.mu.Unlock() if conn.expired(lifetime) { + db.maxLifetimeClosed++ + db.mu.Unlock() conn.Close() return nil, driver.ErrBadConn } + db.mu.Unlock() // Reset the session if required. if err := conn.resetSession(ctx); err == driver.ErrBadConn { @@ -1178,7 +1236,7 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn db.waitCount++ db.mu.Unlock() - waitStart := time.Now() + waitStart := nowFunc() // Timeout the connection request with the context. select { @@ -1212,6 +1270,9 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn // This prioritizes giving a valid connection to a client over the exact connection // lifetime, which could expire exactly after this point anyway. if strategy == cachedOrNewConn && ret.err == nil && ret.conn.expired(lifetime) { + db.mu.Lock() + db.maxLifetimeClosed++ + db.mu.Unlock() ret.conn.Close() return nil, driver.ErrBadConn } @@ -1240,10 +1301,11 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn } db.mu.Lock() dc := &driverConn{ - db: db, - createdAt: nowFunc(), - ci: ci, - inUse: true, + db: db, + createdAt: nowFunc(), + returnedAt: nowFunc(), + ci: ci, + inUse: true, } db.addDepLocked(dc, dc) db.mu.Unlock() @@ -1295,12 +1357,14 @@ func (db *DB) putConn(dc *driverConn, err error, resetSession bool) { } if err != driver.ErrBadConn && dc.expired(db.maxLifetime) { + db.maxLifetimeClosed++ err = driver.ErrBadConn } if debugGetPut { db.lastPut[dc] = stack() } dc.inUse = false + dc.returnedAt = nowFunc() for _, fn := range dc.onPut { fn() @@ -1691,7 +1755,7 @@ func (db *DB) beginDC(ctx context.Context, dc *driverConn, release func(error), keepConnOnRollback := false withLock(dc, func() { _, hasSessionResetter := dc.ci.(driver.SessionResetter) - _, hasConnectionValidator := dc.ci.(validator) + _, hasConnectionValidator := dc.ci.(driver.Validator) keepConnOnRollback = hasSessionResetter && hasConnectionValidator txi, err = ctxDriverBegin(ctx, opts, dc.ci) }) @@ -2729,10 +2793,17 @@ func (rs *Rows) lasterrOrErrLocked(err error) error { return err } +// bypassRowsAwaitDone is only used for testing. +// If true, it will not close the Rows automatically from the context. +var bypassRowsAwaitDone = false + func (rs *Rows) initContextClose(ctx, txctx context.Context) { if ctx.Done() == nil && (txctx == nil || txctx.Done() == nil) { return } + if bypassRowsAwaitDone { + return + } ctx, rs.cancel = context.WithCancel(ctx) go rs.awaitDone(ctx, txctx) } @@ -2942,10 +3013,11 @@ func (ci *ColumnType) Nullable() (nullable, ok bool) { } // DatabaseTypeName returns the database system name of the column type. If an empty -// string is returned the driver type name is not supported. +// string is returned, then the driver type name is not supported. // Consult your driver documentation for a list of driver data types. Length specifiers // are not included. -// Common type include "VARCHAR", "TEXT", "NVARCHAR", "DECIMAL", "BOOL", "INT", "BIGINT". +// Common type names include "VARCHAR", "TEXT", "NVARCHAR", "DECIMAL", "BOOL", +// "INT", and "BIGINT". func (ci *ColumnType) DatabaseTypeName() string { return ci.databaseType } @@ -3160,6 +3232,14 @@ func (r *Row) Scan(dest ...interface{}) error { return r.rows.Close() } +// Err provides a way for wrapping packages to check for +// query errors without calling Scan. +// Err returns the error, if any, that was encountered while running the query. +// If this error is not nil, this error will also be returned from Scan. +func (r *Row) Err() error { + return r.err +} + // A Result summarizes an executed SQL command. type Result interface { // LastInsertId returns the integer generated by the database diff --git a/libgo/go/database/sql/sql_test.go b/libgo/go/database/sql/sql_test.go index a9e18004fb4..5727f0d8aa6 100644 --- a/libgo/go/database/sql/sql_test.go +++ b/libgo/go/database/sql/sql_test.go @@ -823,6 +823,24 @@ func TestQueryRow(t *testing.T) { } } +func TestRowErr(t *testing.T) { + db := newTestDB(t, "people") + + err := db.QueryRowContext(context.Background(), "SELECT|people|bdate|age=?", 3).Err() + if err != nil { + t.Errorf("Unexpected err = %v; want %v", err, nil) + } + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + err = db.QueryRowContext(ctx, "SELECT|people|bdate|age=?", 3).Err() + exp := "context canceled" + if err == nil || !strings.Contains(err.Error(), exp) { + t.Errorf("Expected err = %v; got %v", exp, err) + } +} + func TestTxRollbackCommitErr(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) @@ -2724,7 +2742,7 @@ func TestManyErrBadConn(t *testing.T) { } } -// Issue 34755: Ensure that a Tx cannot commit after a rollback. +// Issue 34775: Ensure that a Tx cannot commit after a rollback. func TestTxCannotCommitAfterRollback(t *testing.T) { db := newTestDB(t, "tx_status") defer closeDB(t, db) @@ -2766,6 +2784,9 @@ func TestTxCannotCommitAfterRollback(t *testing.T) { // 2. (A) Start a query, (B) begin Tx rollback through a ctx cancel. // 3. Check if 2.A has committed in Tx (pass) or outside of Tx (fail). sendQuery := make(chan struct{}) + // The Tx status is returned through the row results, ensure + // that the rows results are not cancelled. + bypassRowsAwaitDone = true hookTxGrabConn = func() { cancel() <-sendQuery @@ -2776,6 +2797,7 @@ func TestTxCannotCommitAfterRollback(t *testing.T) { defer func() { hookTxGrabConn = nil rollbackHook = nil + bypassRowsAwaitDone = false }() err = tx.QueryRow("SELECT|tx_status|tx_status|").Scan(&txStatus) @@ -3812,6 +3834,61 @@ func TestStatsMaxIdleClosedTen(t *testing.T) { } } +func TestMaxIdleTime(t *testing.T) { + list := []struct { + wantMaxIdleTime time.Duration + wantIdleClosed int64 + timeOffset time.Duration + }{ + {time.Nanosecond, 1, 10 * time.Millisecond}, + {time.Hour, 0, 10 * time.Millisecond}, + } + baseTime := time.Unix(0, 0) + defer func() { + nowFunc = time.Now + }() + for _, item := range list { + nowFunc = func() time.Time { + return baseTime + } + t.Run(fmt.Sprintf("%v", item.wantMaxIdleTime), func(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(1) + db.SetConnMaxIdleTime(item.wantMaxIdleTime) + db.SetConnMaxLifetime(0) + + preMaxIdleClosed := db.Stats().MaxIdleTimeClosed + + if err := db.Ping(); err != nil { + t.Fatal(err) + } + + nowFunc = func() time.Time { + return baseTime.Add(item.timeOffset) + } + + db.mu.Lock() + closing := db.connectionCleanerRunLocked() + db.mu.Unlock() + for _, c := range closing { + c.Close() + } + if g, w := int64(len(closing)), item.wantIdleClosed; g != w { + t.Errorf("got: %d; want %d closed conns", g, w) + } + + st := db.Stats() + maxIdleClosed := st.MaxIdleTimeClosed - preMaxIdleClosed + if g, w := maxIdleClosed, item.wantIdleClosed; g != w { + t.Errorf(" got: %d; want %d max idle closed conns", g, w) + } + }) + } +} + type nvcDriver struct { fakeDriver skipNamedValueCheck bool |