Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add arc_lock feature, with guards that can be accessed from inside of Arc #291

Merged
merged 7 commits into from Aug 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Expand Up @@ -17,7 +17,7 @@ jobs:
matrix:
os: [ubuntu, macos, windows]
channel: [1.36.0, stable, beta, nightly]
feature: [serde, deadlock_detection]
feature: [arc_lock, serde, deadlock_detection]
exclude:
- feature: deadlock_detection
channel: '1.36.0'
Expand Down Expand Up @@ -53,7 +53,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: rustup default nightly
- run: cargo doc --workspace --features serde,deadlock_detection --no-deps -p parking_lot -p parking_lot_core -p lock_api
- run: cargo doc --workspace --features arc_lock,serde,deadlock_detection --no-deps -p parking_lot -p parking_lot_core -p lock_api
benchmark:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -23,6 +23,7 @@ bincode = "1.3.0"

[features]
default = []
arc_lock = ["lock_api/arc_lock"]
owning_ref = ["lock_api/owning_ref"]
nightly = ["parking_lot_core/nightly", "lock_api/nightly"]
deadlock_detection = ["parking_lot_core/deadlock_detection"]
Expand Down
1 change: 1 addition & 0 deletions lock_api/Cargo.toml
Expand Up @@ -20,3 +20,4 @@ serde = { version = "1.0.114", default-features = false, optional = true }

[features]
nightly = []
arc_lock = []
7 changes: 6 additions & 1 deletion lock_api/src/lib.rs
Expand Up @@ -79,9 +79,11 @@
//!
//! # Cargo features
//!
//! This crate supports two cargo features:
//! This crate supports three cargo features:
//!
//! - `owning_ref`: Allows your lock types to be used with the `owning_ref` crate.
//! - `arc_lock`: Enables locking from an `Arc`. This enables types such as `ArcMutexGuard`. Note that this
//! requires the `alloc` crate to be present.
//! - `nightly`: Enables nightly-only features. At the moment the only such
//! feature is `const fn` constructors for lock types.

Expand All @@ -93,6 +95,9 @@
#[macro_use]
extern crate scopeguard;

#[cfg(feature = "arc_lock")]
extern crate alloc;

/// Marker type which indicates that the Guard type for a lock is `Send`.
pub struct GuardSend(());

Expand Down
189 changes: 189 additions & 0 deletions lock_api/src/mutex.rs
Expand Up @@ -11,6 +11,13 @@ use core::marker::PhantomData;
use core::mem;
use core::ops::{Deref, DerefMut};

#[cfg(feature = "arc_lock")]
use alloc::sync::Arc;
#[cfg(feature = "arc_lock")]
use core::mem::ManuallyDrop;
#[cfg(feature = "arc_lock")]
use core::ptr;

#[cfg(feature = "owning_ref")]
use owning_ref::StableAddress;

Expand Down Expand Up @@ -286,6 +293,45 @@ impl<R: RawMutex, T: ?Sized> Mutex<R, T> {
pub fn data_ptr(&self) -> *mut T {
self.data.get()
}

/// # Safety
///
/// The lock needs to be held for the behavior of this function to be defined.
#[cfg(feature = "arc_lock")]
#[inline]
unsafe fn guard_arc(self: &Arc<Self>) -> ArcMutexGuard<R, T> {
ArcMutexGuard {
mutex: self.clone(),
marker: PhantomData,
}
}

/// Acquires a lock through an `Arc`.
///
/// This method is similar to the `lock` method; however, it requires the `Mutex` to be inside of an `Arc`
/// and the resulting mutex guard has no lifetime requirements.
#[cfg(feature = "arc_lock")]
#[inline]
pub fn lock_arc(self: &Arc<Self>) -> ArcMutexGuard<R, T> {
self.raw.lock();
// SAFETY: the locking guarantee is upheld
unsafe { self.guard_arc() }
}

/// Attempts to acquire a lock through an `Arc`.
///
/// This method is similar to the `try_lock` method; however, it requires the `Mutex` to be inside of an
/// `Arc` and the resulting mutex guard has no lifetime requirements.
#[cfg(feature = "arc_lock")]
#[inline]
pub fn try_lock_arc(self: &Arc<Self>) -> Option<ArcMutexGuard<R, T>> {
if self.raw.try_lock() {
// SAFETY: locking guarantee is upheld
Some(unsafe { self.guard_arc() })
} else {
None
}
}
}

impl<R: RawMutexFair, T: ?Sized> Mutex<R, T> {
Expand Down Expand Up @@ -336,6 +382,39 @@ impl<R: RawMutexTimed, T: ?Sized> Mutex<R, T> {
None
}
}

