blob: bc15c407e41ead9c533bacb32e04f9855b3de936 [file] [log] [blame]
#![allow(clippy::unit_arg)]
use std::cmp;
use std::fmt;
use std::marker::PhantomData;
use std::mem;
use std::num::NonZeroUsize;
use crate::errors::InvalidThreadAccess;
use crate::registry;
use crate::thread_id;
use crate::StackToken;
/// A [`Sticky<T>`] keeps a value T stored in a thread.
///
/// This type works similar in nature to [`Fragile`](crate::Fragile) and exposes a
/// similar interface. The difference is that whereas [`Fragile`](crate::Fragile) has
/// its destructor called in the thread where the value was sent, a
/// [`Sticky`] that is moved to another thread will have the internal
/// destructor called when the originating thread tears down.
///
/// Because [`Sticky`] allows values to be kept alive for longer than the
/// [`Sticky`] itself, it requires all its contents to be `'static` for
/// soundness. More importantly it also requires the use of [`StackToken`]s.
/// For information about how to use stack tokens and why they are neded,
/// refer to [`stack_token!`](crate::stack_token).
///
/// As this uses TLS internally the general rules about the platform limitations
/// of destructors for TLS apply.
pub struct Sticky<T: 'static> {
item_id: registry::ItemId,
thread_id: NonZeroUsize,
_marker: PhantomData<*mut T>,
}
impl<T> Drop for Sticky<T> {
fn drop(&mut self) {
// if the type needs dropping we can only do so on the
// right thread. worst case we leak the value until the
// thread dies.
if mem::needs_drop::<T>() {
unsafe {
if self.is_valid() {
self.unsafe_take_value();
}
}
// otherwise we take the liberty to drop the value
// right here and now. We can however only do that if
// we are on the right thread. If we are not, we again
// need to wait for the thread to shut down.
} else if let Some(entry) = registry::try_remove(self.item_id, self.thread_id) {
unsafe {
(entry.drop)(entry.ptr);
}
}
}
}
impl<T> Sticky<T> {
/// Creates a new [`Sticky`] wrapping a `value`.
///
/// The value that is moved into the [`Sticky`] can be non `Send` and
/// will be anchored to the thread that created the object. If the
/// sticky wrapper type ends up being send from thread to thread
/// only the original thread can interact with the value.
pub fn new(value: T) -> Self {
let entry = registry::Entry {
ptr: Box::into_raw(Box::new(value)).cast(),
drop: |ptr| {
let ptr = ptr.cast::<T>();
// SAFETY: This callback will only be called once, with the
// above pointer.
drop(unsafe { Box::from_raw(ptr) });
},
};
let thread_id = thread_id::get();
let item_id = registry::insert(thread_id, entry);
Sticky {
item_id,
thread_id,
_marker: PhantomData,
}
}
#[inline(always)]
fn with_value<F: FnOnce(*mut T) -> R, R>(&self, f: F) -> R {
self.assert_thread();
registry::with(self.item_id, self.thread_id, |entry| {
f(entry.ptr.cast::<T>())
})
}
/// Returns `true` if the access is valid.
///
/// This will be `false` if the value was sent to another thread.
#[inline(always)]
pub fn is_valid(&self) -> bool {
thread_id::get() == self.thread_id
}
#[inline(always)]
fn assert_thread(&self) {
if !self.is_valid() {
panic!("trying to access wrapped value in sticky container from incorrect thread.");
}
}
/// Consumes the `Sticky`, returning the wrapped value.
///
/// # Panics
///
/// Panics if called from a different thread than the one where the
/// original value was created.
pub fn into_inner(mut self) -> T {
self.assert_thread();
unsafe {
let rv = self.unsafe_take_value();
mem::forget(self);
rv
}
}
unsafe fn unsafe_take_value(&mut self) -> T {
let ptr = registry::remove(self.item_id, self.thread_id)
.ptr
.cast::<T>();
*Box::from_raw(ptr)
}
/// Consumes the `Sticky`, returning the wrapped value if successful.
///
/// The wrapped value is returned if this is called from the same thread
/// as the one where the original value was created, otherwise the
/// `Sticky` is returned as `Err(self)`.
pub fn try_into_inner(self) -> Result<T, Self> {
if self.is_valid() {
Ok(self.into_inner())
} else {
Err(self)
}
}
/// Immutably borrows the wrapped value.
///
/// # 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`).
pub fn get<'stack>(&'stack self, _proof: &'stack StackToken) -> &'stack T {
self.with_value(|value| unsafe { &*value })
}
/// Mutably borrows the wrapped value.
///
/// # 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`).
pub fn get_mut<'stack>(&'stack mut self, _proof: &'stack StackToken) -> &'stack mut T {
self.with_value(|value| unsafe { &mut *value })
}
/// Tries to immutably borrow the wrapped value.
///
/// Returns `None` if the calling thread is not the one that wrapped the value.
pub fn try_get<'stack>(
&'stack self,
_proof: &'stack StackToken,
) -> Result<&'stack T, InvalidThreadAccess> {
if self.is_valid() {
Ok(self.with_value(|value| unsafe { &*value }))
} else {
Err(InvalidThreadAccess)
}
}
/// Tries to mutably borrow the wrapped value.
///
/// Returns `None` if the calling thread is not the one that wrapped the value.
pub fn try_get_mut<'stack>(
&'stack mut self,
_proof: &'stack StackToken,
) -> Result<&'stack mut T, InvalidThreadAccess> {
if self.is_valid() {
Ok(self.with_value(|value| unsafe { &mut *value }))
} else {
Err(InvalidThreadAccess)
}
}
}
impl<T> From<T> for Sticky<T> {
#[inline]
fn from(t: T) -> Sticky<T> {
Sticky::new(t)
}
}
impl<T: Clone> Clone for Sticky<T> {
#[inline]
fn clone(&self) -> Sticky<T> {
crate::stack_token!(tok);
Sticky::new(self.get(tok).clone())
}
}
impl<T: Default> Default for Sticky<T> {
#[inline]
fn default() -> Sticky<T> {
Sticky::new(T::default())
}
}
impl<T: PartialEq> PartialEq for Sticky<T> {
#[inline]
fn eq(&self, other: &Sticky<T>) -> bool {
crate::stack_token!(tok);
*self.get(tok) == *other.get(tok)
}
}
impl<T: Eq> Eq for Sticky<T> {}
impl<T: PartialOrd> PartialOrd for Sticky<T> {
#[inline]
fn partial_cmp(&self, other: &Sticky<T>) -> Option<cmp::Ordering> {
crate::stack_token!(tok);
self.get(tok).partial_cmp(other.get(tok))
}
#[inline]
fn lt(&self, other: &Sticky<T>) -> bool {
crate::stack_token!(tok);
*self.get(tok) < *other.get(tok)
}
#[inline]
fn le(&self, other: &Sticky<T>) -> bool {
crate::stack_token!(tok);
*self.get(tok) <= *other.get(tok)
}
#[inline]
fn gt(&self, other: &Sticky<T>) -> bool {
crate::stack_token!(tok);
*self.get(tok) > *other.get(tok)
}
#[inline]
fn ge(&self, other: &Sticky<T>) -> bool {
crate::stack_token!(tok);
*self.get(tok) >= *other.get(tok)
}
}
impl<T: Ord> Ord for Sticky<T> {
#[inline]
fn cmp(&self, other: &Sticky<T>) -> cmp::Ordering {
crate::stack_token!(tok);
self.get(tok).cmp(other.get(tok))
}
}
impl<T: fmt::Display> fmt::Display for Sticky<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
crate::stack_token!(tok);
fmt::Display::fmt(self.get(tok), f)
}
}
impl<T: fmt::Debug> fmt::Debug for Sticky<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
crate::stack_token!(tok);
match self.try_get(tok) {
Ok(value) => f.debug_struct("Sticky").field("value", value).finish(),
Err(..) => {
struct InvalidPlaceholder;
impl fmt::Debug for InvalidPlaceholder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("<invalid thread>")
}
}
f.debug_struct("Sticky")
.field("value", &InvalidPlaceholder)
.finish()
}
}
}
}
// similar as for fragile ths type is sync because it only accesses TLS data
// which is thread local. There is nothing that needs to be synchronized.
unsafe impl<T> Sync for Sticky<T> {}
// The entire point of this type is to be Send
unsafe impl<T> Send for Sticky<T> {}
#[test]
fn test_basic() {
use std::thread;
let val = Sticky::new(true);
crate::stack_token!(tok);
assert_eq!(val.to_string(), "true");
assert_eq!(val.get(tok), &true);
assert!(val.try_get(tok).is_ok());
thread::spawn(move || {
crate::stack_token!(tok);
assert!(val.try_get(tok).is_err());
})
.join()
.unwrap();
}
#[test]
fn test_mut() {
let mut val = Sticky::new(true);
crate::stack_token!(tok);
*val.get_mut(tok) = false;
assert_eq!(val.to_string(), "false");
assert_eq!(val.get(tok), &false);
}
#[test]
#[should_panic]
fn test_access_other_thread() {
use std::thread;
let val = Sticky::new(true);
thread::spawn(move || {
crate::stack_token!(tok);
val.get(tok);
})
.join()
.unwrap();
}
#[test]
fn test_drop_same_thread() {
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
let was_called = Arc::new(AtomicBool::new(false));
struct X(Arc<AtomicBool>);
impl Drop for X {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
let val = Sticky::new(X(was_called.clone()));
mem::drop(val);
assert!(was_called.load(Ordering::SeqCst));
}
#[test]
fn test_noop_drop_elsewhere() {
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
let was_called = Arc::new(AtomicBool::new(false));
{
let was_called = was_called.clone();
thread::spawn(move || {
struct X(Arc<AtomicBool>);
impl Drop for X {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
let val = Sticky::new(X(was_called.clone()));
assert!(thread::spawn(move || {
// moves it here but do not deallocate
crate::stack_token!(tok);
val.try_get(tok).ok();
})
.join()
.is_ok());
assert!(!was_called.load(Ordering::SeqCst));
})
.join()
.unwrap();
}
assert!(was_called.load(Ordering::SeqCst));
}
#[test]
fn test_rc_sending() {
use std::rc::Rc;
use std::thread;
let val = Sticky::new(Rc::new(true));
thread::spawn(move || {
crate::stack_token!(tok);
assert!(val.try_get(tok).is_err());
})
.join()
.unwrap();
}
#[test]
fn test_two_stickies() {
struct Wat;
impl Drop for Wat {
fn drop(&mut self) {
// do nothing
}
}
let s1 = Sticky::new(Wat);
let s2 = Sticky::new(Wat);
// make sure all is well
drop(s1);
drop(s2);
}