github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/badger/transaction/deferred_update.go (about)

     1  package transaction
     2  
     3  import (
     4  	"github.com/dgraph-io/badger/v2"
     5  )
     6  
     7  // DeferredDBUpdate is a shorthand notation for an anonymous function that takes
     8  // a `transaction.Tx` as input and runs some database operations as part of that transaction.
     9  type DeferredDBUpdate func(*Tx) error
    10  
    11  // DeferredBadgerUpdate is a shorthand notation for an anonymous function that takes
    12  // a badger transaction as input and runs some database operations as part of that transaction.
    13  type DeferredBadgerUpdate = func(*badger.Txn) error
    14  
    15  // DeferredDbOps is a utility for accumulating deferred database interactions that
    16  // are supposed to be executed in one atomic transaction. It supports:
    17  //   - Deferred database operations that work directly on Badger transactions.
    18  //   - Deferred database operations that work on `transaction.Tx`.
    19  //     Tx is a storage-layer abstraction, with support for callbacks that are executed
    20  //     after the underlying database transaction completed _successfully_.
    21  //
    22  // ORDER OF EXECUTION
    23  // We extend the process in which `transaction.Tx` executes database operations, schedules
    24  // callbacks, and executed the callbacks. Specifically, DeferredDbOps proceeds as follows:
    25  //
    26  //  0. Record functors added via `AddBadgerOp`, `AddDbOp`, `OnSucceed` ...
    27  //     • some functor's may schedule callbacks (depending on their type), which are executed
    28  //     after the underlying database transaction completed _successfully_.
    29  //     • `OnSucceed` is treated exactly the same way:
    30  //     it schedules a callback during its execution, but it has no database actions.
    31  //  1. Execute the functors in the order they were added
    32  //  2. During each functor's execution:
    33  //     • some functor's may schedule callbacks (depending on their type)
    34  //     • record those callbacks in the order they are scheduled (no execution yet)
    35  //  3. If and only if the underlying database transaction succeeds, run the callbacks
    36  //
    37  // DESIGN PATTERN
    38  //   - DeferredDbOps is stateful, i.e. it needs to be passed as pointer variable.
    39  //   - Do not instantiate Tx directly. Instead, use one of the following
    40  //     transaction.Update(db, DeferredDbOps.Pending())
    41  //     transaction.View(db, DeferredDbOps.Pending())
    42  //     operation.RetryOnConflictTx(db, transaction.Update, DeferredDbOps.Pending())
    43  //
    44  // NOT CONCURRENCY SAFE
    45  type DeferredDbOps struct {
    46  	pending DeferredDBUpdate
    47  }
    48  
    49  // NewDeferredDbOps instantiates a DeferredDbOps. Initially, it behaves like a no-op until functors are added.
    50  func NewDeferredDbOps() *DeferredDbOps {
    51  	return &DeferredDbOps{
    52  		pending: func(tx *Tx) error { return nil }, // initially nothing is pending, i.e. no-op
    53  	}
    54  }
    55  
    56  // Pending returns a DeferredDBUpdate that includes all database operations and callbacks
    57  // that were added so far. Caution, DeferredDbOps keeps its internal state of deferred operations.
    58  // Pending() can be called multiple times, but should only be executed in a database transaction
    59  // once to avoid conflicts.
    60  func (d *DeferredDbOps) Pending() DeferredDBUpdate {
    61  	return d.pending
    62  }
    63  
    64  // AddBadgerOp schedules the given DeferredBadgerUpdate to be executed as part of the future transaction.
    65  // For adding multiple DeferredBadgerUpdates, use `AddBadgerOps(ops ...DeferredBadgerUpdate)` if easily possible, as
    66  // it reduces the call stack compared to adding the functors individually via `AddBadgerOp(op DeferredBadgerUpdate)`.
    67  // This method returns a self-reference for chaining.
    68  func (d *DeferredDbOps) AddBadgerOp(op DeferredBadgerUpdate) *DeferredDbOps {
    69  	prior := d.pending
    70  	d.pending = func(tx *Tx) error {
    71  		err := prior(tx)
    72  		if err != nil {
    73  			return err
    74  		}
    75  		err = op(tx.DBTxn)
    76  		if err != nil {
    77  			return err
    78  		}
    79  		return nil
    80  	}
    81  	return d
    82  }
    83  
    84  // AddBadgerOps schedules the given DeferredBadgerUpdates to be executed as part of the future transaction.
    85  // This method returns a self-reference for chaining.
    86  func (d *DeferredDbOps) AddBadgerOps(ops ...DeferredBadgerUpdate) *DeferredDbOps {
    87  	if len(ops) < 1 {
    88  		return d
    89  	}
    90  
    91  	prior := d.pending
    92  	d.pending = func(tx *Tx) error {
    93  		err := prior(tx)
    94  		if err != nil {
    95  			return err
    96  		}
    97  		for _, op := range ops {
    98  			err = op(tx.DBTxn)
    99  			if err != nil {
   100  				return err
   101  			}
   102  		}
   103  		return nil
   104  	}
   105  	return d
   106  }
   107  
   108  // AddDbOp schedules the given DeferredDBUpdate to be executed as part of the future transaction.
   109  // For adding multiple DeferredBadgerUpdates, use `AddDbOps(ops ...DeferredDBUpdate)` if easily possible, as
   110  // it reduces the call stack compared to adding the functors individually via `AddDbOp(op DeferredDBUpdate)`.
   111  // This method returns a self-reference for chaining.
   112  func (d *DeferredDbOps) AddDbOp(op DeferredDBUpdate) *DeferredDbOps {
   113  	prior := d.pending
   114  	d.pending = func(tx *Tx) error {
   115  		err := prior(tx)
   116  		if err != nil {
   117  			return err
   118  		}
   119  		err = op(tx)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		return nil
   124  	}
   125  	return d
   126  }
   127  
   128  // AddDbOps schedules the given DeferredDBUpdates to be executed as part of the future transaction.
   129  // This method returns a self-reference for chaining.
   130  func (d *DeferredDbOps) AddDbOps(ops ...DeferredDBUpdate) *DeferredDbOps {
   131  	if len(ops) < 1 {
   132  		return d
   133  	}
   134  
   135  	prior := d.pending
   136  	d.pending = func(tx *Tx) error {
   137  		err := prior(tx)
   138  		if err != nil {
   139  			return err
   140  		}
   141  		for _, op := range ops {
   142  			err = op(tx)
   143  			if err != nil {
   144  				return err
   145  			}
   146  		}
   147  		return nil
   148  	}
   149  	return d
   150  }
   151  
   152  // OnSucceed adds a callback to be executed after the deferred database operations have succeeded. For
   153  // adding multiple callbacks, use `OnSucceeds(callbacks ...func())` if easily possible, as it reduces
   154  // the call stack compared to adding the functors individually via `OnSucceed(callback func())`.
   155  // This method returns a self-reference for chaining.
   156  func (d *DeferredDbOps) OnSucceed(callback func()) *DeferredDbOps {
   157  	prior := d.pending
   158  	d.pending = func(tx *Tx) error {
   159  		err := prior(tx)
   160  		if err != nil {
   161  			return err
   162  		}
   163  		tx.OnSucceed(callback)
   164  		return nil
   165  	}
   166  	return d
   167  }
   168  
   169  // OnSucceeds adds callbacks to be executed after the deferred database operations have succeeded.
   170  // This method returns a self-reference for chaining.
   171  func (d *DeferredDbOps) OnSucceeds(callbacks ...func()) *DeferredDbOps {
   172  	if len(callbacks) < 1 {
   173  		return d
   174  	}
   175  
   176  	prior := d.pending
   177  	d.pending = func(tx *Tx) error {
   178  		err := prior(tx)
   179  		if err != nil {
   180  			return err
   181  		}
   182  		for _, c := range callbacks {
   183  			tx.OnSucceed(c)
   184  		}
   185  		return nil
   186  	}
   187  	return d
   188  }