Line data Source code
1 : import 'dart:async';
2 :
3 : /// A lock that ensures that only one async function executes at a time.
4 : class AsyncLock<T> {
5 : /// Creates a new [AsyncLock].
6 1 : AsyncLock(this.function, {this.retainFutureErrors = false});
7 :
8 : /// The function to execute.
9 : final Future<T> Function() function;
10 :
11 : Completer<T>? _completer;
12 :
13 : /// Whether to retain errors or allow reentrancy until the Future completes
14 : /// successfully.
15 : final bool retainFutureErrors;
16 :
17 : /// Executes the given [function] and returns the value, but ensures that
18 : /// only one async function executes at a time. The call defaults to a
19 : /// timeout of 5 minutes.
20 1 : Future<T> execute([Duration timeout = const Duration(minutes: 5)]) async {
21 : try {
22 1 : if (_completer != null) {
23 2 : return _completer!.future;
24 : } else {
25 2 : _completer = Completer<T>();
26 : }
27 :
28 3 : final result = await function().timeout(
29 : timeout,
30 1 : onTimeout: () {
31 : //On timeout complete with an error
32 2 : final error = TimeoutException('Timeout of $timeout exceeded.');
33 2 : _completer!.completeError(error);
34 1 : if (!retainFutureErrors) {
35 : //Clear the completer/error if we're not hanging on to it
36 : //TODO: verify this happens in all cases
37 1 : _completer = null;
38 : }
39 : throw error;
40 : },
41 : );
42 :
43 2 : _completer!.complete(result);
44 : return result;
45 : // ignore: avoid_catches_without_on_clauses
46 : } catch (error, stackTrace) {
47 : // coverage:ignore-start
48 : //This is only here for some potential future use cases
49 : if (retainFutureErrors) {
50 : _completer!.completeError(error, stackTrace);
51 : // coverage:ignore-end
52 : } else {
53 1 : _completer = null;
54 : }
55 :
56 : rethrow;
57 : }
58 : }
59 : }
|