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

Implement stack tokens to avoid unsound behavior #29

Merged
merged 1 commit into from Oct 18, 2022
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: 3 additions & 1 deletion CHANGELOG.md
Expand Up @@ -2,9 +2,11 @@

All notable changes to similar are documented here.

## 1.3.0
## 2.0.0

* `Fragile` no longer boxes internally.
* `Sticky` and `SemiSticky` now require the use of stack tokens.
For more information see [#26](https://github.com/mitsuhiko/fragile/issues/26)

## 1.2.1

Expand Down
17 changes: 9 additions & 8 deletions src/fragile.rs
Expand Up @@ -7,14 +7,15 @@ use crate::errors::InvalidThreadAccess;
use crate::thread_id;
use std::mem::ManuallyDrop;

/// A `Fragile<T>` wraps a non sendable `T` to be safely send to other threads.
/// A [`Fragile<T>`] wraps a non sendable `T` to be safely send to other threads.
///
/// Once the value has been wrapped it can be sent to other threads but access
/// to the value on those threads will fail.
///
/// If the value needs destruction and the fragile wrapper is on another thread
/// the destructor will panic. Alternatively you can use `Sticky<T>` which is
/// not going to panic but might temporarily leak the value.
/// the destructor will panic. Alternatively you can use
/// [`Sticky`](crate::Sticky) which is not going to panic but might temporarily
/// leak the value.
pub struct Fragile<T> {
// ManuallyDrop is necessary because we need to move out of this `Box` without running the
// Drop code in functions like `into_inner`.
Expand All @@ -23,9 +24,9 @@ pub struct Fragile<T> {
}

impl<T> Fragile<T> {
/// Creates a new `Fragile` wrapping a `value`.
/// Creates a new [`Fragile`] wrapping a `value`.
///
/// The value that is moved into the `Fragile` can be non `Send` and
/// The value that is moved into the [`Fragile`] can be non `Send` and
/// will be anchored to the thread that created the object. If the
/// fragile wrapper type ends up being send from thread to thread
/// only the original thread can interact with the value.
Expand Down Expand Up @@ -70,7 +71,7 @@ impl<T> Fragile<T> {
///
/// The wrapped value is returned if this is called from the same thread
/// as the one where the original value was created, otherwise the
/// `Fragile` is returned as `Err(self)`.
/// [`Fragile`] is returned as `Err(self)`.
pub fn try_into_inner(self) -> Result<T, Self> {
if thread_id::get() == self.thread_id {
Ok(self.into_inner())
Expand All @@ -84,7 +85,7 @@ impl<T> Fragile<T> {
/// # Panics
///
/// Panics if the calling thread is not the one that wrapped the value.
/// For a non-panicking variant, use [`try_get`](#method.try_get`).
/// For a non-panicking variant, use [`try_get`](Self::try_get).
pub fn get(&self) -> &T {
self.assert_thread();
&**self.value
Expand All @@ -95,7 +96,7 @@ impl<T> Fragile<T> {
/// # Panics
///
/// Panics if the calling thread is not the one that wrapped the value.
/// For a non-panicking variant, use [`try_get_mut`](#method.try_get_mut`).
/// For a non-panicking variant, use [`try_get_mut`](Self::try_get_mut).
pub fn get_mut(&mut self) -> &mut T {
self.assert_thread();
&mut **self.value
Expand Down
92 changes: 85 additions & 7 deletions src/lib.rs
@@ -1,26 +1,32 @@
//! 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<T>` and `Sticky<T>` which are similar in nature
//! 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<T>` type which uses `Sticky<T>` if the value has a
//! destructor and `Fragile<T>` if it does not.
//! the extra [`SemiSticky`] type which uses [`Sticky`] if the value has a
//! destructor and [`Fragile`] if it does not.
//!
//! Both 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 two types starts playing a role once
//! destructors are involved.
//!
//! A `Fragile<T>` will actually send the `T` from thread to thread but will only
//! 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<T>` on the other hand does not actually send the `T` around but keeps
//! 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.
//! until the thread shuts down naturally. [`Sticky`] (and by extension
//! [`SemiSticky`]) 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.
//!
//! # Example usage
//! # Fragile Usage
//!
//! [`Fragile`] is the easiest type to use. It works almost like a cell.
//!
//! ```
//! use std::thread;
Expand All @@ -38,6 +44,32 @@
//! .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
Expand All @@ -62,3 +94,49 @@ 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(*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(std::ptr::null())
}
}

/// 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() };
};
}