From 1b1304dcd032c1715518532b5a1222ca4288ccd8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 18 Oct 2022 19:26:06 +0200 Subject: [PATCH] Implement stack tokens to avoid unsound behavior (#29) --- CHANGELOG.md | 4 +- src/fragile.rs | 17 ++++---- src/lib.rs | 92 +++++++++++++++++++++++++++++++++++++---- src/semisticky.rs | 102 ++++++++++++++++++++++++++++------------------ src/sticky.rs | 87 +++++++++++++++++++++++++-------------- 5 files changed, 216 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8147dcd..2319617 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/fragile.rs b/src/fragile.rs index 8616075..92eb3d3 100644 --- a/src/fragile.rs +++ b/src/fragile.rs @@ -7,14 +7,15 @@ use crate::errors::InvalidThreadAccess; use crate::thread_id; use std::mem::ManuallyDrop; -/// A `Fragile` wraps a non sendable `T` to be safely send to other threads. +/// A [`Fragile`] 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` 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 { // ManuallyDrop is necessary because we need to move out of here without running the // Drop code in functions like `into_inner`. @@ -23,9 +24,9 @@ pub struct Fragile { } impl Fragile { - /// 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. @@ -70,7 +71,7 @@ impl Fragile { /// /// 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 { if thread_id::get() == self.thread_id { Ok(self.into_inner()) @@ -84,7 +85,7 @@ impl Fragile { /// # 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 @@ -95,7 +96,7 @@ impl Fragile { /// # 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 diff --git a/src/lib.rs b/src/lib.rs index 8d578a2..0b66f43 100644 --- a/src/lib.rs +++ b/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` and `Sticky` 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` type which uses `Sticky` if the value has a -//! destructor and `Fragile` 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` 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` 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; @@ -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 @@ -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() }; + }; +} diff --git a/src/semisticky.rs b/src/semisticky.rs index d3ac068..2b6c0f4 100644 --- a/src/semisticky.rs +++ b/src/semisticky.rs @@ -1,35 +1,37 @@ use std::cmp; use std::fmt; +use std::mem; use crate::errors::InvalidThreadAccess; use crate::fragile::Fragile; use crate::sticky::Sticky; -use std::mem; +use crate::StackToken; enum SemiStickyImpl { Fragile(Box>), Sticky(Sticky), } -/// A `SemiSticky` keeps a value T stored in a thread if it has a drop. +/// A [`SemiSticky`] keeps a value T stored in a thread if it has a drop. /// -/// This is a combined version of `Fragile` and `Sticky`. If the type -/// does not have a drop it will effectively be a `Fragile`, otherwise it -/// will be internally behave like a `Sticky`. +/// This is a combined version of [`Fragile`] and [`Sticky`]. If the type +/// does not have a drop it will effectively be a [`Fragile`], otherwise it +/// will be internally behave like a [`Sticky`]. /// -/// This type requires `T: 'static` for the same reasons as `Sticky`. +/// This type requires `T: 'static` for the same reasons as [`Sticky`] and +/// also uses [`StackToken`]s. pub struct SemiSticky { inner: SemiStickyImpl, } impl SemiSticky { - /// Creates a new `SemiSticky` wrapping a `value`. + /// Creates a new [`SemiSticky`] wrapping a `value`. /// /// The value that is moved into the `SemiSticky` 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. In case the - /// value does not have `Drop` it will be stored in the `SemiSticky` + /// value does not have `Drop` it will be stored in the [`SemiSticky`] /// instead. pub fn new(value: T) -> Self { SemiSticky { @@ -51,7 +53,7 @@ impl SemiSticky { } } - /// Consumes the `SemiSticky`, returning the wrapped value. + /// Consumes the [`SemiSticky`], returning the wrapped value. /// /// # Panics /// @@ -64,11 +66,11 @@ impl SemiSticky { } } - /// Consumes the `SemiSticky`, returning the wrapped value if successful. + /// Consumes the [`SemiSticky`], 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 - /// `SemiSticky` is returned as `Err(self)`. + /// [`SemiSticky`] is returned as `Err(self)`. pub fn try_into_inner(self) -> Result { match self.inner { SemiStickyImpl::Fragile(inner) => inner.try_into_inner().map_err(|inner| SemiSticky { @@ -85,11 +87,11 @@ impl SemiSticky { /// # 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(&self) -> &T { + /// For a non-panicking variant, use [`try_get`](Self::try_get). + pub fn get<'stack>(&'stack self, _proof: &'stack StackToken) -> &'stack T { match self.inner { SemiStickyImpl::Fragile(ref inner) => inner.get(), - SemiStickyImpl::Sticky(ref inner) => inner.get(), + SemiStickyImpl::Sticky(ref inner) => inner.get(_proof), } } @@ -98,31 +100,37 @@ impl SemiSticky { /// # 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(&mut self) -> &mut T { + /// For a non-panicking variant, use [`try_get_mut`](Self::try_get_mut). + pub fn get_mut<'stack>(&'stack mut self, _proof: &'stack StackToken) -> &'stack mut T { match self.inner { SemiStickyImpl::Fragile(ref mut inner) => inner.get_mut(), - SemiStickyImpl::Sticky(ref mut inner) => inner.get_mut(), + SemiStickyImpl::Sticky(ref mut inner) => inner.get_mut(_proof), } } /// 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(&self) -> Result<&T, InvalidThreadAccess> { + pub fn try_get<'stack>( + &'stack self, + _proof: &'stack StackToken, + ) -> Result<&'stack T, InvalidThreadAccess> { match self.inner { SemiStickyImpl::Fragile(ref inner) => inner.try_get(), - SemiStickyImpl::Sticky(ref inner) => inner.try_get(), + SemiStickyImpl::Sticky(ref inner) => inner.try_get(_proof), } } /// 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(&mut self) -> Result<&mut T, InvalidThreadAccess> { + pub fn try_get_mut<'stack>( + &'stack mut self, + _proof: &'stack StackToken, + ) -> Result<&'stack mut T, InvalidThreadAccess> { match self.inner { SemiStickyImpl::Fragile(ref mut inner) => inner.try_get_mut(), - SemiStickyImpl::Sticky(ref mut inner) => inner.try_get_mut(), + SemiStickyImpl::Sticky(ref mut inner) => inner.try_get_mut(_proof), } } } @@ -137,7 +145,8 @@ impl From for SemiSticky { impl Clone for SemiSticky { #[inline] fn clone(&self) -> SemiSticky { - SemiSticky::new(self.get().clone()) + crate::stack_token!(tok); + SemiSticky::new(self.get(tok).clone()) } } @@ -151,7 +160,8 @@ impl Default for SemiSticky { impl PartialEq for SemiSticky { #[inline] fn eq(&self, other: &SemiSticky) -> bool { - *self.get() == *other.get() + crate::stack_token!(tok); + *self.get(tok) == *other.get(tok) } } @@ -160,46 +170,54 @@ impl Eq for SemiSticky {} impl PartialOrd for SemiSticky { #[inline] fn partial_cmp(&self, other: &SemiSticky) -> Option { - self.get().partial_cmp(other.get()) + crate::stack_token!(tok); + self.get(tok).partial_cmp(other.get(tok)) } #[inline] fn lt(&self, other: &SemiSticky) -> bool { - *self.get() < *other.get() + crate::stack_token!(tok); + *self.get(tok) < *other.get(tok) } #[inline] fn le(&self, other: &SemiSticky) -> bool { - *self.get() <= *other.get() + crate::stack_token!(tok); + *self.get(tok) <= *other.get(tok) } #[inline] fn gt(&self, other: &SemiSticky) -> bool { - *self.get() > *other.get() + crate::stack_token!(tok); + *self.get(tok) > *other.get(tok) } #[inline] fn ge(&self, other: &SemiSticky) -> bool { - *self.get() >= *other.get() + crate::stack_token!(tok); + *self.get(tok) >= *other.get(tok) } } impl Ord for SemiSticky { #[inline] fn cmp(&self, other: &SemiSticky) -> cmp::Ordering { - self.get().cmp(other.get()) + crate::stack_token!(tok); + self.get(tok).cmp(other.get(tok)) } } impl fmt::Display for SemiSticky { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - fmt::Display::fmt(self.get(), f) + crate::stack_token!(tok); + fmt::Display::fmt(self.get(tok), f) } } impl fmt::Debug for SemiSticky { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self.try_get() { + crate::stack_token!(tok); + match self.try_get(tok) { Ok(value) => f.debug_struct("SemiSticky").field("value", value).finish(), Err(..) => { struct InvalidPlaceholder; @@ -221,11 +239,13 @@ impl fmt::Debug for SemiSticky { fn test_basic() { use std::thread; let val = SemiSticky::new(true); + crate::stack_token!(tok); assert_eq!(val.to_string(), "true"); - assert_eq!(val.get(), &true); - assert!(val.try_get().is_ok()); + assert_eq!(val.get(tok), &true); + assert!(val.try_get(tok).is_ok()); thread::spawn(move || { - assert!(val.try_get().is_err()); + crate::stack_token!(tok); + assert!(val.try_get(tok).is_err()); }) .join() .unwrap(); @@ -234,9 +254,10 @@ fn test_basic() { #[test] fn test_mut() { let mut val = SemiSticky::new(true); - *val.get_mut() = false; + crate::stack_token!(tok); + *val.get_mut(tok) = false; assert_eq!(val.to_string(), "false"); - assert_eq!(val.get(), &false); + assert_eq!(val.get(tok), &false); } #[test] @@ -245,7 +266,8 @@ fn test_access_other_thread() { use std::thread; let val = SemiSticky::new(true); thread::spawn(move || { - val.get(); + crate::stack_token!(tok); + val.get(tok); }) .join() .unwrap(); @@ -288,7 +310,8 @@ fn test_noop_drop_elsewhere() { let val = SemiSticky::new(X(was_called.clone())); assert!(thread::spawn(move || { // moves it here but do not deallocate - val.try_get().ok(); + crate::stack_token!(tok); + val.try_get(tok).ok(); }) .join() .is_ok()); @@ -308,7 +331,8 @@ fn test_rc_sending() { use std::thread; let val = SemiSticky::new(Rc::new(true)); thread::spawn(move || { - assert!(val.try_get().is_err()); + crate::stack_token!(tok); + assert!(val.try_get(tok).is_err()); }) .join() .unwrap(); diff --git a/src/sticky.rs b/src/sticky.rs index 00b5bdf..291ab8e 100644 --- a/src/sticky.rs +++ b/src/sticky.rs @@ -9,18 +9,21 @@ use std::num::NonZeroUsize; use crate::errors::InvalidThreadAccess; use crate::registry; use crate::thread_id; +use crate::StackToken; -/// A `Sticky` keeps a value T stored in a thread. +/// A [`Sticky`] keeps a value T stored in a thread. /// -/// This type works similar in nature to `Fragile` and exposes a -/// similar interface. The difference is that whereas `Fragile` has +/// 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 +/// [`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. +/// 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. @@ -43,9 +46,9 @@ impl Drop for Sticky { } impl Sticky { - /// Creates a new `Sticky` wrapping a `value`. + /// Creates a new [`Sticky`] wrapping a `value`. /// - /// The value that is moved into the `Sticky` can be non `Send` and + /// 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. @@ -135,7 +138,7 @@ impl Sticky { /// /// 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(&self) -> &T { + pub fn get<'stack>(&'stack self, _proof: &'stack StackToken) -> &'stack T { self.with_value(|value| unsafe { &*value }) } @@ -145,14 +148,17 @@ impl Sticky { /// /// 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(&mut self) -> &mut T { + 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(&self) -> Result<&T, InvalidThreadAccess> { + 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 { @@ -163,7 +169,10 @@ impl Sticky { /// 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(&mut self) -> Result<&mut T, InvalidThreadAccess> { + 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 { @@ -182,7 +191,8 @@ impl From for Sticky { impl Clone for Sticky { #[inline] fn clone(&self) -> Sticky { - Sticky::new(self.get().clone()) + crate::stack_token!(tok); + Sticky::new(self.get(tok).clone()) } } @@ -196,7 +206,8 @@ impl Default for Sticky { impl PartialEq for Sticky { #[inline] fn eq(&self, other: &Sticky) -> bool { - *self.get() == *other.get() + crate::stack_token!(tok); + *self.get(tok) == *other.get(tok) } } @@ -205,46 +216,54 @@ impl Eq for Sticky {} impl PartialOrd for Sticky { #[inline] fn partial_cmp(&self, other: &Sticky) -> Option { - self.get().partial_cmp(other.get()) + crate::stack_token!(tok); + self.get(tok).partial_cmp(other.get(tok)) } #[inline] fn lt(&self, other: &Sticky) -> bool { - *self.get() < *other.get() + crate::stack_token!(tok); + *self.get(tok) < *other.get(tok) } #[inline] fn le(&self, other: &Sticky) -> bool { - *self.get() <= *other.get() + crate::stack_token!(tok); + *self.get(tok) <= *other.get(tok) } #[inline] fn gt(&self, other: &Sticky) -> bool { - *self.get() > *other.get() + crate::stack_token!(tok); + *self.get(tok) > *other.get(tok) } #[inline] fn ge(&self, other: &Sticky) -> bool { - *self.get() >= *other.get() + crate::stack_token!(tok); + *self.get(tok) >= *other.get(tok) } } impl Ord for Sticky { #[inline] fn cmp(&self, other: &Sticky) -> cmp::Ordering { - self.get().cmp(other.get()) + crate::stack_token!(tok); + self.get(tok).cmp(other.get(tok)) } } impl fmt::Display for Sticky { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - fmt::Display::fmt(self.get(), f) + crate::stack_token!(tok); + fmt::Display::fmt(self.get(tok), f) } } impl fmt::Debug for Sticky { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self.try_get() { + crate::stack_token!(tok); + match self.try_get(tok) { Ok(value) => f.debug_struct("Sticky").field("value", value).finish(), Err(..) => { struct InvalidPlaceholder; @@ -273,11 +292,13 @@ unsafe impl Send for Sticky {} 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(), &true); - assert!(val.try_get().is_ok()); + assert_eq!(val.get(tok), &true); + assert!(val.try_get(tok).is_ok()); thread::spawn(move || { - assert!(val.try_get().is_err()); + crate::stack_token!(tok); + assert!(val.try_get(tok).is_err()); }) .join() .unwrap(); @@ -286,9 +307,10 @@ fn test_basic() { #[test] fn test_mut() { let mut val = Sticky::new(true); - *val.get_mut() = false; + crate::stack_token!(tok); + *val.get_mut(tok) = false; assert_eq!(val.to_string(), "false"); - assert_eq!(val.get(), &false); + assert_eq!(val.get(tok), &false); } #[test] @@ -297,7 +319,8 @@ fn test_access_other_thread() { use std::thread; let val = Sticky::new(true); thread::spawn(move || { - val.get(); + crate::stack_token!(tok); + val.get(tok); }) .join() .unwrap(); @@ -340,7 +363,8 @@ fn test_noop_drop_elsewhere() { let val = Sticky::new(X(was_called.clone())); assert!(thread::spawn(move || { // moves it here but do not deallocate - val.try_get().ok(); + crate::stack_token!(tok); + val.try_get(tok).ok(); }) .join() .is_ok()); @@ -360,7 +384,8 @@ fn test_rc_sending() { use std::thread; let val = Sticky::new(Rc::new(true)); thread::spawn(move || { - assert!(val.try_get().is_err()); + crate::stack_token!(tok); + assert!(val.try_get(tok).is_err()); }) .join() .unwrap();