executeWithLock executes a function with distributed locks to ensure exclusive access to both source and destination balances. It resolves balance IDs first (handling @world indicators), then acquires locks in deterministic order to prevent deadlocks. Parameters: - ctx context.Context: The context
(ctx context.Context, transaction *model.Transaction, fn func(context.Context) (*model.Transaction, error))
| 1154 | // - *model.Transaction: A pointer to the Transaction model returned by the function. |
| 1155 | // - error: An error if the locks could not be acquired or if the function execution fails. |
| 1156 | func (l *LedgerForge) executeWithLock(ctx context.Context, transaction *model.Transaction, fn func(context.Context) (*model.Transaction, error)) (*model.Transaction, error) { |
| 1157 | ctx, span := tracer.Start(ctx, "ExecuteWithLock") |
| 1158 | defer span.End() |
| 1159 | |
| 1160 | // First resolve balance IDs (handle @ indicators) BEFORE acquiring locks |
| 1161 | // This ensures we lock on the correct balance IDs |
| 1162 | sourceID, destID, err := l.resolveBalanceIDs(ctx, transaction) |
| 1163 | if err != nil { |
| 1164 | span.RecordError(err) |
| 1165 | return nil, fmt.Errorf("failed to resolve balance IDs: %w", err) |
| 1166 | } |
| 1167 | |
| 1168 | // Update transaction with resolved IDs |
| 1169 | transaction.Source = sourceID |
| 1170 | transaction.Destination = destID |
| 1171 | |
| 1172 | // Acquire distributed locks for both source and destination balances |
| 1173 | // MultiLocker handles deduplication (if source == destination) and sorts keys lexicographically |
| 1174 | locker, err := l.acquireLock(ctx, sourceID, destID) |
| 1175 | if err != nil { |
| 1176 | hotpairs.RecordContention(ctx, l.hotPairs, sourceID, destID, transaction.Currency, err) |
| 1177 | metrics.HotpairsContentionTotal.Add(ctx, 1) |
| 1178 | span.RecordError(err) |
| 1179 | return nil, fmt.Errorf("failed to acquire lock: %w", err) |
| 1180 | } |
| 1181 | defer l.releaseLock(ctx, locker) |
| 1182 | |
| 1183 | // Execute the provided function with the locks held |
| 1184 | // The function will re-fetch balances to get fresh state after lock acquisition |
| 1185 | return fn(ctx) |
| 1186 | } |
| 1187 | |
| 1188 | // validateAndPrepareTransaction validates the transaction and prepares it by retrieving the source and destination balances. |
| 1189 | // It starts a tracing span, validates the transaction, retrieves the balances, and updates the transaction with the balance IDs. |
no test coverage detected