Fixed deadlock with DynamicMutexGuard::unlocked

During the change callback process, unlocked is called to allow the
change callbacks to run while the dynamic is unlocked. The error with
the previous version of this code is that the during_callback_state was
always overwritten when being returned. The problem is that another
thread could currently have the mutex locked and could have stored its
own state -- which can happen if two threads are both trying to invoke
change callbacks at the same time.

By moving the state saving and reloading to only happen when the mutex
guard is acquired, we can ensure that interleaving threads will work
correctly.
This commit is contained in:
Jonathan Johnson 2024-06-13 09:12:10 -07:00
parent f61cb2b91b
commit 6a1f7f5462
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
2 changed files with 6 additions and 5 deletions

View file

@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
window.
- `CallbackHandle` now has a `must_use` hint that might help users discover the
persist function.
- Fixed a deadlock that could occur when multiple threads were attempting to
execute change callbacks for the same dynamic at the same time.
### Added

View file

@ -1437,11 +1437,10 @@ where
impl<'a, T> DynamicMutexGuard<'a, T> {
fn unlocked(&mut self, while_unlocked: impl FnOnce()) {
MutexGuard::unlocked(&mut self.guard, || {
let current_state = self.dynamic.during_callback_state.lock().take();
while_unlocked();
*self.dynamic.during_callback_state.lock() = current_state;
});
let previous_state = self.dynamic.during_callback_state.lock().take();
MutexGuard::unlocked(&mut self.guard, while_unlocked);
*self.dynamic.during_callback_state.lock() = previous_state;
}
}