/// 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 needed, /// refer to [`stack_token!`](crate::stack_token). /// /// As this uses TLS internally the general rules about the platform limitations /// of destructors for TLS apply. pubstruct Sticky<T: 'static> {
item_id: registry::ItemId,
thread_id: NonZeroUsize,
_marker: PhantomData<*mut T>,
}
impl<T> Drop for Sticky<T> { fn drop(&mutself) { // if the type needs dropping we can only do so on the right thread. // worst case we leak the value until the thread dies when drop will be // called by the registry. if mem::needs_drop::<T>() { unsafe { ifself.is_valid() { self.unsafe_take_value();
}
}
}
}
}
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. pubfn 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(entry);
/// Returns `true` if the access is valid. /// /// This will be `false` if the value was sent to another thread. #[inline(always)] pubfn 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. pubfn into_inner(mutself) -> T { self.assert_thread(); unsafe { let rv = self.unsafe_take_value();
mem::forget(self);
rv
}
}
unsafefn unsafe_take_value(&mutself) -> T { let ptr = registry::try_remove(self.item_id).unwrap().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)`. pubfn try_into_inner(self) -> Result<T, Self> { ifself.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`). pubfn get<'stack>(&'stack self, _proof: &'stack StackToken) -> &'</span>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`). pubfn get_mut<'stack>(&'stack mutself, _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. pubfn try_get<'stack>(
&'stack self,
_proof: &'stack StackToken,
) -> Result<&'stack T, InvalidThreadAccess> { ifself.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. pubfn try_get_mut<'stack>(
&'stack mut self,
_proof: &'stack StackToken,
) -> Result<&'stack mut T, InvalidThreadAccess> { ifself.is_valid() {
Ok(self.with_value(|value| unsafe { &mut *value }))
} else {
Err(InvalidThreadAccess)
}
}
}
// similar as for fragile the type is sync because it only accesses TLS data // which is thread local. There is nothing that needs to be synchronized. unsafeimpl<T> Sync for Sticky<T> {}
// The entire point of this type is to be Send unsafeimpl<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] #[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(&mutself) { 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(&mutself) { 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());
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.