1import 'dart:async'; 2 3import '../runtime/runtime_implementation.dart'; 4 5/// A map storing active locks by name. 6/// 7/// Each lock is represented as a Future that completes when the lock is released. 8/// This allows queuing of operations waiting for the same lock. 9final Map<Object, Future<void>> _locks = {}; 10 11/// Acquires a lock for the given name. 12/// 13/// Returns a function that releases the lock when called. 14/// The lock is automatically added to the queue of pending operations. 15/// 16/// This implements a fair (FIFO) mutex pattern where operations are executed 17/// in the order they acquire the lock. 18Future<void Function()> _acquireLocalLock(Object name) { 19 final completer = Completer<void Function()>(); 20 21 // Get the previous lock in the queue (or a resolved promise if none) 22 final prev = _locks[name] ?? Future.value(); 23 24 // Create a completer for the release function 25 final releaseCompleter = Completer<void>(); 26 27 // Chain onto the previous lock 28 final next = prev.then((_) { 29 // This runs when we've acquired the lock 30 return releaseCompleter.future; 31 }); 32 33 // Store our lock as the new tail of the queue 34 _locks[name] = next; 35 36 // Resolve the acquire promise with the release function 37 prev.then((_) { 38 void release() { 39 // Only delete the lock if it's still the current one 40 // (it might have been replaced by a newer lock) 41 if (_locks[name] == next) { 42 _locks.remove(name); 43 } 44 45 // Complete the release, allowing the next operation to proceed 46 if (!releaseCompleter.isCompleted) { 47 releaseCompleter.complete(); 48 } 49 } 50 51 completer.complete(release); 52 }); 53 54 return completer.future; 55} 56 57/// Executes a function while holding a named lock. 58/// 59/// This is a local (in-memory) lock implementation that prevents concurrent 60/// execution of the same operation within a single isolate/process. 61/// 62/// The lock is automatically released when the function completes or throws an error. 63/// 64/// Example: 65/// ```dart 66/// final result = await requestLocalLock('my-operation', () async { 67/// // Only one execution at a time for 'my-operation' 68/// return await performCriticalOperation(); 69/// }); 70/// ``` 71/// 72/// Use cases: 73/// - Token refresh (prevent multiple simultaneous refresh requests) 74/// - Database transactions 75/// - File operations 76/// - Any operation that must not run concurrently with itself 77/// 78/// Note: This is an in-memory lock. It does not work across: 79/// - Multiple isolates 80/// - Multiple processes 81/// - Multiple app instances 82/// 83/// For cross-process locking, implement a platform-specific RuntimeLock. 84Future<T> requestLocalLock<T>(String name, FutureOr<T> Function() fn) async { 85 // Acquire the lock and get the release function 86 final release = await _acquireLocalLock(name); 87 88 try { 89 // Execute the function while holding the lock 90 return await fn(); 91 } finally { 92 // Always release the lock, even if the function throws 93 release(); 94 } 95} 96 97/// Convenience getter that returns the requestLocalLock function as a RuntimeLock. 98/// 99/// This can be used as the default implementation for RuntimeImplementation.requestLock. 100RuntimeLock get requestLocalLockImpl => requestLocalLock;