func (db *DB) PingContext(ctx context.Context) error {
var dc *driverConn
var err error
- var isBadConn bool
- for i := 0; i < maxBadConnRetries; i++ {
- dc, err = db.conn(ctx, cachedOrNewConn)
- isBadConn = errors.Is(err, driver.ErrBadConn)
- if !isBadConn {
- break
- }
- }
- if isBadConn {
- dc, err = db.conn(ctx, alwaysNewConn)
- }
+
+ err = db.retry(func(strategy connReuseStrategy) error {
+ dc, err = db.conn(ctx, strategy)
+ return err
+ })
+
if err != nil {
return err
}
// connection to be opened.
const maxBadConnRetries = 2
+func (db *DB) retry(fn func(strategy connReuseStrategy) error) error {
+ for i := int64(0); i < maxBadConnRetries; i++ {
+ err := fn(cachedOrNewConn)
+ // retry if err is driver.ErrBadConn
+ if err == nil || !errors.Is(err, driver.ErrBadConn) {
+ return err
+ }
+ }
+
+ return fn(alwaysNewConn)
+}
+
// PrepareContext creates a prepared statement for later queries or executions.
// Multiple queries or executions may be run concurrently from the
// returned statement.
func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) {
var stmt *Stmt
var err error
- var isBadConn bool
- for i := 0; i < maxBadConnRetries; i++ {
- stmt, err = db.prepare(ctx, query, cachedOrNewConn)
- isBadConn = errors.Is(err, driver.ErrBadConn)
- if !isBadConn {
- break
- }
- }
- if isBadConn {
- return db.prepare(ctx, query, alwaysNewConn)
- }
+
+ err = db.retry(func(strategy connReuseStrategy) error {
+ stmt, err = db.prepare(ctx, query, strategy)
+ return err
+ })
+
return stmt, err
}
func (db *DB) ExecContext(ctx context.Context, query string, args ...any) (Result, error) {
var res Result
var err error
- var isBadConn bool
- for i := 0; i < maxBadConnRetries; i++ {
- res, err = db.exec(ctx, query, args, cachedOrNewConn)
- isBadConn = errors.Is(err, driver.ErrBadConn)
- if !isBadConn {
- break
- }
- }
- if isBadConn {
- return db.exec(ctx, query, args, alwaysNewConn)
- }
+
+ err = db.retry(func(strategy connReuseStrategy) error {
+ res, err = db.exec(ctx, query, args, strategy)
+ return err
+ })
+
return res, err
}
func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error) {
var rows *Rows
var err error
- var isBadConn bool
- for i := 0; i < maxBadConnRetries; i++ {
- rows, err = db.query(ctx, query, args, cachedOrNewConn)
- isBadConn = errors.Is(err, driver.ErrBadConn)
- if !isBadConn {
- break
- }
- }
- if isBadConn {
- return db.query(ctx, query, args, alwaysNewConn)
- }
+
+ err = db.retry(func(strategy connReuseStrategy) error {
+ rows, err = db.query(ctx, query, args, strategy)
+ return err
+ })
+
return rows, err
}
func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error) {
var tx *Tx
var err error
- var isBadConn bool
- for i := 0; i < maxBadConnRetries; i++ {
- tx, err = db.begin(ctx, opts, cachedOrNewConn)
- isBadConn = errors.Is(err, driver.ErrBadConn)
- if !isBadConn {
- break
- }
- }
- if isBadConn {
- return db.begin(ctx, opts, alwaysNewConn)
- }
+
+ err = db.retry(func(strategy connReuseStrategy) error {
+ tx, err = db.begin(ctx, opts, strategy)
+ return err
+ })
+
return tx, err
}
func (db *DB) Conn(ctx context.Context) (*Conn, error) {
var dc *driverConn
var err error
- var isBadConn bool
- for i := 0; i < maxBadConnRetries; i++ {
- dc, err = db.conn(ctx, cachedOrNewConn)
- isBadConn = errors.Is(err, driver.ErrBadConn)
- if !isBadConn {
- break
- }
- }
- if isBadConn {
- dc, err = db.conn(ctx, alwaysNewConn)
- }
+
+ err = db.retry(func(strategy connReuseStrategy) error {
+ dc, err = db.conn(ctx, strategy)
+ return err
+ })
+
if err != nil {
return nil, err
}
defer s.closemu.RUnlock()
var res Result
- strategy := cachedOrNewConn
- for i := 0; i < maxBadConnRetries+1; i++ {
- if i == maxBadConnRetries {
- strategy = alwaysNewConn
- }
+ err := s.db.retry(func(strategy connReuseStrategy) error {
dc, releaseConn, ds, err := s.connStmt(ctx, strategy)
if err != nil {
- if errors.Is(err, driver.ErrBadConn) {
- continue
- }
- return nil, err
+ return err
}
res, err = resultFromStatement(ctx, dc.ci, ds, args...)
releaseConn(err)
- if !errors.Is(err, driver.ErrBadConn) {
- return res, err
- }
- }
- return nil, driver.ErrBadConn
+ return err
+ })
+
+ return res, err
}
// Exec executes a prepared statement with the given arguments and
defer s.closemu.RUnlock()
var rowsi driver.Rows
- strategy := cachedOrNewConn
- for i := 0; i < maxBadConnRetries+1; i++ {
- if i == maxBadConnRetries {
- strategy = alwaysNewConn
- }
+ var rows *Rows
+
+ err := s.db.retry(func(strategy connReuseStrategy) error {
dc, releaseConn, ds, err := s.connStmt(ctx, strategy)
if err != nil {
- if errors.Is(err, driver.ErrBadConn) {
- continue
- }
- return nil, err
+ return err
}
rowsi, err = rowsiFromStatement(ctx, dc.ci, ds, args...)
if err == nil {
// Note: ownership of ci passes to the *Rows, to be freed
// with releaseConn.
- rows := &Rows{
+ rows = &Rows{
dc: dc,
rowsi: rowsi,
// releaseConn set below
txctx = s.cg.txCtx()
}
rows.initContextClose(ctx, txctx)
- return rows, nil
+ return nil
}
releaseConn(err)
- if !errors.Is(err, driver.ErrBadConn) {
- return nil, err
- }
- }
- return nil, driver.ErrBadConn
+ return err
+ })
+
+ return rows, err
}
// Query executes a prepared query statement with the given arguments