diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..16edc9d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,157 @@ +//! This library provides wrapper types that permit sending non `Send` types to +//! other threads and use runtime checks to ensure safety. +//! +//! It provides three types: [`Fragile`] and [`Sticky`] which are similar in nature +//! but have different behaviors with regards to how destructors are executed and +//! the extra [`SemiSticky`] type which uses [`Sticky`] if the value has a +//! destructor and [`Fragile`] if it does not. +//! +//! All three types wrap a value and provide a `Send` bound. Neither of the types permit +//! access to the enclosed value unless the thread that wrapped the value is attempting +//! to access it. The difference between the types starts playing a role once +//! destructors are involved. +//! +//! A [`Fragile`] will actually send the `T` from thread to thread but will only +//! permit the original thread to invoke the destructor. If the value gets dropped +//! in a different thread, the destructor will panic. +//! +//! A [`Sticky`] on the other hand does not actually send the `T` around but keeps +//! it stored in the original thread's thread local storage. If it gets dropped +//! in the originating thread it gets cleaned up immediately, otherwise it leaks +//! until the thread shuts down naturally. [`Sticky`] because it borrows into the +//! TLS also requires you to "prove" that you are not doing any funny business with +//! the borrowed value that lives for longer than the current stack frame which +//! results in a slightly more complex API. +//! +//! There is a third typed called [`SemiSticky`] which shares the API with [`Sticky`] +//! but internally uses a boxed [`Fragile`] if the type does not actually need a dtor +//! in which case [`Fragile`] is preferred. +//! +//! # Fragile Usage +//! +//! [`Fragile`] is the easiest type to use. It works almost like a cell. +//! +//! ``` +//! use std::thread; +//! use fragile::Fragile; +//! +//! // creating and using a fragile object in the same thread works +//! let val = Fragile::new(true); +//! assert_eq!(*val.get(), true); +//! assert!(val.try_get().is_ok()); +//! +//! // once send to another thread it stops working +//! thread::spawn(move || { +//! assert!(val.try_get().is_err()); +//! }).join() +//! .unwrap(); +//! ``` +//! +//! # Sticky Usage +//! +//! [`Sticky`] is similar to [`Fragile`] but because it places the value in the +//! thread local storage it comes with some extra restrictions to make it sound. +//! The advantage is it can be dropped from any thread but it comes with extra +//! restrictions. In particular it requires that values placed in it are `'static` +//! and that [`StackToken`]s are used to restrict lifetimes. +//! +//! ``` +//! use std::thread; +//! use fragile::Sticky; +//! +//! // creating and using a fragile object in the same thread works +//! fragile::stack_token!(tok); +//! let val = Sticky::new(true); +//! assert_eq!(*val.get(tok), true); +//! assert!(val.try_get(tok).is_ok()); +//! +//! // once send to another thread it stops working +//! thread::spawn(move || { +//! fragile::stack_token!(tok); +//! assert!(val.try_get(tok).is_err()); +//! }).join() +//! .unwrap(); +//! ``` +//! +//! # Why? +//! +//! Most of the time trying to use this crate is going to indicate some code smell. But +//! there are situations where this is useful. For instance you might have a bunch of +//! non `Send` types but want to work with a `Send` error type. In that case the non +//! sendable extra information can be contained within the error and in cases where the +//! error did not cross a thread boundary yet extra information can be obtained. +//! +//! # Drop / Cleanup Behavior +//! +//! All types will try to eagerly drop a value if they are dropped on the right thread. +//! [`Sticky`] and [`SemiSticky`] will however temporarily leak memory until a thread +//! shuts down if the value is dropped on the wrong thread. The benefit however is that +//! if you have that type of situation, and you can live with the consequences, the +//! type is not panicking. A [`Fragile`] dropped in the wrong thread will not just panic, +//! it will effectively also tear down the process because panicking in destructors is +//! non recoverable. +//! +//! # Features +//! +//! By default the crate has no dependencies. Optionally the `slab` feature can +//! be enabled which optimizes the internal storage of the [`Sticky`] type to +//! make it use a [`slab`](https://docs.rs/slab/latest/slab/) instead. +mod errors; +mod fragile; +mod registry; +mod semisticky; +mod sticky; +mod thread_id; + +use std::marker::PhantomData; + +pub use crate::errors::InvalidThreadAccess; +pub use crate::fragile::Fragile; +pub use crate::semisticky::SemiSticky; +pub use crate::sticky::Sticky; + +/// A token that is placed to the stack to constrain lifetimes. +/// +/// For more information about how these work see the documentation of +/// [`stack_token!`] which is the only way to create this token. +pub struct StackToken(PhantomData<*const ()>); + +impl StackToken { + /// Stack tokens must only be created on the stack. + #[doc(hidden)] + pub unsafe fn __private_new() -> StackToken { + // we place a const pointer in there to get a type + // that is neither Send nor Sync. + StackToken(PhantomData) + } +} + +/// Crates a token on the stack with a certain name for semi-sticky. +/// +/// The argument to the macro is the target name of a local variable +/// which holds a reference to a stack token. Because this is the +/// only way to create such a token, it acts as a proof to [`Sticky`] +/// or [`SemiSticky`] that can be used to constrain the lifetime of the +/// return values to the stack frame. +/// +/// This is necessary as otherwise a [`Sticky`] placed in a [`Box`] and +/// leaked with [`Box::leak`] (which creates a static lifetime) would +/// otherwise create a reference with `'static` lifetime. This is incorrect +/// as the actual lifetime is constrained to the lifetime of the thread. +/// For more information see [`issue 26`](https://github.com/mitsuhiko/fragile/issues/26). +/// +/// ```rust +/// let sticky = fragile::Sticky::new(true); +/// +/// // this places a token on the stack. +/// fragile::stack_token!(my_token); +/// +/// // the token needs to be passed to `get` and others. +/// let _ = sticky.get(my_token); +/// ``` +#[macro_export] +macro_rules! stack_token { + ($name:ident) => { + let $name = &unsafe { $crate::StackToken::__private_new() }; + }; +} |