blob: 9792d902a39bc0a189bc2c3e13a7b85ee12c8c77 [file] [edit]
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:dart_runtime_service/dart_runtime_service.dart';
import 'package:test/test.dart';
void main() {
group('Mutex', () {
test('runGuarded executes critical sections in FIFO order', () async {
final mutex = Mutex();
final events = <String>[];
final completer1 = Completer<void>();
final completer2 = Completer<void>();
final future1 = mutex.runGuarded(() async {
events.add('start1');
await completer1.future;
events.add('end1');
return 1;
});
final future2 = mutex.runGuarded(() async {
events.add('start2');
await completer2.future;
events.add('end2');
return 2;
});
final future3 = mutex.runGuarded(() {
events.add('start3');
events.add('end3');
return 3;
});
await pumpEventQueue();
expect(events, ['start1']);
completer1.complete();
await pumpEventQueue();
expect(events, ['start1', 'end1', 'start2']);
completer2.complete();
final results = await Future.wait([future1, future2, future3]);
expect(results, [1, 2, 3]);
expect(events, ['start1', 'end1', 'start2', 'end2', 'start3', 'end3']);
});
test(
'runGuardedWeak allows concurrent weak guards and blocks strong guards',
() async {
final mutex = Mutex();
final events = <String>[];
final weakCompleter1 = Completer<void>();
final weakCompleter2 = Completer<void>();
final weak1 = mutex.runGuardedWeak(() async {
events.add('weak1_start');
await weakCompleter1.future;
events.add('weak1_end');
});
final weak2 = mutex.runGuardedWeak(() async {
events.add('weak2_start');
await weakCompleter2.future;
events.add('weak2_end');
});
await pumpEventQueue();
expect(events, ['weak1_start', 'weak2_start']);
final strong = mutex.runGuarded(() {
events.add('strong');
});
await pumpEventQueue();
expect(events, ['weak1_start', 'weak2_start']);
weakCompleter1.complete();
await pumpEventQueue();
expect(events, ['weak1_start', 'weak2_start', 'weak1_end']);
weakCompleter2.complete();
await Future.wait([weak1, weak2, strong]);
expect(events, [
'weak1_start',
'weak2_start',
'weak1_end',
'weak2_end',
'strong',
]);
},
);
test('runGuardedWeak queued behind runGuarded does not deadlock subsequent '
'callers', () async {
final mutex = Mutex();
final events = <String>[];
final strong1Completer = Completer<void>();
final weak1Completer = Completer<void>();
final weak2Completer = Completer<void>();
final strong1 = mutex.runGuarded(() async {
events.add('strong1_start');
await strong1Completer.future;
events.add('strong1_end');
});
await pumpEventQueue();
expect(events, ['strong1_start']);
// Queue two weak guards and a second strong guard behind strong1.
final weak1 = mutex.runGuardedWeak(() async {
events.add('weak1_start');
await weak1Completer.future;
events.add('weak1_end');
});
final weak2 = mutex.runGuardedWeak(() async {
events.add('weak2_start');
await weak2Completer.future;
events.add('weak2_end');
});
final strong2 = mutex.runGuarded(() {
events.add('strong2');
});
await pumpEventQueue();
expect(events, ['strong1_start']);
// Release strong1; both weak1 and weak2 should start concurrently,
// while strong2 must wait until both weak1 and weak2 complete.
strong1Completer.complete();
await pumpEventQueue();
expect(events, [
'strong1_start',
'strong1_end',
'weak1_start',
'weak2_start',
]);
weak1Completer.complete();
await pumpEventQueue();
expect(events, [
'strong1_start',
'strong1_end',
'weak1_start',
'weak2_start',
'weak1_end',
]);
weak2Completer.complete();
await Future.wait([
strong1,
weak1,
weak2,
strong2,
]).timeout(const Duration(seconds: 5));
expect(events, [
'strong1_start',
'strong1_end',
'weak1_start',
'weak2_start',
'weak1_end',
'weak2_end',
'strong2',
]);
});
test('releases lock when criticalSection throws in runGuarded or '
'runGuardedWeak', () async {
final mutex = Mutex();
await expectLater(
mutex.runGuarded<void>(() => throw StateError('strong failure')),
throwsStateError,
);
await expectLater(
mutex.runGuardedWeak<void>(() => throw StateError('weak failure')),
throwsStateError,
);
final result = await mutex
.runGuarded(() => 'recovered')
.timeout(const Duration(seconds: 5));
expect(result, 'recovered');
});
});
}