Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"wrappers/tokio/impls/tokio-util",
"wrappers/tokio/wrappers/shuttle-tokio",
"wrappers/tokio/wrappers/shuttle-tokio-stream",
"wrappers/parking_lot",
]

resolver = "2"
20 changes: 20 additions & 0 deletions wrappers/parking_lot/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "shuttle-parking_lot"
version = "0.12.5"
edition = "2024"

[features]
shuttle = [ "dep:shuttle-parking_lot-impl" ]

arc_lock = [ "parking_lot/arc_lock", "shuttle-parking_lot-impl?/arc_lock" ]
deadlock_detection = [ "parking_lot/deadlock_detection", "shuttle-parking_lot-impl?/deadlock_detection" ]
hardware-lock-elision = [ "parking_lot/hardware-lock-elision", "shuttle-parking_lot-impl?/hardware-lock-elision" ]
nightly = [ "parking_lot/nightly", "shuttle-parking_lot-impl?/nightly" ]
owning_ref = [ "parking_lot/owning_ref", "shuttle-parking_lot-impl?/owning_ref" ]
send_guard = [ "parking_lot/send_guard", "shuttle-parking_lot-impl?/send_guard" ]
serde = [ "parking_lot/serde", "shuttle-parking_lot-impl?/serde" ]

[dependencies]
cfg-if = "1.0"
parking_lot = "0.12"
shuttle-parking_lot-impl = { path = "./parking_lot_impl", version = "0.1.0", optional = true }
23 changes: 23 additions & 0 deletions wrappers/parking_lot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Shuttle support for `parking_lot`

This folder contains the implementation and wrapper that enables testing of [parking_lot](https://crates.io/crates/parking_lot) applications with Shuttle.

## How to use

To use it, add the following in your Cargo.toml:

```
[features]
shuttle = [
"parking_lot/shuttle",
]

[dependencies]
parking_lot = { package = "shuttle-parking_lot", version = "VERSION_NUMBER" }
```

The code will then behave as before when the `shuttle` feature flag is not provided, and will run with Shuttle-compatible primitives when the `shuttle` feature flag is provided.

## Limitations

Shuttle's parking_lot functionality is currently limited to a subset of the `Mutex` and `RwLock` primitives. If your project needs functionality which is not currently supported, please file an issue or, better yet, open a PR to contribute the functionality.
17 changes: 17 additions & 0 deletions wrappers/parking_lot/parking_lot_impl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "shuttle-parking_lot-impl"
version = "0.1.0"
edition = "2024"

[features]
arc_lock = []
deadlock_detection = []
hardware-lock-elision = []
nightly = []
owning_ref = []
send_guard = []
serde = []

[dependencies]
shuttle = { path = "../../../shuttle", version = "*"}
tracing = { version = "0.1.36", default-features = false, features = ["std"] }
13 changes: 13 additions & 0 deletions wrappers/parking_lot/parking_lot_impl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! This crate contains Shuttle's internal implementations of the `parking_lot` crate.
//! Do not depend on this crate directly. Use the `shuttle-parking_lot` crate, which conditionally
//! exposes these implementations with the `shuttle` feature or the original crate without it.
//!
//! [`Shuttle`]: <https://crates.io/crates/shuttle>
//!
//! [`parking_lot`]: <https://crates.io/crates/parking_lot>

mod mutex;
mod rwlock;

pub use mutex::*;
pub use rwlock::*;
146 changes: 146 additions & 0 deletions wrappers/parking_lot/parking_lot_impl/src/mutex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//! An asynchronous mutual exclusion primitive.

// This implementation is adapted from the one in shuttle-tokio

use shuttle::future::batch_semaphore::{BatchSemaphore, Fairness, TryAcquireError};
use std::cell::UnsafeCell;
use std::error::Error;
use std::fmt::{self, Debug, Display};
use std::ops::{Deref, DerefMut};
use std::thread;
use tracing::trace;

/// An asynchronous mutex
pub struct Mutex<T: ?Sized> {
semaphore: BatchSemaphore,
inner: UnsafeCell<T>,
}

/// A handle to a held `Mutex`. The guard can be held across any `.await` point
/// as it is [`Send`].
pub struct MutexGuard<'a, T: ?Sized> {
mutex: &'a Mutex<T>,
}

impl<T: ?Sized + Display> Display for MutexGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&**self, f)
}
}

/// Error returned from the [`Mutex::try_lock`], `RwLock::try_read` and
/// `RwLock::try_write` functions.
#[derive(Debug)]
pub struct TryLockError(pub(super) ());

impl Display for TryLockError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "operation would block")
}
}

impl Error for TryLockError {}

// As long as T: Send, it's fine to send and share Mutex<T> between threads.
// If T was not Send, sending and sharing a Mutex<T> would be bad, since you can
// access T through Mutex<T>.
unsafe impl<T> Send for Mutex<T> where T: ?Sized + Send {}
unsafe impl<T> Sync for Mutex<T> where T: ?Sized + Send {}
unsafe impl<T> Sync for MutexGuard<'_, T> where T: ?Sized + Send + Sync {}

impl<T> Mutex<T> {
/// Creates a new lock in an unlocked state ready for use.
pub const fn new(t: T) -> Self {
Self {
semaphore: BatchSemaphore::const_new(1, Fairness::StrictlyFair),
inner: UnsafeCell::new(t),
}
}

/// Consumes the mutex, returning the underlying data.
pub fn into_inner(self) -> T {
self.inner.into_inner()
}
}

impl<T: ?Sized> Mutex<T> {
fn acquire(&self) {
trace!("acquiring parking_lot lock {:p}", self);
self.semaphore.acquire_blocking(1).unwrap_or_else(|_| {
// The semaphore was closed. but, we never explicitly close it, and
// we own it exclusively, which means that this can never happen.
if !thread::panicking() {
unreachable!()
}
});
trace!("acquired parking_lot lock {:p}", self);
}

/// Acquires a mutex, blocking the current thread until it is able to do so.
pub fn lock(&self) -> MutexGuard<'_, T> {
self.acquire();

MutexGuard { mutex: self }
}

fn try_acquire(&self) -> Result<(), TryAcquireError> {
self.semaphore.try_acquire(1)
}

/// Attempts to acquire this lock.
pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
match self.try_acquire() {
Ok(()) => Some(MutexGuard { mutex: self }),
Err(_) => None,
}
}
}

impl<T: ?Sized + Debug> Debug for Mutex<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// SAFETY: Shuttle is running single-threaded, only we are able to access `inner` at the time of this call.
Debug::fmt(&unsafe { &*self.inner.get() }, f)
}
}

impl<T: ?Sized> Deref for MutexGuard<'_, T> {
type Target = T;

fn deref(&self) -> &Self::Target {
unsafe { &*self.mutex.inner.get() }
}
}

impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.mutex.inner.get() }
}
}

impl<T: ?Sized> Drop for MutexGuard<'_, T> {
fn drop(&mut self) {
trace!("releasing lock {:p}", self);
self.mutex.semaphore.release(1);
}
}

impl<T: ?Sized + Debug> Debug for MutexGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.mutex, f)
}
}

impl<T> From<T> for Mutex<T> {
fn from(s: T) -> Self {
Self::new(s)
}
}

impl<T> Default for Mutex<T>
where
T: Default,
{
fn default() -> Self {
Self::new(T::default())
}
}
Loading
Loading