blob: 9aff468435b96542287f902ec5156eaa6ff479f9 [file] [log] [blame]
// Copyright (c) 2021, 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 'dart:collection';
/// Used to protect global state accessed in blocks containing calls to
/// asynchronous methods.
class Mutex {
/// Executes a block of code containing asynchronous calls atomically.
///
/// If no other asynchronous context is currently executing within
/// [criticalSection] or a [runGuardedWeak] scope, it will immediately be
/// called. Otherwise, the caller will be suspended and entered into a queue
/// to be resumed once the lock is released.
Future<T> runGuarded<T>(FutureOr<T> Function() criticalSection) async {
try {
await _acquireLock();
return await criticalSection();
} finally {
_releaseLock();
}
}
/// Executes a block of code containing asynchronous calls, allowing for other
/// weakly guarded sections to be executed concurrently.
///
/// If no other asynchronous context is currently executing within a
/// [runGuarded] scope, [criticalSection] will immediately be called.
/// Otherwise, the caller will be suspended and entered into a queue to be
/// resumed once the lock is released.
Future<T> runGuardedWeak<T>(FutureOr<T> Function() criticalSection) async {
_weakGuards++;
if (_weakGuards == 1) {
// Reinitialize if this is the only weakly guarded scope.
_outstandingReadersCompleter = Completer<void>();
}
final result;
try {
await _acquireLock(strong: false);
result = await criticalSection();
} finally {
_weakGuards--;
if (_weakGuards == 0) {
// Notify callers of `runGuarded` that they can try to execute again.
_outstandingReadersCompleter.complete();
}
}
return result;
}
Future<void> _acquireLock({bool strong = true}) async {
// The lock cannot be acquired by `runGuarded` if there is outstanding
// execution in weakly guarded sections. Loop in case we've entered another
// weakly guarded scope before we've woken up.
while (strong && _weakGuards > 0) {
await _outstandingReadersCompleter.future;
}
if (!_locked) {
if (strong) {
// Don't actually lock for weakly guarded sections, just make sure the
// lock isn't held before entering.
_locked = true;
}
return;
}
final request = Completer<void>();
_outstandingRequests.add(request);
await request.future;
}
void _releaseLock() {
_locked = false;
if (_outstandingRequests.isNotEmpty) {
final request = _outstandingRequests.removeFirst();
request.complete();
}
}
int _weakGuards = 0;
bool _locked = false;
var _outstandingReadersCompleter = Completer<void>();
final _outstandingRequests = Queue<Completer<void>>();
}