Skip to main content

Module mutable

Module mutable 

Source
Expand description

The Mutable abstraction over the single-threaded and the multi-threaded backend, the handful of traits that are the only sanctioned way to reach through it, and the rules a callback has to follow.

§Rule 1: the guard must not outlive the operation

Refer to this case: https://stackoverflow.com/q/79621758/9315497 And this case:

let lock = Mutex::new("My String".to_owned());
// let equals = { lock.lock().unwrap().clone() } == { lock.lock().unwrap().clone() }; // No deadlock
let equals = lock.lock().unwrap().clone() == lock.lock().unwrap().clone(); // Deadlock

Both guards are temporaries of the same statement, so the first one is still alive when the second is taken. MutableHelper::with_mut and with_ref rule this out structurally: they hand the callback a &mut T / &T, keep the guard as a temporary of their own body, and release it before returning. A guard can no longer be named, stored or compared.

§Rule 2: the callback must not run anything that can take the same lock again

This is the rule the type system cannot enforce, and it is the one that bites. Dropping a value counts as running code: the obvious clear on a collection of subscriptions

self.subscriptions.with_mut(Vec::clear) // Deadlock: dropping a Subscription disposes it,
                                        // and disposing it takes this very lock again.

is a deadlock in the Disposable of merge_all. So is calling an Observer or a Disposable from inside the callback, because both are user code that can re-enter the operator.

The fix is always the same shape — take the value out under the lock, act on it afterwards:

  • MutableExt::take_value instead of clear, and MutableExt::replace_value instead of an assignment: both return the value they displaced, so the caller acts on it — and drops it — outside the lock.
  • When the operator has to decide what to do while holding the lock, let the callback compute an action and return it, and run that action after with_mut has returned. This is what ref_count, unicast_subject, amb and serialized_delivery do.

In debug builds this rule is checked at runtime: taking a lock the current thread already holds panics at the offending call site instead of deadlocking.

Traits§

MutableBoolHelper
MutableExt
The handful of one-shot operations that cover most uses of a Mutable.
MutableHelper
The single entry point to a Mutable, for both the single-threaded and the multi-threaded backend.

Type Aliases§

Mutable
MutableBool