272 lines
8.2 KiB
Rust
272 lines
8.2 KiB
Rust
#![doc(html_root_url = "https://docs.rs/try-lock/0.2.3")]
|
|
#![deny(missing_docs)]
|
|
#![deny(missing_debug_implementations)]
|
|
#![deny(warnings)]
|
|
#![cfg_attr(not(test), no_std)]
|
|
|
|
//! A light-weight lock guarded by an atomic boolean.
|
|
//!
|
|
//! Most efficient when contention is low, acquiring the lock is a single
|
|
//! atomic swap, and releasing it just 1 more atomic swap.
|
|
//!
|
|
//! # Example
|
|
//!
|
|
//! ```
|
|
//! use std::sync::Arc;
|
|
//! use try_lock::TryLock;
|
|
//!
|
|
//! // a thing we want to share
|
|
//! struct Widget {
|
|
//! name: String,
|
|
//! }
|
|
//!
|
|
//! // lock it up!
|
|
//! let widget1 = Arc::new(TryLock::new(Widget {
|
|
//! name: "Spanner".into(),
|
|
//! }));
|
|
//!
|
|
//! let widget2 = widget1.clone();
|
|
//!
|
|
//!
|
|
//! // mutate the widget
|
|
//! let mut locked = widget1.try_lock().expect("example isn't locked yet");
|
|
//! locked.name.push_str(" Bundle");
|
|
//!
|
|
//! // hands off, buddy
|
|
//! let not_locked = widget2.try_lock();
|
|
//! assert!(not_locked.is_none(), "widget1 has the lock");
|
|
//!
|
|
//! // ok, you can have it
|
|
//! drop(locked);
|
|
//!
|
|
//! let locked2 = widget2.try_lock().expect("widget1 lock is released");
|
|
//!
|
|
//! assert_eq!(locked2.name, "Spanner Bundle");
|
|
//! ```
|
|
|
|
#[cfg(test)]
|
|
extern crate core;
|
|
|
|
use core::cell::UnsafeCell;
|
|
use core::fmt;
|
|
use core::ops::{Deref, DerefMut};
|
|
use core::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
/// A light-weight lock guarded by an atomic boolean.
|
|
///
|
|
/// Most efficient when contention is low, acquiring the lock is a single
|
|
/// atomic swap, and releasing it just 1 more atomic swap.
|
|
///
|
|
/// It is only possible to try to acquire the lock, it is not possible to
|
|
/// wait for the lock to become ready, like with a `Mutex`.
|
|
#[derive(Default)]
|
|
pub struct TryLock<T> {
|
|
is_locked: AtomicBool,
|
|
value: UnsafeCell<T>,
|
|
}
|
|
|
|
impl<T> TryLock<T> {
|
|
/// Create a `TryLock` around the value.
|
|
#[inline]
|
|
pub fn new(val: T) -> TryLock<T> {
|
|
TryLock {
|
|
is_locked: AtomicBool::new(false),
|
|
value: UnsafeCell::new(val),
|
|
}
|
|
}
|
|
|
|
/// Try to acquire the lock of this value.
|
|
///
|
|
/// If the lock is already acquired by someone else, this returns
|
|
/// `None`. You can try to acquire again whenever you want, perhaps
|
|
/// by spinning a few times, or by using some other means of
|
|
/// notification.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The default memory ordering is to use `Acquire` to lock, and `Release`
|
|
/// to unlock. If different ordering is required, use
|
|
/// [`try_lock_explicit`](TryLock::try_lock_explicit) or
|
|
/// [`try_lock_explicit_unchecked`](TryLock::try_lock_explicit_unchecked).
|
|
#[inline]
|
|
pub fn try_lock(&self) -> Option<Locked<T>> {
|
|
unsafe {
|
|
self.try_lock_explicit_unchecked(Ordering::Acquire, Ordering::Release)
|
|
}
|
|
}
|
|
|
|
/// Try to acquire the lock of this value using the lock and unlock orderings.
|
|
///
|
|
/// If the lock is already acquired by someone else, this returns
|
|
/// `None`. You can try to acquire again whenever you want, perhaps
|
|
/// by spinning a few times, or by using some other means of
|
|
/// notification.
|
|
#[inline]
|
|
#[deprecated(
|
|
since = "0.2.3",
|
|
note = "This method is actually unsafe because it unsafely allows \
|
|
the use of weaker memory ordering. Please use try_lock_explicit instead"
|
|
)]
|
|
pub fn try_lock_order(&self, lock_order: Ordering, unlock_order: Ordering) -> Option<Locked<T>> {
|
|
unsafe {
|
|
self.try_lock_explicit_unchecked(lock_order, unlock_order)
|
|
}
|
|
}
|
|
|
|
/// Try to acquire the lock of this value using the specified lock and
|
|
/// unlock orderings.
|
|
///
|
|
/// If the lock is already acquired by someone else, this returns
|
|
/// `None`. You can try to acquire again whenever you want, perhaps
|
|
/// by spinning a few times, or by using some other means of
|
|
/// notification.
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// This method panics if `lock_order` is not any of `Acquire`, `AcqRel`,
|
|
/// and `SeqCst`, or `unlock_order` is not any of `Release` and `SeqCst`.
|
|
#[inline]
|
|
pub fn try_lock_explicit(&self, lock_order: Ordering, unlock_order: Ordering) -> Option<Locked<T>> {
|
|
match lock_order {
|
|
Ordering::Acquire |
|
|
Ordering::AcqRel |
|
|
Ordering::SeqCst => {}
|
|
_ => panic!("lock ordering must be `Acquire`, `AcqRel`, or `SeqCst`"),
|
|
}
|
|
|
|
match unlock_order {
|
|
Ordering::Release |
|
|
Ordering::SeqCst => {}
|
|
_ => panic!("unlock ordering must be `Release` or `SeqCst`"),
|
|
}
|
|
|
|
unsafe {
|
|
self.try_lock_explicit_unchecked(lock_order, unlock_order)
|
|
}
|
|
}
|
|
|
|
/// Try to acquire the lock of this value using the specified lock and
|
|
/// unlock orderings without checking that the specified orderings are
|
|
/// strong enough to be safe.
|
|
///
|
|
/// If the lock is already acquired by someone else, this returns
|
|
/// `None`. You can try to acquire again whenever you want, perhaps
|
|
/// by spinning a few times, or by using some other means of
|
|
/// notification.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Unlike [`try_lock_explicit`], this method is unsafe because it does not
|
|
/// check that the given memory orderings are strong enough to prevent data
|
|
/// race.
|
|
///
|
|
/// [`try_lock_explicit`]: Self::try_lock_explicit
|
|
#[inline]
|
|
pub unsafe fn try_lock_explicit_unchecked(&self, lock_order: Ordering, unlock_order: Ordering) -> Option<Locked<T>> {
|
|
if !self.is_locked.swap(true, lock_order) {
|
|
Some(Locked {
|
|
lock: self,
|
|
order: unlock_order,
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Take the value back out of the lock when this is the sole owner.
|
|
#[inline]
|
|
pub fn into_inner(self) -> T {
|
|
debug_assert!(!self.is_locked.load(Ordering::Relaxed), "TryLock was mem::forgotten");
|
|
// Since the compiler can statically determine this is the only owner,
|
|
// it's safe to take the value out. In fact, in newer versions of Rust,
|
|
// `UnsafeCell::into_inner` has been marked safe.
|
|
//
|
|
// To support older version (1.21), the unsafe block is still here.
|
|
#[allow(unused_unsafe)]
|
|
unsafe {
|
|
self.value.into_inner()
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe impl<T: Send> Send for TryLock<T> {}
|
|
unsafe impl<T: Send> Sync for TryLock<T> {}
|
|
|
|
impl<T: fmt::Debug> fmt::Debug for TryLock<T> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
// Used if the TryLock cannot acquire the lock.
|
|
struct LockedPlaceholder;
|
|
|
|
impl fmt::Debug for LockedPlaceholder {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.write_str("<locked>")
|
|
}
|
|
}
|
|
|
|
let mut builder = f.debug_struct("TryLock");
|
|
if let Some(locked) = self.try_lock() {
|
|
builder.field("value", &*locked);
|
|
} else {
|
|
builder.field("value", &LockedPlaceholder);
|
|
}
|
|
builder.finish()
|
|
}
|
|
}
|
|
|
|
/// A locked value acquired from a `TryLock`.
|
|
///
|
|
/// The type represents an exclusive view at the underlying value. The lock is
|
|
/// released when this type is dropped.
|
|
///
|
|
/// This type derefs to the underlying value.
|
|
#[must_use = "TryLock will immediately unlock if not used"]
|
|
pub struct Locked<'a, T: 'a> {
|
|
lock: &'a TryLock<T>,
|
|
order: Ordering,
|
|
}
|
|
|
|
impl<'a, T> Deref for Locked<'a, T> {
|
|
type Target = T;
|
|
#[inline]
|
|
fn deref(&self) -> &T {
|
|
unsafe { &*self.lock.value.get() }
|
|
}
|
|
}
|
|
|
|
impl<'a, T> DerefMut for Locked<'a, T> {
|
|
#[inline]
|
|
fn deref_mut(&mut self) -> &mut T {
|
|
unsafe { &mut *self.lock.value.get() }
|
|
}
|
|
}
|
|
|
|
impl<'a, T> Drop for Locked<'a, T> {
|
|
#[inline]
|
|
fn drop(&mut self) {
|
|
self.lock.is_locked.store(false, self.order);
|
|
}
|
|
}
|
|
|
|
impl<'a, T: fmt::Debug> fmt::Debug for Locked<'a, T> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
fmt::Debug::fmt(&**self, f)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::TryLock;
|
|
|
|
#[test]
|
|
fn fmt_debug() {
|
|
let lock = TryLock::new(5);
|
|
assert_eq!(format!("{:?}", lock), "TryLock { value: 5 }");
|
|
|
|
let locked = lock.try_lock().unwrap();
|
|
assert_eq!(format!("{:?}", locked), "5");
|
|
|
|
assert_eq!(format!("{:?}", lock), "TryLock { value: <locked> }");
|
|
}
|
|
}
|