Fixed change callback deadlock

Very hard to intentionally reproduce, thankfully the second time I saw
it I was in the debugger and was able to reason about the code path that
could have gotten in that particular state. Comment explains the actual
situation.
This commit is contained in:
Jonathan Johnson 2024-09-25 11:13:15 -07:00
parent 8561b6cf5e
commit e54bbd743d
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
3 changed files with 23 additions and 2 deletions

View file

@ -87,6 +87,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- When a keyboard-activated widget activates another widget during its callback,
the key-up event now sends the deactivate event to the finally-activated
widget.
- A rare deadlock occurring when multiple threads were racing to execute
`Dynamic<T>` change callbacks has been fixed.
### Added

View file

@ -1227,8 +1227,27 @@ impl<T> Dynamic<T> {
}
fn lock_inner<const READONLY: bool>(&self) -> DynamicGuard<'_, T, READONLY> {
let 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,
};
}
}
DynamicGuard {
guard: DynamicOrOwnedGuard::Dynamic(self.0.state().expect("deadlocked")),
guard: DynamicOrOwnedGuard::Dynamic(guard),
accessed_mut: false,
prevent_notifications: false,
}

View file

@ -1819,7 +1819,7 @@ where
this.synchronize_platform_window(&mut window);
// Perform an initial layout.
let _result = this.prepare(window, graphics);
this.prepare(window, graphics);
this
}