walletdb: add View and Update to DB interface

We want to make sure that every database backend has their own
implementation of the View() and Update() methods to make sure any
custom retry/commit/rollback strategies are used even when invoked
through the package-provided View() and Update() functions.
This commit is contained in:
Oliver Gugger 2021-07-19 13:33:20 +02:00
parent f07fdfb6b9
commit a27bab6eb9
No known key found for this signature in database
GPG key ID: 8E4256593F177720
2 changed files with 121 additions and 49 deletions

View file

@ -365,6 +365,83 @@ func (db *db) Batch(f func(tx walletdb.ReadWriteTx) error) error {
}) })
} }
// View opens a database read transaction and executes the function f with the
// transaction passed as a parameter. After f exits, the transaction is rolled
// back. If f errors, its error is returned, not a rollback error (if any
// occur). The passed reset function is called before the start of the
// transaction and can be used to reset intermediate state. As callers may
// expect retries of the f closure (depending on the database backend used), the
// reset function will be called before each retry respectively.
func (db *db) View(f func(tx walletdb.ReadTx) error, reset func()) error {
// We don't do any retries with bolt so we just initially call the reset
// function once.
reset()
tx, err := db.BeginReadTx()
if err != nil {
return err
}
// Make sure the transaction rolls back in the event of a panic.
defer func() {
if tx != nil {
_ = tx.Rollback()
}
}()
err = f(tx)
rollbackErr := tx.Rollback()
if err != nil {
return err
}
if rollbackErr != nil {
return rollbackErr
}
return nil
}
// Update opens a database read/write transaction and executes the function f
// with the transaction passed as a parameter. After f exits, if f did not
// error, the transaction is committed. Otherwise, if f did error, the
// transaction is rolled back. If the rollback fails, the original error
// returned by f is still returned. If the commit fails, the commit error is
// returned. As callers may expect retries of the f closure (depending on the
// database backend used), the reset function will be called before each retry
// respectively.
func (db *db) Update(f func(tx walletdb.ReadWriteTx) error, reset func()) error {
// We don't do any retries with bolt so we just initially call the reset
// function once.
reset()
tx, err := db.BeginReadWriteTx()
if err != nil {
return err
}
// Make sure the transaction rolls back in the event of a panic.
defer func() {
if tx != nil {
_ = tx.Rollback()
}
}()
err = f(tx)
if err != nil {
// Want to return the original error, not a rollback error if
// any occur.
_ = tx.Rollback()
return err
}
return tx.Commit()
}
// PrintStats returns all collected stats pretty printed into a string.
func (db *db) PrintStats() string {
return "<no stats are collected by bdb backend>"
}
// filesExists reports whether the named file or directory exists. // filesExists reports whether the named file or directory exists.
func fileExists(name string) bool { func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil { if _, err := os.Stat(name); err != nil {

View file

@ -126,8 +126,9 @@ type ReadWriteBucket interface {
// ErrTxNotWritable if attempted against a read-only transaction. // ErrTxNotWritable if attempted against a read-only transaction.
Delete(key []byte) error Delete(key []byte) error
// Cursor returns a new cursor, allowing for iteration over the bucket's // ReadWriteCursor returns a new cursor, allowing for iteration over the
// key/value pairs and nested buckets in forward or backward order. // bucket's key/value pairs and nested buckets in forward or backward
// order.
ReadWriteCursor() ReadWriteCursor ReadWriteCursor() ReadWriteCursor
// Tx returns the bucket's transaction. // Tx returns the bucket's transaction.
@ -190,7 +191,7 @@ func BucketIsEmpty(bucket ReadBucket) bool {
return k == nil && v == nil return k == nil && v == nil
} }
// DB represents an ACID database. All database access is performed through // DB represents an ACID database. All database access is performed through
// read or read+write transactions. // read or read+write transactions.
type DB interface { type DB interface {
// BeginReadTx opens a database read transaction. // BeginReadTx opens a database read transaction.
@ -205,16 +206,45 @@ type DB interface {
// Close cleanly shuts down the database and syncs all data. // Close cleanly shuts down the database and syncs all data.
Close() error Close() error
// PrintStats returns all collected stats pretty printed into a string.
PrintStats() string
// View opens a database read transaction and executes the function f
// with the transaction passed as a parameter. After f exits, the
// transaction is rolled back. If f errors, its error is returned, not a
// rollback error (if any occur). The passed reset function is called
// before the start of the transaction and can be used to reset
// intermediate state. As callers may expect retries of the f closure
// (depending on the database backend used), the reset function will be
// called before each retry respectively.
//
// NOTE: For new code, this method should be used directly instead of
// the package level View() function.
View(f func(tx ReadTx) error, reset func()) error
// Update opens a database read/write transaction and executes the
// function f with the transaction passed as a parameter. After f exits,
// if f did not error, the transaction is committed. Otherwise, if f did
// error, the transaction is rolled back. If the rollback fails, the
// original error returned by f is still returned. If the commit fails,
// the commit error is returned. As callers may expect retries of the f
// closure (depending on the database backend used), the reset function
// will be called before each retry respectively.
//
// NOTE: For new code, this method should be used directly instead of
// the package level Update() function.
Update(f func(tx ReadWriteTx) error, reset func()) error
} }
// BatchDB is a special version of the main DB interface that allos the caller // BatchDB is a special version of the main DB interface that allows the caller
// to specify write transactions that should be combine dtoegether if multiple // to specify write transactions that should be combined toegether if multiple
// goroutines are calling the Batch method. // goroutines are calling the Batch method.
type BatchDB interface { type BatchDB interface {
DB DB
// Batch is similar to the package-level Update method, but it will // Batch is similar to the package-level Update method, but it will
// attempt to optismitcally combine the invocation of several // attempt to optimistically combine the invocation of several
// transaction functions into a single db write transaction. // transaction functions into a single db write transaction.
Batch(func(tx ReadWriteTx) error) error Batch(func(tx ReadWriteTx) error) error
} }
@ -223,29 +253,11 @@ type BatchDB interface {
// transaction passed as a parameter. After f exits, the transaction is rolled // transaction passed as a parameter. After f exits, the transaction is rolled
// back. If f errors, its error is returned, not a rollback error (if any // back. If f errors, its error is returned, not a rollback error (if any
// occur). // occur).
//
// NOTE: For new code the database backend's View method should be used directly
// as this package level function will be phased out in the future.
func View(db DB, f func(tx ReadTx) error) error { func View(db DB, f func(tx ReadTx) error) error {
tx, err := db.BeginReadTx() return db.View(f, func() {})
if err != nil {
return err
}
// Make sure the transaction rolls back in the event of a panic.
defer func() {
if tx != nil {
tx.Rollback()
}
}()
err = f(tx)
rollbackErr := tx.Rollback()
if err != nil {
return err
}
if rollbackErr != nil {
return rollbackErr
}
return nil
} }
// Update opens a database read/write transaction and executes the function f // Update opens a database read/write transaction and executes the function f
@ -254,28 +266,11 @@ func View(db DB, f func(tx ReadTx) error) error {
// transaction is rolled back. If the rollback fails, the original error // transaction is rolled back. If the rollback fails, the original error
// returned by f is still returned. If the commit fails, the commit error is // returned by f is still returned. If the commit fails, the commit error is
// returned. // returned.
//
// NOTE: For new code the database backend's Update method should be used
// directly as this package level function will be phased out in the future.
func Update(db DB, f func(tx ReadWriteTx) error) error { func Update(db DB, f func(tx ReadWriteTx) error) error {
tx, err := db.BeginReadWriteTx() return db.Update(f, func() {})
if err != nil {
return err
}
// Make sure the transaction rolls back in the event of a panic.
defer func() {
if tx != nil {
tx.Rollback()
}
}()
err = f(tx)
if err != nil {
// Want to return the original error, not a rollback error if
// any occur.
_ = tx.Rollback()
return err
}
return tx.Commit()
} }
// Batch opens a database read/write transaction and executes the function f // Batch opens a database read/write transaction and executes the function f