summaryrefslogtreecommitdiff
path: root/src/semisticky.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/semisticky.rs')
-rw-r--r--src/semisticky.rs339
1 files changed, 339 insertions, 0 deletions
diff --git a/src/semisticky.rs b/src/semisticky.rs
new file mode 100644
index 0000000..2b6c0f4
--- /dev/null
+++ b/src/semisticky.rs
@@ -0,0 +1,339 @@
+use std::cmp;
+use std::fmt;
+use std::mem;
+
+use crate::errors::InvalidThreadAccess;
+use crate::fragile::Fragile;
+use crate::sticky::Sticky;
+use crate::StackToken;
+
+enum SemiStickyImpl<T: 'static> {
+ Fragile(Box<Fragile<T>>),
+ Sticky(Sticky<T>),
+}
+
+/// A [`SemiSticky<T>`] 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 type requires `T: 'static` for the same reasons as [`Sticky`] and
+/// also uses [`StackToken`]s.
+pub struct SemiSticky<T: 'static> {
+ inner: SemiStickyImpl<T>,
+}
+
+impl<T> SemiSticky<T> {
+ /// 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`]
+ /// instead.
+ pub fn new(value: T) -> Self {
+ SemiSticky {
+ inner: if mem::needs_drop::<T>() {
+ SemiStickyImpl::Sticky(Sticky::new(value))
+ } else {
+ SemiStickyImpl::Fragile(Box::new(Fragile::new(value)))
+ },
+ }
+ }
+
+ /// Returns `true` if the access is valid.
+ ///
+ /// This will be `false` if the value was sent to another thread.
+ pub fn is_valid(&self) -> bool {
+ match self.inner {
+ SemiStickyImpl::Fragile(ref inner) => inner.is_valid(),
+ SemiStickyImpl::Sticky(ref inner) => inner.is_valid(),
+ }
+ }
+
+ /// Consumes the [`SemiSticky`], 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(self) -> T {
+ match self.inner {
+ SemiStickyImpl::Fragile(inner) => inner.into_inner(),
+ SemiStickyImpl::Sticky(inner) => inner.into_inner(),
+ }
+ }
+
+ /// 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)`.
+ pub fn try_into_inner(self) -> Result<T, Self> {
+ match self.inner {
+ SemiStickyImpl::Fragile(inner) => inner.try_into_inner().map_err(|inner| SemiSticky {
+ inner: SemiStickyImpl::Fragile(Box::new(inner)),
+ }),
+ SemiStickyImpl::Sticky(inner) => inner.try_into_inner().map_err(|inner| SemiSticky {
+ inner: SemiStickyImpl::Sticky(inner),
+ }),
+ }
+ }
+
+ /// 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`](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(_proof),
+ }
+ }
+
+ /// 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`](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(_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<'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(_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<'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(_proof),
+ }
+ }
+}
+
+impl<T> From<T> for SemiSticky<T> {
+ #[inline]
+ fn from(t: T) -> SemiSticky<T> {
+ SemiSticky::new(t)
+ }
+}
+
+impl<T: Clone> Clone for SemiSticky<T> {
+ #[inline]
+ fn clone(&self) -> SemiSticky<T> {
+ crate::stack_token!(tok);
+ SemiSticky::new(self.get(tok).clone())
+ }
+}
+
+impl<T: Default> Default for SemiSticky<T> {
+ #[inline]
+ fn default() -> SemiSticky<T> {
+ SemiSticky::new(T::default())
+ }
+}
+
+impl<T: PartialEq> PartialEq for SemiSticky<T> {
+ #[inline]
+ fn eq(&self, other: &SemiSticky<T>) -> bool {
+ crate::stack_token!(tok);
+ *self.get(tok) == *other.get(tok)
+ }
+}
+
+impl<T: Eq> Eq for SemiSticky<T> {}
+
+impl<T: PartialOrd> PartialOrd for SemiSticky<T> {
+ #[inline]
+ fn partial_cmp(&self, other: &SemiSticky<T>) -> Option<cmp::Ordering> {
+ crate::stack_token!(tok);
+ self.get(tok).partial_cmp(other.get(tok))
+ }
+
+ #[inline]
+ fn lt(&self, other: &SemiSticky<T>) -> bool {
+ crate::stack_token!(tok);
+ *self.get(tok) < *other.get(tok)
+ }
+
+ #[inline]
+ fn le(&self, other: &SemiSticky<T>) -> bool {
+ crate::stack_token!(tok);
+ *self.get(tok) <= *other.get(tok)
+ }
+
+ #[inline]
+ fn gt(&self, other: &SemiSticky<T>) -> bool {
+ crate::stack_token!(tok);
+ *self.get(tok) > *other.get(tok)
+ }
+
+ #[inline]
+ fn ge(&self, other: &SemiSticky<T>) -> bool {
+ crate::stack_token!(tok);
+ *self.get(tok) >= *other.get(tok)
+ }
+}
+
+impl<T: Ord> Ord for SemiSticky<T> {
+ #[inline]
+ fn cmp(&self, other: &SemiSticky<T>) -> cmp::Ordering {
+ crate::stack_token!(tok);
+ self.get(tok).cmp(other.get(tok))
+ }
+}
+
+impl<T: fmt::Display> fmt::Display for SemiSticky<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 SemiSticky<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("SemiSticky").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("SemiSticky")
+ .field("value", &InvalidPlaceholder)
+ .finish()
+ }
+ }
+ }
+}
+
+#[test]
+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(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 = SemiSticky::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 = SemiSticky::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 = SemiSticky::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 = SemiSticky::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 = SemiSticky::new(Rc::new(true));
+ thread::spawn(move || {
+ crate::stack_token!(tok);
+ assert!(val.try_get(tok).is_err());
+ })
+ .join()
+ .unwrap();
+}