Main coves client
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;