Skip to content

Commit

Permalink
Allowing change callbacks to recurse still
Browse files Browse the repository at this point in the history
The last fix had a panic where a guard should have been allowed to be
created. Change callbacks detect when they are about to be fired from
themselves, and prevent the deadlock.
  • Loading branch information
ecton committed Sep 25, 2024
1 parent e54bbd7 commit 2045d5f
Showing 1 changed file with 13 additions and 10 deletions.
23 changes: 13 additions & 10 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1227,24 +1227,27 @@ impl<T> Dynamic<T> {
}

fn lock_inner<const READONLY: bool>(&self) -> DynamicGuard<'_, T, READONLY> {
let guard = self.0.state().expect("deadlocked");
let mut guard = self.0.state().expect("deadlocked");
// Before allowing a lock, we need to ensure that the current change
// callbacks aren't executing. Otherwise, during drop of this guard, if
// we notify of changes from a second thread than one set is already
// occuring on, both sets of invocations can end up waiting on each
// other and deadlocking. By ensuring a single guard and change
// callbacks cycle can exist at any one time, we prevent this deadlock.
if !READONLY && guard.callbacks.currently_executing.lock().thread.is_some() {
let current_thread_id = std::thread::current().id();
let callbacks = guard.callbacks.clone();
let mut executing = callbacks.currently_executing.lock();
loop {
match &executing.thread {
Some(th) if th == &current_thread_id => panic!("deadlocked"),
Some(_) => callbacks.sync.wait(&mut executing),
None => break,
};
}
guard.unlocked(|| {
let current_thread_id = std::thread::current().id();
let mut executing = callbacks.currently_executing.lock();

loop {
match &executing.thread {
Some(th) if th == &current_thread_id => break,
None => break,
Some(_) => callbacks.sync.wait(&mut executing),
};
}
});
}
DynamicGuard {
guard: DynamicOrOwnedGuard::Dynamic(guard),
Expand Down

0 comments on commit 2045d5f

Please sign in to comment.