diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e71ddb..18a1095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` change callbacks has been fixed. ### Added diff --git a/src/value.rs b/src/value.rs index a1a88c1..11d9102 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1227,8 +1227,27 @@ impl Dynamic { } fn lock_inner(&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 == ¤t_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, } diff --git a/src/window.rs b/src/window.rs index 4ffe9ac..76a3914 100644 --- a/src/window.rs +++ b/src/window.rs @@ -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 }