github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/computation/computer/transaction_coordinator.go (about) 1 package computer 2 3 import ( 4 "sync" 5 "time" 6 7 "github.com/onflow/flow-go/fvm" 8 "github.com/onflow/flow-go/fvm/storage" 9 "github.com/onflow/flow-go/fvm/storage/derived" 10 "github.com/onflow/flow-go/fvm/storage/logical" 11 "github.com/onflow/flow-go/fvm/storage/snapshot" 12 ) 13 14 type TransactionWriteBehindLogger interface { 15 AddTransactionResult( 16 txn TransactionRequest, 17 snapshot *snapshot.ExecutionSnapshot, 18 output fvm.ProcedureOutput, 19 timeSpent time.Duration, 20 numTxnConflictRetries int, 21 ) 22 } 23 24 // transactionCoordinator provides synchronization functionality for driving 25 // transaction execution. 26 type transactionCoordinator struct { 27 vm fvm.VM 28 29 mutex *sync.Mutex 30 cond *sync.Cond 31 32 snapshotTime logical.Time // guarded by mutex, cond broadcast on updates. 33 abortErr error // guarded by mutex, cond broadcast on updates. 34 35 // Note: database commit and result logging must occur within the same 36 // critical section (guraded by mutex). 37 database *storage.BlockDatabase 38 writeBehindLog TransactionWriteBehindLogger 39 } 40 41 type transaction struct { 42 request TransactionRequest 43 numConflictRetries int 44 45 coordinator *transactionCoordinator 46 47 startedAt time.Time 48 storage.Transaction 49 fvm.ProcedureExecutor 50 } 51 52 func newTransactionCoordinator( 53 vm fvm.VM, 54 storageSnapshot snapshot.StorageSnapshot, 55 cachedDerivedBlockData *derived.DerivedBlockData, 56 writeBehindLog TransactionWriteBehindLogger, 57 ) *transactionCoordinator { 58 mutex := &sync.Mutex{} 59 cond := sync.NewCond(mutex) 60 61 database := storage.NewBlockDatabase( 62 storageSnapshot, 63 0, 64 cachedDerivedBlockData) 65 66 return &transactionCoordinator{ 67 vm: vm, 68 mutex: mutex, 69 cond: cond, 70 snapshotTime: 0, 71 abortErr: nil, 72 database: database, 73 writeBehindLog: writeBehindLog, 74 } 75 } 76 77 func (coordinator *transactionCoordinator) SnapshotTime() logical.Time { 78 coordinator.mutex.Lock() 79 defer coordinator.mutex.Unlock() 80 81 return coordinator.snapshotTime 82 } 83 84 func (coordinator *transactionCoordinator) Error() error { 85 coordinator.mutex.Lock() 86 defer coordinator.mutex.Unlock() 87 88 return coordinator.abortErr 89 } 90 91 func (coordinator *transactionCoordinator) AbortAllOutstandingTransactions( 92 err error, 93 ) { 94 coordinator.mutex.Lock() 95 defer coordinator.mutex.Unlock() 96 97 if coordinator.abortErr != nil { // Transactions are already aborting. 98 return 99 } 100 101 coordinator.abortErr = err 102 coordinator.cond.Broadcast() 103 } 104 105 func (coordinator *transactionCoordinator) NewTransaction( 106 request TransactionRequest, 107 attempt int, 108 ) ( 109 *transaction, 110 error, 111 ) { 112 err := coordinator.Error() 113 if err != nil { 114 return nil, err 115 } 116 117 txn, err := coordinator.database.NewTransaction( 118 request.ExecutionTime(), 119 fvm.ProcedureStateParameters(request.ctx, request)) 120 if err != nil { 121 return nil, err 122 } 123 124 return &transaction{ 125 request: request, 126 coordinator: coordinator, 127 numConflictRetries: attempt, 128 startedAt: time.Now(), 129 Transaction: txn, 130 ProcedureExecutor: coordinator.vm.NewExecutor( 131 request.ctx, 132 request.TransactionProcedure, 133 txn), 134 }, nil 135 } 136 137 func (coordinator *transactionCoordinator) commit(txn *transaction) error { 138 coordinator.mutex.Lock() 139 defer coordinator.mutex.Unlock() 140 141 if coordinator.abortErr != nil { 142 return coordinator.abortErr 143 } 144 145 executionSnapshot, err := txn.Transaction.Commit() 146 if err != nil { 147 return err 148 } 149 150 coordinator.writeBehindLog.AddTransactionResult( 151 txn.request, 152 executionSnapshot, 153 txn.Output(), 154 time.Since(txn.startedAt), 155 txn.numConflictRetries) 156 157 // Commit advances the database's snapshot. 158 coordinator.snapshotTime += 1 159 coordinator.cond.Broadcast() 160 161 return nil 162 } 163 164 func (txn *transaction) Commit() error { 165 return txn.coordinator.commit(txn) 166 } 167 168 func (coordinator *transactionCoordinator) waitForUpdatesNewerThan( 169 snapshotTime logical.Time, 170 ) ( 171 logical.Time, 172 error, 173 logical.Time, 174 error, 175 ) { 176 coordinator.mutex.Lock() 177 defer coordinator.mutex.Unlock() 178 179 startTime := coordinator.snapshotTime 180 startErr := coordinator.abortErr 181 for coordinator.snapshotTime <= snapshotTime && coordinator.abortErr == nil { 182 coordinator.cond.Wait() 183 } 184 185 return startTime, startErr, coordinator.snapshotTime, coordinator.abortErr 186 } 187 188 func (txn *transaction) WaitForUpdates() error { 189 // Note: the frist three returned values are only used by tests to ensure 190 // the function correctly waited. 191 _, _, _, err := txn.coordinator.waitForUpdatesNewerThan(txn.SnapshotTime()) 192 return err 193 }