metastore: Add WithTransaction convenience method
Most snapshotters end up manually handling the rollback logic, either by calling `t.Rollback()` in every failure path, setting up a custom defer func to log on certain errors, or just deferring `t.Rollback()` even for `snapshotter.Commit()` which *will* cause `t.Rollback()` to return an error afaict, but it's just never checked and luckily bolt handles this alright... The devmapper snapshotter has a solution to this which is to have a method that starts either a read-only or writable transaction inside the method, and you pass in a callback to do your bidding and any failures are rolled back, and if it's writable will handle the commit for you. This seems like the right model to me, it removes the burden from the snapshot author to remember to either defer/call rollback in every method for every failure case. This change exposes the convenience method from devmapper to the snapshots/storage package as a method off of `storage.MetaStore` and moves over the devmapper snapshotter to use this. Signed-off-by: Danny Canter <danny@dcantah.dev>
This commit is contained in:
@@ -26,7 +26,9 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/snapshots"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
@@ -104,6 +106,51 @@ func (ms *MetaStore) TransactionContext(ctx context.Context, writable bool) (con
|
||||
return ctx, tx, nil
|
||||
}
|
||||
|
||||
// TransactionCallback represents a callback to be invoked while under a metastore transaction.
|
||||
type TransactionCallback func(ctx context.Context) error
|
||||
|
||||
// WithTransaction is a convenience method to run a function `fn` while holding a meta store transaction.
|
||||
// If the callback `fn` returns an error or the transaction is not writable, the database transaction will be discarded.
|
||||
func (ms *MetaStore) WithTransaction(ctx context.Context, writable bool, fn TransactionCallback) error {
|
||||
ctx, trans, err := ms.TransactionContext(ctx, writable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var result *multierror.Error
|
||||
err = fn(ctx)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
|
||||
// Always rollback if transaction is not writable
|
||||
if err != nil || !writable {
|
||||
if terr := trans.Rollback(); terr != nil {
|
||||
log.G(ctx).WithError(terr).Error("failed to rollback transaction")
|
||||
|
||||
result = multierror.Append(result, fmt.Errorf("rollback failed: %w", terr))
|
||||
}
|
||||
} else {
|
||||
if terr := trans.Commit(); terr != nil {
|
||||
log.G(ctx).WithError(terr).Error("failed to commit transaction")
|
||||
|
||||
result = multierror.Append(result, fmt.Errorf("commit failed: %w", terr))
|
||||
}
|
||||
}
|
||||
|
||||
if err := result.ErrorOrNil(); err != nil {
|
||||
log.G(ctx).WithError(err).Debug("snapshotter error")
|
||||
|
||||
// Unwrap if just one error
|
||||
if len(result.Errors) == 1 {
|
||||
return result.Errors[0]
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the metastore and any underlying database connections
|
||||
func (ms *MetaStore) Close() error {
|
||||
ms.dbL.Lock()
|
||||
|
||||
Reference in New Issue
Block a user