/// Attempts to acquire this lock through an `Arc` until a timeout is reached.
///
/// This method is similar to the `try_lock_for` method; however, it requires the `Mutex` to be inside of an
/// `Arc` and the resulting mutex guard has no lifetime requirements.
#[cfg(feature = "arc_lock")]
#[inline]
pub fn try_lock_arc_for(self: &Arc<Self>, timeout: R::Duration) -> Option<ArcMutexGuard<R, T>> {
if self.raw.try_lock_for(timeout) {
// SAFETY: locking guarantee is upheld
Some(unsafe { self.guard_arc() })
} else {
None
}
}

/// Attempts to acquire this lock through an `Arc` until a timeout is reached.
///
/// This method is similar to the `try_lock_until` method; however, it requires the `Mutex` to be inside of
/// an `Arc` and the resulting mutex guard has no lifetime requirements.
#[cfg(feature = "arc_lock")]
#[inline]
pub fn try_lock_arc_until(
self: &Arc<Self>,
timeout: R::Instant,
) -> Option<ArcMutexGuard<R, T>> {
if self.raw.try_lock_until(timeout) {
// SAFETY: locking guarantee is upheld
Some(unsafe { self.guard_arc() })
} else {
None
}
}
}

impl<R: RawMutex, T: ?Sized + Default> Default for Mutex<R, T> {
Expand Down Expand Up @@ -583,6 +662,116 @@ impl<'a, R: RawMutex + 'a, T: fmt::Display + ?Sized + 'a> fmt::Display for Mutex
#[cfg(feature = "owning_ref")]
unsafe impl<'a, R: RawMutex + 'a, T: ?Sized + 'a> StableAddress for MutexGuard<'a, R, T> {}

/// An RAII mutex guard returned by the `Arc` locking operations on `Mutex`.
///
/// This is similar to the `MutexGuard` struct, except instead of using a reference to unlock the `Mutex` it
/// uses an `Arc<Mutex>`. This has several advantages, most notably that it has an `'static` lifetime.
#[cfg(feature = "arc_lock")]
#[must_use = "if unused the Mutex will immediately unlock"]
pub struct ArcMutexGuard<R: RawMutex, T: ?Sized> {
mutex: Arc<Mutex<R, T>>,
marker: PhantomData<R::GuardMarker>,
}

#[cfg(feature = "arc_lock")]
impl<R: RawMutex, T: ?Sized> ArcMutexGuard<R, T> {
/// Returns a reference to the `Mutex` this is guarding, contained in its `Arc`.
#[inline]
pub fn mutex(&self) -> &Arc<Mutex<R, T>> {
&self.mutex
}

/// Temporarily unlocks the mutex to execute the given function.
///
/// This is safe because `&mut` guarantees that there exist no other
/// references to the data protected by the mutex.
#[inline]
pub fn unlocked<F, U>(s: &mut Self, f: F) -> U
where
F: FnOnce() -> U,
{
// Safety: A MutexGuard always holds the lock.
unsafe {
s.mutex.raw.unlock();
}
defer!(s.mutex.raw.lock());
f()
}
}

#[cfg(feature = "arc_lock")]
impl<R: RawMutexFair, T: ?Sized> ArcMutexGuard<R, T> {
/// Unlocks the mutex using a fair unlock protocol.
///
/// This is functionally identical to the `unlock_fair` method on [`MutexGuard`].
#[inline]
pub fn unlock_fair(s: Self) {
// Safety: A MutexGuard always holds the lock.
unsafe {
s.mutex.raw.unlock_fair();
}

// SAFETY: make sure the Arc gets it reference decremented
let mut s = ManuallyDrop::new(s);
unsafe { ptr::drop_in_place(&mut s.mutex) };
}

/// Temporarily unlocks the mutex to execute the given function.
///
/// This is functionally identical to the `unlocked_fair` method on [`MutexGuard`].
#[inline]
pub fn unlocked_fair<F, U>(s: &mut Self, f: F) -> U
where
F: FnOnce() -> U,
{
// Safety: A MutexGuard always holds the lock.
unsafe {
s.mutex.raw.unlock_fair();
}
defer!(s.mutex.raw.lock());
f()
}

/// Temporarily yields the mutex to a waiting thread if there is one.
///
/// This is functionally identical to the `bump` method on [`MutexGuard`].
#[inline]
pub fn bump(s: &mut Self) {
// Safety: A MutexGuard always holds the lock.
unsafe {
s.mutex.raw.bump();
}
}
}

#[cfg(feature = "arc_lock")]
impl<R: RawMutex, T: ?Sized> Deref for ArcMutexGuard<R, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.mutex.data.get() }
}
}

#[cfg(feature = "arc_lock")]
impl<R: RawMutex, T: ?Sized> DerefMut for ArcMutexGuard<R, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.mutex.data.get() }
}
}

#[cfg(feature = "arc_lock")]
impl<R: RawMutex, T: ?Sized> Drop for ArcMutexGuard<R, T> {
#[inline]
fn drop(&mut self) {
// Safety: A MutexGuard always holds the lock.
unsafe {
self.mutex.raw.unlock();
}
}
}

/// An RAII mutex guard returned by `MutexGuard::map`, which can point to a
/// subfield of the protected data.
///
Expand Down