Skip to content

Commit 971f8a2

Browse files
committed
database/sql: process all Session Resets synchronously
Adds a new interface, driver.ConnectionValidator, to allow drivers to signal they should not be used again, separatly from the session resetter interface. This is done now that the session reset is done after the connection is put into the connection pool. Previous behavior attempted to run Session Resets in a background worker. This implementation had two problems: untested performance gains for additional complexity, and failures when the pool size exceeded the connection reset channel buffer size. Fixes #31480 Change-Id: I7d483b883c24a362c292471e87a88db5b204d1d0 Reviewed-on: https://go-review.googlesource.com/c/go/+/174122 Reviewed-by: Emmanuel Odeke <[email protected]> Run-TryBot: Emmanuel Odeke <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 0eeec4f commit 971f8a2

File tree

3 files changed

+63
-78
lines changed

3 files changed

+63
-78
lines changed

Diff for: src/database/sql/driver/driver.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -255,15 +255,23 @@ type ConnBeginTx interface {
255255
// SessionResetter may be implemented by Conn to allow drivers to reset the
256256
// session state associated with the connection and to signal a bad connection.
257257
type SessionResetter interface {
258-
// ResetSession is called while a connection is in the connection
259-
// pool. No queries will run on this connection until this method returns.
260-
//
261-
// If the connection is bad this should return driver.ErrBadConn to prevent
262-
// the connection from being returned to the connection pool. Any other
263-
// error will be discarded.
258+
// ResetSession is called prior to executing a query on the connection
259+
// if the connection has been used before. If the driver returns ErrBadConn
260+
// the connection is discarded.
264261
ResetSession(ctx context.Context) error
265262
}
266263

264+
// ConnectionValidator may be implemented by Conn to allow drivers to
265+
// signal if a connection is valid or if it should be discarded.
266+
//
267+
// If implemented, drivers may return the underlying error from queries,
268+
// even if the connection should be discarded by the connection pool.
269+
type ConnectionValidator interface {
270+
// ValidConnection is called prior to placing the connection into the
271+
// connection pool. The connection will be discarded if false is returned.
272+
ValidConnection() bool
273+
}
274+
267275
// Result is the result of a query execution.
268276
type Result interface {
269277
// LastInsertId returns the database's auto-generated ID

Diff for: src/database/sql/fakedb_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,12 @@ func (c *fakeConn) ResetSession(ctx context.Context) error {
396396
return nil
397397
}
398398

399+
var _ driver.ConnectionValidator = (*fakeConn)(nil)
400+
401+
func (c *fakeConn) ValidConnection() bool {
402+
return !c.isBad()
403+
}
404+
399405
func (c *fakeConn) Close() (err error) {
400406
drv := fdriver.(*fakeDriver)
401407
defer func() {

Diff for: src/database/sql/sql.go

+43-72
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,6 @@ type DB struct {
421421
// It is closed during db.Close(). The close tells the connectionOpener
422422
// goroutine to exit.
423423
openerCh chan struct{}
424-
resetterCh chan *driverConn
425424
closed bool
426425
dep map[finalCloser]depSet
427426
lastPut map[*driverConn]string // stacktrace of last conn's put; debug only
@@ -460,10 +459,10 @@ type driverConn struct {
460459

461460
sync.Mutex // guards following
462461
ci driver.Conn
462+
needReset bool // The connection session should be reset before use if true.
463463
closed bool
464464
finalClosed bool // ci.Close has been called
465465
openStmt map[*driverStmt]bool
466-
lastErr error // lastError captures the result of the session resetter.
467466

468467
// guarded by db.mu
469468
inUse bool
@@ -489,6 +488,36 @@ func (dc *driverConn) expired(timeout time.Duration) bool {
489488
return dc.createdAt.Add(timeout).Before(nowFunc())
490489
}
491490

491+
// resetSession checks if the driver connection needs the
492+
// session to be reset and if required, resets it.
493+
func (dc *driverConn) resetSession(ctx context.Context) error {
494+
dc.Lock()
495+
defer dc.Unlock()
496+
497+
if !dc.needReset {
498+
return nil
499+
}
500+
if cr, ok := dc.ci.(driver.SessionResetter); ok {
501+
return cr.ResetSession(ctx)
502+
}
503+
return nil
504+
}
505+
506+
// validateConnection checks if the connection is valid and can
507+
// still be used. It also marks the session for reset if required.
508+
func (dc *driverConn) validateConnection(needsReset bool) bool {
509+
dc.Lock()
510+
defer dc.Unlock()
511+
512+
if needsReset {
513+
dc.needReset = true
514+
}
515+
if cv, ok := dc.ci.(driver.ConnectionValidator); ok {
516+
return cv.ValidConnection()
517+
}
518+
return true
519+
}
520+
492521
// prepareLocked prepares the query on dc. When cg == nil the dc must keep track of
493522
// the prepared statements in a pool.
494523
func (dc *driverConn) prepareLocked(ctx context.Context, cg stmtConnGrabber, query string) (*driverStmt, error) {
@@ -514,19 +543,6 @@ func (dc *driverConn) prepareLocked(ctx context.Context, cg stmtConnGrabber, que
514543
return ds, nil
515544
}
516545

517-
// resetSession resets the connection session and sets the lastErr
518-
// that is checked before returning the connection to another query.
519-
//
520-
// resetSession assumes that the embedded mutex is locked when the connection
521-
// was returned to the pool. This unlocks the mutex.
522-
func (dc *driverConn) resetSession(ctx context.Context) {
523-
defer dc.Unlock() // In case of panic.
524-
if dc.closed { // Check if the database has been closed.
525-
return
526-
}
527-
dc.lastErr = dc.ci.(driver.SessionResetter).ResetSession(ctx)
528-
}
529-
530546
// the dc.db's Mutex is held.
531547
func (dc *driverConn) closeDBLocked() func() error {
532548
dc.Lock()
@@ -716,14 +732,12 @@ func OpenDB(c driver.Connector) *DB {
716732
db := &DB{
717733
connector: c,
718734
openerCh: make(chan struct{}, connectionRequestQueueSize),
719-
resetterCh: make(chan *driverConn, 50),
720735
lastPut: make(map[*driverConn]string),
721736
connRequests: make(map[uint64]chan connRequest),
722737
stop: cancel,
723738
}
724739

725740
go db.connectionOpener(ctx)
726-
go db.connectionResetter(ctx)
727741

728742
return db
729743
}
@@ -1118,23 +1132,6 @@ func (db *DB) connectionOpener(ctx context.Context) {
11181132
}
11191133
}
11201134

1121-
// connectionResetter runs in a separate goroutine to reset connections async
1122-
// to exported API.
1123-
func (db *DB) connectionResetter(ctx context.Context) {
1124-
for {
1125-
select {
1126-
case <-ctx.Done():
1127-
close(db.resetterCh)
1128-
for dc := range db.resetterCh {
1129-
dc.Unlock()
1130-
}
1131-
return
1132-
case dc := <-db.resetterCh:
1133-
dc.resetSession(ctx)
1134-
}
1135-
}
1136-
}
1137-
11381135
// Open one new connection
11391136
func (db *DB) openNewConnection(ctx context.Context) {
11401137
// maybeOpenNewConnctions has already executed db.numOpen++ before it sent
@@ -1216,14 +1213,13 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn
12161213
conn.Close()
12171214
return nil, driver.ErrBadConn
12181215
}
1219-
// Lock around reading lastErr to ensure the session resetter finished.
1220-
conn.Lock()
1221-
err := conn.lastErr
1222-
conn.Unlock()
1223-
if err == driver.ErrBadConn {
1216+
1217+
// Reset the session if required.
1218+
if err := conn.resetSession(ctx); err == driver.ErrBadConn {
12241219
conn.Close()
12251220
return nil, driver.ErrBadConn
12261221
}
1222+
12271223
return conn, nil
12281224
}
12291225

@@ -1272,11 +1268,9 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn
12721268
if ret.conn == nil {
12731269
return nil, ret.err
12741270
}
1275-
// Lock around reading lastErr to ensure the session resetter finished.
1276-
ret.conn.Lock()
1277-
err := ret.conn.lastErr
1278-
ret.conn.Unlock()
1279-
if err == driver.ErrBadConn {
1271+
1272+
// Reset the session if required.
1273+
if err := ret.conn.resetSession(ctx); err == driver.ErrBadConn {
12801274
ret.conn.Close()
12811275
return nil, driver.ErrBadConn
12821276
}
@@ -1337,6 +1331,11 @@ const debugGetPut = false
13371331
// putConn adds a connection to the db's free pool.
13381332
// err is optionally the last error that occurred on this connection.
13391333
func (db *DB) putConn(dc *driverConn, err error, resetSession bool) {
1334+
if err != driver.ErrBadConn {
1335+
if !dc.validateConnection(resetSession) {
1336+
err = driver.ErrBadConn
1337+
}
1338+
}
13401339
db.mu.Lock()
13411340
if !dc.inUse {
13421341
if debugGetPut {
@@ -1368,41 +1367,13 @@ func (db *DB) putConn(dc *driverConn, err error, resetSession bool) {
13681367
if putConnHook != nil {
13691368
putConnHook(db, dc)
13701369
}
1371-
if db.closed {
1372-
// Connections do not need to be reset if they will be closed.
1373-
// Prevents writing to resetterCh after the DB has closed.
1374-
resetSession = false
1375-
}
1376-
if resetSession {
1377-
if _, resetSession = dc.ci.(driver.SessionResetter); resetSession {
1378-
// Lock the driverConn here so it isn't released until
1379-
// the connection is reset.
1380-
// The lock must be taken before the connection is put into
1381-
// the pool to prevent it from being taken out before it is reset.
1382-
dc.Lock()
1383-
}
1384-
}
13851370
added := db.putConnDBLocked(dc, nil)
13861371
db.mu.Unlock()
13871372

13881373
if !added {
1389-
if resetSession {
1390-
dc.Unlock()
1391-
}
13921374
dc.Close()
13931375
return
13941376
}
1395-
if !resetSession {
1396-
return
1397-
}
1398-
select {
1399-
default:
1400-
// If the resetterCh is blocking then mark the connection
1401-
// as bad and continue on.
1402-
dc.lastErr = driver.ErrBadConn
1403-
dc.Unlock()
1404-
case db.resetterCh <- dc:
1405-
}
14061377
}
14071378

14081379
// Satisfy a connRequest or put the driverConn in the idle pool and return true

0 commit comments

Comments
 (0)