From 0f76c01b7464d4222020b8e3257d645e81cbd67d Mon Sep 17 00:00:00 2001 From: SpaceBroetchen Date: Fri, 21 Nov 2025 19:19:21 +0100 Subject: [PATCH 1/5] Added unlock functionality to mutex guard and rw lock guards --- library/std/src/sync/nonpoison/mutex.rs | 56 +++++++++++- library/std/src/sync/nonpoison/rwlock.rs | 112 +++++++++++++++++++++-- library/std/tests/sync/mutex.rs | 19 ++++ library/std/tests/sync/rwlock.rs | 38 +++++++- 4 files changed, 211 insertions(+), 14 deletions(-) diff --git a/library/std/src/sync/nonpoison/mutex.rs b/library/std/src/sync/nonpoison/mutex.rs index ed3f8cfed821a..f9ffb99df313f 100644 --- a/library/std/src/sync/nonpoison/mutex.rs +++ b/library/std/src/sync/nonpoison/mutex.rs @@ -98,6 +98,9 @@ unsafe impl Sync for Mutex {} #[cfg_attr(not(test), rustc_diagnostic_item = "NonPoisonMutexGuard")] pub struct MutexGuard<'a, T: ?Sized + 'a> { lock: &'a Mutex, + /// The unlocked state is used to prevent double unlocking of guards upon panicking in + /// unlocked scopes. + unlocked: bool, } /// A [`MutexGuard`] is not `Send` to maximize platform portability. @@ -447,7 +450,10 @@ impl fmt::Debug for Mutex { impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> { unsafe fn new(lock: &'mutex Mutex) -> MutexGuard<'mutex, T> { - return MutexGuard { lock }; + MutexGuard { + lock, + unlocked: false, + } } } @@ -471,8 +477,10 @@ impl DerefMut for MutexGuard<'_, T> { impl Drop for MutexGuard<'_, T> { #[inline] fn drop(&mut self) { - unsafe { - self.lock.inner.unlock(); + if !self.unlocked { + unsafe { + self.lock.inner.unlock(); + } } } } @@ -496,6 +504,48 @@ pub(super) fn guard_lock<'a, T: ?Sized>(guard: &MutexGuard<'a, T>) -> &'a sys::M &guard.lock.inner } +impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> { + /// Unlocks the [`MutexGuard`] for the scope of `func` and acquires it again after. + /// Panics won't lock the guard again. + /// + /// # Examples + /// + /// ``` + /// #[feature(unlockable_guards)] + /// + /// use std::sync::nonpoison::Mutex; + /// use std::sync::nonpoison::MutexGuard; + /// use std::sync::nonpoison::TryLockResult; + /// + /// let mutex = Mutex::new(1usize); + /// let mut guard = mutex.lock(); + /// + /// // guard is locked and can be used here + /// *guard = 5; + /// + /// MutexGuard::unlocked(&mut guard, || { + /// // guard is locked and can be acquired potentially from another thread + /// assert!(matches!(mutex.try_lock(), TryLockResult::Ok(_))); + /// }); + /// + /// // guard is locked again + /// assert_eq!(*guard, 5); + /// ``` + #[unstable(feature = "unlockable_guards", issue = "148568")] + pub fn unlocked(self: &mut Self, func: F) -> () + where + F: FnOnce() -> () + { + self.unlocked = true; + unsafe { self.lock.inner.unlock() }; + + func(); + + self.lock.inner.lock(); + self.unlocked = false; + } +} + impl<'a, T: ?Sized> MutexGuard<'a, T> { /// Makes a [`MappedMutexGuard`] for a component of the borrowed data, e.g. /// an enum variant. diff --git a/library/std/src/sync/nonpoison/rwlock.rs b/library/std/src/sync/nonpoison/rwlock.rs index dc5d9479ba5a9..253187138eff4 100644 --- a/library/std/src/sync/nonpoison/rwlock.rs +++ b/library/std/src/sync/nonpoison/rwlock.rs @@ -81,6 +81,9 @@ pub struct RwLockReadGuard<'rwlock, T: ?Sized + 'rwlock> { data: NonNull, /// A reference to the internal [`sys::RwLock`] that we have read-locked. inner_lock: &'rwlock sys::RwLock, + /// The unlocked state is used to prevent double unlocking of guards upon panicking in + /// unlocked scopes. + unlocked: bool, } #[unstable(feature = "nonpoison_rwlock", issue = "134645")] @@ -107,6 +110,9 @@ unsafe impl Sync for RwLockReadGuard<'_, T> {} pub struct RwLockWriteGuard<'rwlock, T: ?Sized + 'rwlock> { /// A reference to the [`RwLock`] that we have write-locked. lock: &'rwlock RwLock, + /// The unlocked state is used to prevent double unlocking of guards upon panicking in + /// unlocked scopes. + unlocked: bool, } #[unstable(feature = "nonpoison_rwlock", issue = "134645")] @@ -607,6 +613,7 @@ impl<'rwlock, T: ?Sized> RwLockReadGuard<'rwlock, T> { RwLockReadGuard { data: unsafe { NonNull::new_unchecked(lock.data.get()) }, inner_lock: &lock.inner, + unlocked: false, } } @@ -684,7 +691,10 @@ impl<'rwlock, T: ?Sized> RwLockWriteGuard<'rwlock, T> { /// `lock.inner.write()`, `lock.inner.try_write()`, or `lock.inner.try_upgrade` before /// instantiating this object. unsafe fn new(lock: &'rwlock RwLock) -> RwLockWriteGuard<'rwlock, T> { - RwLockWriteGuard { lock } + RwLockWriteGuard { + lock, + unlocked: false, + } } /// Downgrades a write-locked `RwLockWriteGuard` into a read-locked [`RwLockReadGuard`]. @@ -976,9 +986,11 @@ impl<'rwlock, T: ?Sized> MappedRwLockWriteGuard<'rwlock, T> { #[unstable(feature = "nonpoison_rwlock", issue = "134645")] impl Drop for RwLockReadGuard<'_, T> { fn drop(&mut self) { - // SAFETY: the conditions of `RwLockReadGuard::new` were satisfied when created. - unsafe { - self.inner_lock.read_unlock(); + if !self.unlocked { + // SAFETY: the conditions of `RwLockReadGuard::new` were satisfied when created. + unsafe { + self.inner_lock.read_unlock(); + } } } } @@ -986,9 +998,11 @@ impl Drop for RwLockReadGuard<'_, T> { #[unstable(feature = "nonpoison_rwlock", issue = "134645")] impl Drop for RwLockWriteGuard<'_, T> { fn drop(&mut self) { - // SAFETY: the conditions of `RwLockWriteGuard::new` were satisfied when created. - unsafe { - self.lock.inner.write_unlock(); + if !self.unlocked { + // SAFETY: the conditions of `RwLockWriteGuard::new` were satisfied when created. + unsafe { + self.lock.inner.write_unlock(); + } } } } @@ -1138,3 +1152,87 @@ impl fmt::Display for MappedRwLockWriteGuard<'_, T> { (**self).fmt(f) } } + +impl<'rw_lock, T: ?Sized> RwLockReadGuard<'rw_lock, T> { + /// Unlocks the [`RwLockReadGuard`] for the scope of `func` and acquires it again after. + /// Panics won't lock the guard again. + /// + /// # Examples + /// + /// ``` + /// #[feature(unlockable_guards)] + /// + /// use std::sync::nonpoison::RwLock; + /// use std::sync::nonpoison::RwLockReadGuard; + /// use std::sync::nonpoison::TryLockResult; + /// + /// let rw_lock = RwLock::new(1usize); + /// let mut read_guard = rw_lock.read(); + /// + /// // guard is locked and can be used here + /// assert_eq!(*read_guard, 1); + /// + /// RwLockReadGuard::unlocked(&mut read_guard, || { + /// // guard is locked and can be acquired potentially from another thread + /// assert!(matches!(rw_lock.try_write(), TryLockResult::Ok(_))); + /// }); + /// + /// // guard is locked again + /// assert_eq!(*read_guard, 1); + /// ``` + #[unstable(feature = "unlockable_guards", issue = "148568")] + pub fn unlocked(self: &mut Self, func: F) -> () + where + F: FnOnce() -> () + { + self.unlocked = true; + unsafe { self.inner_lock.read_unlock() }; + + func(); + + self.inner_lock.read(); + self.unlocked = false; + } +} + +impl<'rw_lock, T: ?Sized> RwLockWriteGuard<'rw_lock, T> { + /// Unlocks the [`RwLockWriteGuard`] for the scope of `func` and acquires it again after. + /// Panics won't lock the guard again. + /// + /// # Examples + /// + /// ``` + /// #[feature(unlockable_guards)] + /// + /// use std::sync::nonpoison::RwLock; + /// use std::sync::nonpoison::RwLockWriteGuard; + /// use std::sync::nonpoison::TryLockResult; + /// + /// let rw_lock = RwLock::new(1usize); + /// let mut write_guard = rw_lock.write(); + /// + /// // guard is locked and can be used here + /// *write_guard = 5; + /// + /// RwLockWriteGuard::unlocked(&mut write_guard, || { + /// // guard is locked and can be acquired potentially from another thread + /// assert!(matches!(rw_lock.try_write(), TryLockResult::Ok(_))); + /// }); + /// + /// // guard is locked again + /// assert_eq!(*write_guard, 5); + /// ``` + #[unstable(feature = "unlockable_guards", issue = "148568")] + pub fn unlocked(self: &mut Self, func: F) -> () + where + F: FnOnce() -> () + { + self.unlocked = true; + unsafe { self.lock.inner.write_unlock() }; + + func(); + + self.lock.inner.write(); + self.unlocked = false; + } +} diff --git a/library/std/tests/sync/mutex.rs b/library/std/tests/sync/mutex.rs index 75a6bf64607ef..fc0fc740674e9 100644 --- a/library/std/tests/sync/mutex.rs +++ b/library/std/tests/sync/mutex.rs @@ -531,3 +531,22 @@ fn test_mutex_with_mut() { assert_eq!(*mutex.lock(), 5); assert_eq!(result, 10); } + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Nonpoison Tests +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#[test] +fn test_mutex_guard_unlocked() { + let mutex = Mutex::new(1usize); + let mut guard = mutex.lock(); + + + assert!(matches!(mutex.try_lock(), std::sync::nonpoison::TryLockResult::Err(_))); + + MutexGuard::unlocked(&mut guard, || { + assert!(matches!(mutex.try_lock(), std::sync::nonpoison::TryLockResult::Ok(_))); + }); + + assert!(matches!(mutex.try_lock(), std::sync::nonpoison::TryLockResult::Err(_))); +} diff --git a/library/std/tests/sync/rwlock.rs b/library/std/tests/sync/rwlock.rs index 392c45c8ba05d..1943f5f901bf3 100644 --- a/library/std/tests/sync/rwlock.rs +++ b/library/std/tests/sync/rwlock.rs @@ -3,10 +3,7 @@ use std::ops::FnMut; use std::panic::{self, AssertUnwindSafe}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::channel; -use std::sync::{ - Arc, MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, - TryLockError, -}; +use std::sync::{Arc, MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, TryLockError}; use std::{hint, mem, thread}; use rand::Rng; @@ -883,3 +880,36 @@ fn test_rwlock_with_mut() { assert_eq!(*rwlock.read(), 5); assert_eq!(result, 10); } + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Non-poison Tests +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#[test] +fn test_read_guard_unlocked() { + let rw_lock = RwLock::new(1usize); + let mut read_guard = rw_lock.read(); + + assert!(matches!(rw_lock.try_write(), std::sync::nonpoison::TryLockResult::Err(_))); + + RwLockWriteGuard::unlocked(&mut read_guard, || { + assert!(matches!(rw_lock.try_write(), std::sync::nonpoison::TryLockResult::Ok(_))); + }); + + assert!(matches!(rw_lock.try_write(), std::sync::nonpoison::TryLockResult::Err(_))); +} + +#[test] +fn test_write_guard_unlocked() { + let rw_lock = RwLock::new(1usize); + let mut write_guard = rw_lock.write(); + + assert!(matches!(rw_lock.try_write(), std::sync::nonpoison::TryLockResult::Err(_))); + + RwLockWriteGuard::unlocked(&mut write_guard, || { + assert!(matches!(rw_lock.try_write(), std::sync::nonpoison::TryLockResult::Ok(_))); + }); + + assert!(matches!(rw_lock.try_write(), std::sync::nonpoison::TryLockResult::Err(_))); +} From 53533d87fd27e98ea2739dafb12d418a08a7308c Mon Sep 17 00:00:00 2001 From: SpaceBroetchen Date: Fri, 21 Nov 2025 21:10:14 +0100 Subject: [PATCH 2/5] Fixed format for unlockable mutex guards --- library/std/src/sync/nonpoison/mutex.rs | 9 +++------ library/std/src/sync/nonpoison/rwlock.rs | 9 +++------ library/std/tests/sync/mutex.rs | 5 ++--- library/std/tests/sync/rwlock.rs | 6 ++++-- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/library/std/src/sync/nonpoison/mutex.rs b/library/std/src/sync/nonpoison/mutex.rs index f9ffb99df313f..168f6fb5f5487 100644 --- a/library/std/src/sync/nonpoison/mutex.rs +++ b/library/std/src/sync/nonpoison/mutex.rs @@ -450,10 +450,7 @@ impl fmt::Debug for Mutex { impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> { unsafe fn new(lock: &'mutex Mutex) -> MutexGuard<'mutex, T> { - MutexGuard { - lock, - unlocked: false, - } + MutexGuard { lock, unlocked: false, } } } @@ -534,13 +531,13 @@ impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> { #[unstable(feature = "unlockable_guards", issue = "148568")] pub fn unlocked(self: &mut Self, func: F) -> () where - F: FnOnce() -> () + F: FnOnce() -> (), { self.unlocked = true; unsafe { self.lock.inner.unlock() }; func(); - + self.lock.inner.lock(); self.unlocked = false; } diff --git a/library/std/src/sync/nonpoison/rwlock.rs b/library/std/src/sync/nonpoison/rwlock.rs index 253187138eff4..eb013354f67ce 100644 --- a/library/std/src/sync/nonpoison/rwlock.rs +++ b/library/std/src/sync/nonpoison/rwlock.rs @@ -691,10 +691,7 @@ impl<'rwlock, T: ?Sized> RwLockWriteGuard<'rwlock, T> { /// `lock.inner.write()`, `lock.inner.try_write()`, or `lock.inner.try_upgrade` before /// instantiating this object. unsafe fn new(lock: &'rwlock RwLock) -> RwLockWriteGuard<'rwlock, T> { - RwLockWriteGuard { - lock, - unlocked: false, - } + RwLockWriteGuard { lock, unlocked: false } } /// Downgrades a write-locked `RwLockWriteGuard` into a read-locked [`RwLockReadGuard`]. @@ -1183,7 +1180,7 @@ impl<'rw_lock, T: ?Sized> RwLockReadGuard<'rw_lock, T> { #[unstable(feature = "unlockable_guards", issue = "148568")] pub fn unlocked(self: &mut Self, func: F) -> () where - F: FnOnce() -> () + F: FnOnce() -> (), { self.unlocked = true; unsafe { self.inner_lock.read_unlock() }; @@ -1225,7 +1222,7 @@ impl<'rw_lock, T: ?Sized> RwLockWriteGuard<'rw_lock, T> { #[unstable(feature = "unlockable_guards", issue = "148568")] pub fn unlocked(self: &mut Self, func: F) -> () where - F: FnOnce() -> () + F: FnOnce() -> (), { self.unlocked = true; unsafe { self.lock.inner.write_unlock() }; diff --git a/library/std/tests/sync/mutex.rs b/library/std/tests/sync/mutex.rs index fc0fc740674e9..42bc425a2e0f2 100644 --- a/library/std/tests/sync/mutex.rs +++ b/library/std/tests/sync/mutex.rs @@ -541,11 +541,10 @@ fn test_mutex_guard_unlocked() { let mutex = Mutex::new(1usize); let mut guard = mutex.lock(); - assert!(matches!(mutex.try_lock(), std::sync::nonpoison::TryLockResult::Err(_))); - + MutexGuard::unlocked(&mut guard, || { - assert!(matches!(mutex.try_lock(), std::sync::nonpoison::TryLockResult::Ok(_))); + assert!(matches!(mutex.try_lock(), std::sync::nonpoison::TryLockResult::Ok(_))); }); assert!(matches!(mutex.try_lock(), std::sync::nonpoison::TryLockResult::Err(_))); diff --git a/library/std/tests/sync/rwlock.rs b/library/std/tests/sync/rwlock.rs index 1943f5f901bf3..25f89540b4071 100644 --- a/library/std/tests/sync/rwlock.rs +++ b/library/std/tests/sync/rwlock.rs @@ -3,7 +3,10 @@ use std::ops::FnMut; use std::panic::{self, AssertUnwindSafe}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::channel; -use std::sync::{Arc, MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, TryLockError}; +use std::sync::{ + Arc, MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, MutexGuard, RwLock, RwLockReadGuard, + RwLockWriteGuard, TryLockError, +}; use std::{hint, mem, thread}; use rand::Rng; @@ -881,7 +884,6 @@ fn test_rwlock_with_mut() { assert_eq!(result, 10); } - //////////////////////////////////////////////////////////////////////////////////////////////////// // Non-poison Tests //////////////////////////////////////////////////////////////////////////////////////////////////// From b782dc690ec68cc86d7b8f888c2a66c8d4cb87ec Mon Sep 17 00:00:00 2001 From: SpaceBroetchen Date: Fri, 21 Nov 2025 21:19:16 +0100 Subject: [PATCH 3/5] Fixed format for unlockable mutex guards --- library/std/src/sync/nonpoison/mutex.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/sync/nonpoison/mutex.rs b/library/std/src/sync/nonpoison/mutex.rs index 168f6fb5f5487..032ff59a4302d 100644 --- a/library/std/src/sync/nonpoison/mutex.rs +++ b/library/std/src/sync/nonpoison/mutex.rs @@ -450,7 +450,7 @@ impl fmt::Debug for Mutex { impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> { unsafe fn new(lock: &'mutex Mutex) -> MutexGuard<'mutex, T> { - MutexGuard { lock, unlocked: false, } + MutexGuard { lock, unlocked: false } } } From 9ce5968df796e322eae71edf767088fa3ebd958a Mon Sep 17 00:00:00 2001 From: SpaceBroetchen Date: Fri, 21 Nov 2025 23:19:18 +0100 Subject: [PATCH 4/5] Fixed tests for unlockable mutex guards --- library/std/tests/sync/mutex.rs | 2 +- library/std/tests/sync/rwlock.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/std/tests/sync/mutex.rs b/library/std/tests/sync/mutex.rs index 42bc425a2e0f2..373b37b78ed0d 100644 --- a/library/std/tests/sync/mutex.rs +++ b/library/std/tests/sync/mutex.rs @@ -538,7 +538,7 @@ fn test_mutex_with_mut() { #[test] fn test_mutex_guard_unlocked() { - let mutex = Mutex::new(1usize); + let mutex = std::sync::nonpoison::Mutex::new(1usize); let mut guard = mutex.lock(); assert!(matches!(mutex.try_lock(), std::sync::nonpoison::TryLockResult::Err(_))); diff --git a/library/std/tests/sync/rwlock.rs b/library/std/tests/sync/rwlock.rs index 25f89540b4071..99406ce1b8039 100644 --- a/library/std/tests/sync/rwlock.rs +++ b/library/std/tests/sync/rwlock.rs @@ -4,8 +4,8 @@ use std::panic::{self, AssertUnwindSafe}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::channel; use std::sync::{ - Arc, MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, MutexGuard, RwLock, RwLockReadGuard, - RwLockWriteGuard, TryLockError, + Arc, MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, + TryLockError }; use std::{hint, mem, thread}; @@ -890,12 +890,12 @@ fn test_rwlock_with_mut() { #[test] fn test_read_guard_unlocked() { - let rw_lock = RwLock::new(1usize); + let rw_lock = std::sync::nonpoison::RwLock::new(1usize); let mut read_guard = rw_lock.read(); assert!(matches!(rw_lock.try_write(), std::sync::nonpoison::TryLockResult::Err(_))); - RwLockWriteGuard::unlocked(&mut read_guard, || { + RwLockReadGuard::unlocked(&mut read_guard, || { assert!(matches!(rw_lock.try_write(), std::sync::nonpoison::TryLockResult::Ok(_))); }); @@ -904,7 +904,7 @@ fn test_read_guard_unlocked() { #[test] fn test_write_guard_unlocked() { - let rw_lock = RwLock::new(1usize); + let rw_lock = std::sync::nonpoison::RwLock::new(1usize); let mut write_guard = rw_lock.write(); assert!(matches!(rw_lock.try_write(), std::sync::nonpoison::TryLockResult::Err(_))); From a1aa681d0d3f24b5eb69fe8651d14ab83af38557 Mon Sep 17 00:00:00 2001 From: SpaceBroetchen Date: Fri, 21 Nov 2025 23:28:12 +0100 Subject: [PATCH 5/5] Fixed format for unlockable mutex guards --- library/std/tests/sync/rwlock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/tests/sync/rwlock.rs b/library/std/tests/sync/rwlock.rs index 99406ce1b8039..c743c95cfd4d3 100644 --- a/library/std/tests/sync/rwlock.rs +++ b/library/std/tests/sync/rwlock.rs @@ -5,7 +5,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::channel; use std::sync::{ Arc, MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, - TryLockError + TryLockError, }; use std::{hint, mem, thread};