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 }