diff --git a/Cargo.toml b/Cargo.toml index a463d8f0..838f4561 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/wrappers/parking_lot/Cargo.toml b/wrappers/parking_lot/Cargo.toml new file mode 100644 index 00000000..7ce14bf1 --- /dev/null +++ b/wrappers/parking_lot/Cargo.toml @@ -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 } diff --git a/wrappers/parking_lot/parking_lot_impl/Cargo.toml b/wrappers/parking_lot/parking_lot_impl/Cargo.toml new file mode 100644 index 00000000..ffc7528f --- /dev/null +++ b/wrappers/parking_lot/parking_lot_impl/Cargo.toml @@ -0,0 +1,16 @@ +[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 = "*"} diff --git a/wrappers/parking_lot/parking_lot_impl/src/lib.rs b/wrappers/parking_lot/parking_lot_impl/src/lib.rs new file mode 100644 index 00000000..43efd726 --- /dev/null +++ b/wrappers/parking_lot/parking_lot_impl/src/lib.rs @@ -0,0 +1,149 @@ +//! 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`]: +//! +//! [`parking_lot`]: + +use shuttle::sync; + +// TODO: All the `expect`s below are wrong — it is possible to poison a Shuttle mutex. +// `clear_poison` should be added and used to avoid the `expect`. +// TODO: Shuttle should be split up into a "core" crate which offers a more versatile +// Mutex which is parametric on poisoning (and this crate would initialize it as non-poisonable) + +#[derive(Default, Debug)] +pub struct Mutex { + inner: sync::Mutex, +} + +pub use sync::{MutexGuard, RwLockReadGuard, RwLockWriteGuard}; + +impl Mutex { + /// Creates a new mutex in an unlocked state ready for use. + pub fn new(value: T) -> Self { + Self { + inner: sync::Mutex::new(value), + } + } + + /// Consumes this mutex, returning the underlying data. + #[inline] + pub fn into_inner(self) -> T { + self.inner + .into_inner() + .expect("This shouldn't happen as there is no way to poision a Shuttle Mutex.") + } +} + +impl Mutex { + /// Acquires a mutex, blocking the current thread until it is able to do so. + /// + /// This function will block the local thread until it is available to acquire + /// the mutex. Upon returning, the thread is the only thread with the mutex + /// held. An RAII guard is returned to allow scoped unlock of the lock. When + /// the guard goes out of scope, the mutex will be unlocked. + /// + /// Attempts to lock a mutex in the thread which already holds the lock will + /// result in a deadlock. + #[inline] + pub fn lock(&self) -> sync::MutexGuard<'_, T> { + self.inner + .lock() + .expect("This shouldn't happen as there is no way to poision a Shuttle Mutex.") + } + + /// Attempts to acquire this lock. + /// + /// If the lock could not be acquired at this time, then `None` is returned. + /// Otherwise, an RAII guard is returned. The lock will be unlocked when the + /// guard is dropped. + #[inline] + pub fn try_lock(&self) -> Option> { + self.inner.try_lock().ok() + } +} + +/// A reader-writer lock +#[derive(Debug, Default)] +pub struct RwLock { + inner: sync::RwLock, +} + +impl RwLock { + /// Creates a new instance of an `RwLock` which is unlocked. + pub fn new(value: T) -> Self { + Self { + inner: sync::RwLock::new(value), + } + } + + /// Consumes this `RwLock`, returning the underlying data. + #[inline] + pub fn into_inner(self) -> T { + self.inner + .into_inner() + .expect("This shouldn't happen as there is no way to poision a Shuttle RwLock.") + } +} + +impl RwLock { + /// Locks this `RwLock` with shared read access, blocking the current thread + /// until it can be acquired. + /// + /// The calling thread will be blocked until there are no more writers which + /// hold the lock. There may be other readers currently inside the lock when + /// this method returns. + /// + /// Note that attempts to recursively acquire a read lock on a `RwLock` when + /// the current thread already holds one may result in a deadlock. + /// + /// Returns an RAII guard which will release this thread's shared access + /// once it is dropped. + #[inline] + pub fn read(&self) -> RwLockReadGuard<'_, T> { + self.inner + .read() + .expect("This shouldn't happen as there is no way to poision a Shuttle RwLock.") + } + + /// Attempts to acquire this `RwLock` with shared read access. + /// + /// If the access could not be granted at this time, then `None` is returned. + /// Otherwise, an RAII guard is returned which will release the shared access + /// when it is dropped. + /// + /// This function does not block. + #[inline] + pub fn try_read(&self) -> Option> { + self.inner.try_read().ok() + } + + /// Locks this `RwLock` with exclusive write access, blocking the current + /// thread until it can be acquired. + /// + /// This function will not return while other writers or other readers + /// currently have access to the lock. + /// + /// Returns an RAII guard which will drop the write access of this `RwLock` + /// when dropped. + #[inline] + pub fn write(&self) -> RwLockWriteGuard<'_, T> { + self.inner + .write() + .expect("This shouldn't happen as there is no way to poision a Shuttle RwLock.") + } + + /// Attempts to lock this `RwLock` with exclusive write access. + /// + /// If the lock could not be acquired at this time, then `None` is returned. + /// Otherwise, an RAII guard is returned which will release the lock when + /// it is dropped. + /// + /// This function does not block. + #[inline] + pub fn try_write(&self) -> Option> { + self.inner.try_write().ok() + } +} diff --git a/wrappers/parking_lot/src/lib.rs b/wrappers/parking_lot/src/lib.rs new file mode 100644 index 00000000..e40b6de1 --- /dev/null +++ b/wrappers/parking_lot/src/lib.rs @@ -0,0 +1,34 @@ +//! This crate provides a Shuttle-compatible implementation and wrapper for [`parking_lot`] in order to make it +//! more ergonomic to run a codebase under Shuttle. +//! +//! [`parking_lot`]: +//! +//! To use this crate, add something akin to the following to your Cargo.toml: +//! +//! ```ignore +//! [features] +//! shuttle = [ +//! "parking_lot/shuttle", +//! ] +//! +//! [dependencies] +//! parking_lot = { package = "shuttle-parking_lot", version = "0.12" } +//! ``` +//! +//! The rest of the codebase then remains unchanged, and running with Shuttle-compatible `parking_lot` can be done via the "shuttle" feature flag. +//! +//! By default the version of `parking_lot` exported is the latest `0.12` version (the same as if you had written `parking_lot = "0.12"` in your Cargo.toml). +//! If you need to constrain the version of `parking_lot` that you depend on (eg. to pin the version), then this can be done by adding an entry like: +//! +//! ```ignore +//! [dependencies] +//! parking_lot-version-import-dont-use = { package = "parking_lot", version = "=0.12.5" } +//! ``` + +cfg_if::cfg_if! { + if #[cfg(feature = "shuttle")] { + pub use shuttle_parking_lot_impl::*; + } else { + pub use parking_lot::*; + } +}