Skip to content

Commit 7b090f6

Browse files
Add a default timeout and a test to ensure that timeout works
1 parent bf46738 commit 7b090f6

File tree

3 files changed

+70
-24
lines changed

3 files changed

+70
-24
lines changed

lib/async_lock.dart

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,31 @@ class AsyncLock<T> {
1515
final bool retainFutureErrors;
1616

1717
/// Executes the given [function] and returns the value, but ensures that
18-
/// only one async function executes at a time.
19-
Future<T> execute() async {
18+
/// only one async function executes at a time. The call defaults to a
19+
/// timeout of 5 minutes.
20+
Future<T> execute([Duration timeout = const Duration(minutes: 5)]) async {
2021
try {
2122
if (_completer != null) {
2223
return _completer!.future;
2324
} else {
2425
_completer = Completer<T>();
2526
}
26-
final result = await function();
27+
28+
final result = await function().timeout(
29+
timeout,
30+
onTimeout: () {
31+
//On timeout complete with an error
32+
final error = TimeoutException('Timeout of $timeout exceeded.');
33+
_completer!.completeError(error);
34+
if (!retainFutureErrors) {
35+
//Clear the completer/error if we're not hanging on to it
36+
//TODO: verify this happens in all cases
37+
_completer = null;
38+
}
39+
throw error;
40+
},
41+
);
42+
2743
_completer!.complete(result);
2844
return result;
2945
// ignore: avoid_catches_without_on_clauses

lib/ioc_container.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,9 @@ extension IocContainerExtensions on IocContainer {
268268
///with [IocContainerBuilder.addAsync] or
269269
///[IocContainerBuilder.addSingletonAsync]. You can only use this on factories
270270
///that return a Future<>.
271-
Future<T> getAsync<T>() async {
271+
Future<T> getAsync<T>([
272+
Duration? timeout = const Duration(minutes: 5),
273+
]) async {
272274
final serviceDefinition = serviceDefinitionsByType[Future<T>];
273275

274276
if (serviceDefinition == null) {
@@ -295,7 +297,7 @@ extension IocContainerExtensions on IocContainer {
295297

296298
try {
297299
//Await the locked call
298-
final future = lock.execute();
300+
final future = timeout != null ? lock.execute(timeout) : lock.execute();
299301
await future;
300302

301303
//Store successful future

test/ioc_container_test.dart

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -526,37 +526,65 @@ void main() {
526526
);
527527
});
528528

529-
test('Test initSafe - Deadlock times out', () async {
530-
var throwException = true;
529+
test('Test Time Out', () async {
530+
var loop = true;
531531

532532
final builder = IocContainerBuilder()
533-
..addSingletonAsync(
534-
(c) async => throwException ? throw Exception() : A('a'),
533+
..addSingletonAsync<A>(
534+
(c) async {
535+
while (loop) {
536+
await Future<void>.delayed(const Duration(milliseconds: 10));
537+
}
538+
return A('a');
539+
},
535540
);
536541

537542
final container = builder.toContainer();
538543

539-
//This causes a deadlock because the future is never completed, so cleanup
540-
//never occurs
541-
expect(() async => container.getAsync<A>(), throwsException);
544+
final future1 = container.getAsync<A>(const Duration(milliseconds: 100));
545+
final future2 = container.getAsync<A>(const Duration(milliseconds: 200));
542546

543-
//We should not have stored the bad future
544-
expect(container.singletons.containsKey(Future<A>), false);
547+
final combinedFutures = Future.wait([future1, future2]);
545548

546-
throwException = false;
549+
await expectLater(combinedFutures, throwsA(isA<Exception>()));
547550

548-
//We can recover
549-
final a = await container.getAsync<A>();
551+
loop = false;
550552

551-
expect(
552-
identical(
553-
a,
554-
await container.getAsync<A>(),
555-
),
556-
true,
557-
);
553+
final a = await container.getAsync<A>();
554+
expect(a.name, 'a');
558555
});
559556

557+
// test('Test initSafe - Deadlock times out', () async {
558+
// var throwException = true;
559+
560+
// final builder = IocContainerBuilder()
561+
// ..addSingletonAsync(
562+
// (c) async => throwException ? throw Exception() : A('a'),
563+
// );
564+
565+
// final container = builder.toContainer();
566+
567+
// //This causes a deadlock because the future is never completed, so cleanup
568+
// //never occurs
569+
// expect(() async => container.getAsync<A>(), throwsException);
570+
571+
// //We should not have stored the bad future
572+
// expect(container.singletons.containsKey(Future<A>), false);
573+
574+
// throwException = false;
575+
576+
// //We can recover
577+
// final a = await container.getAsync<A>();
578+
579+
// expect(
580+
// identical(
581+
// a,
582+
// await container.getAsync<A>(),
583+
// ),
584+
// true,
585+
// );
586+
// });
587+
560588
test('Test initSafe - Recover From Error expectLater', () async {
561589
var throwException = true;
562590

0 commit comments

Comments
 (0)