private/dbutil: add WithTx transaction helpers
These helpers will work similar to the WithTx method we have added to our dbx.DB instances, but it will use crdb.ExecuteTx or crdb.ExecuteInTx when the backend is CockroachDB, so that transactions are retried correctly. Anything that uses transactions and might need to work against CockroachDB needs to handle "RetriableError" from cockroachdb by restarting the transaction. This will probably be a large pain if not using these helpers or something very like them. Subsequent changes will undertake transforming all db-transaction uses in satellite code so that they are cockroach-safe. Change-Id: I648b8de2168612c67b9d6eb8402bccf8286249a9
This commit is contained in:
parent
f41d440944
commit
6231842422
61
private/dbutil/txutil/transactions.go
Normal file
61
private/dbutil/txutil/transactions.go
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
// Package txutil provides safe transaction-encapsulation functions which have retry
|
||||
// semantics as necessary.
|
||||
package txutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
|
||||
"github.com/cockroachdb/cockroach-go/crdb"
|
||||
"github.com/zeebo/errs"
|
||||
"storj.io/storj/private/dbutil/cockroachutil"
|
||||
)
|
||||
|
||||
// txLike is the minimal interface for transaction-like objects to work with the necessary retry
|
||||
// semantics on things like CockroachDB.
|
||||
type txLike interface {
|
||||
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||
Commit() error
|
||||
Rollback() error
|
||||
}
|
||||
|
||||
// ExecuteInTx runs the fn callback inside the specified transaction, restarting the transaction
|
||||
// as necessary (for systems like CockroachDB), and committing or rolling back the transaction
|
||||
// depending on whether fn returns an error.
|
||||
//
|
||||
// In most cases, WithTx() is to be preferred, but this variant is useful when working with a db
|
||||
// that isn't an *sql.DB.
|
||||
func ExecuteInTx(ctx context.Context, dbDriver driver.Driver, tx txLike, fn func() error) (err error) {
|
||||
if _, ok := dbDriver.(*cockroachutil.Driver); ok {
|
||||
return crdb.ExecuteInTx(ctx, tx, fn)
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
} else {
|
||||
err = errs.Combine(err, tx.Rollback())
|
||||
}
|
||||
}()
|
||||
return fn()
|
||||
}
|
||||
|
||||
// WithTx starts a transaction on the given sql.DB. The transaction is started in the appropriate
|
||||
// manner, and will be restarted if appropriate. While in the transaction, fn is called with a
|
||||
// handle to the transaction in order to make use of it. If fn returns an error, the transaction
|
||||
// is rolled back. If fn returns nil, the transaction is committed.
|
||||
//
|
||||
// If fn has any side effects outside of changes to the database, they must be idempotent! fn may
|
||||
// be called more than one time.
|
||||
func WithTx(ctx context.Context, db *sql.DB, txOpts *sql.TxOptions, fn func(context.Context, *sql.Tx) error) error {
|
||||
tx, err := db.BeginTx(ctx, txOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ExecuteInTx(ctx, db.Driver(), tx, func() error {
|
||||
return fn(ctx, tx)
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user