The MultiLock class provides a mechanism to acquire multiple locks together as a single atomic operation.
Critical Requirement: Idempotent Iterables
When passing an Iterable of locks to MultiLock, the iterable must be idempotent. This means every time the iterable is traversed, it must return the exact same set of Lock instances.
If you pass a lazy iterable that generates new lock instances on every iteration (for example, using .map() to create new objects), MultiLock will fail to provide mutual exclusion because it will attempt to acquire different locks during its internal acquisition and release phases.
Best Practice: Always pass a stable collection, such as a list literal or a pre-computed list, to ensure the same locks are used throughout the lifecycle of the multi-lock operation.
// CORRECT: Using a list literal (stable/idempotent)
await MultiLock([lockA, lockB]).synchronized(() async {
// protected code
});
// CORRECT: Using a stable mapping over existing objects
final locks = owners.map((o) => o.lock).toList();
await MultiLock(locks).synchronized(() async {
// protected code
});
// INCORRECT: Lazy iterable that creates new locks every iteration
// This will result in a silent loss of mutual exclusion.
await MultiLock(base.map((e) => Lock())).synchronized(() async {
// This code is NOT protected!
});