blob: 5fb4b1fee6ac23c6293b8fdf350676d5bbd28dd9 [file] [log] [blame]
use std::cmp;
use std::fmt;
use crate::errors::InvalidThreadAccess;
use crate::fragile::Fragile;
use crate::sticky::Sticky;
use std::mem;
enum SemiStickyImpl<T> {
Fragile(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<T>` and `Sticky<T>`. If the type
/// does not have a drop it will effectively be a `Fragile<T>`, otherwise it
/// will be internally behave like a `Sticky<T>`.
pub struct SemiSticky<T> {
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(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(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`](#method.try_get`).
pub fn get(&self) -> &T {
match self.inner {
SemiStickyImpl::Fragile(ref inner) => inner.get(),
SemiStickyImpl::Sticky(ref inner) => inner.get(),
}
}
/// 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(&mut self) -> &mut T {
match self.inner {
SemiStickyImpl::Fragile(ref mut inner) => inner.get_mut(),
SemiStickyImpl::Sticky(ref mut inner) => inner.get_mut(),
}
}
/// 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> {
match self.inner {
SemiStickyImpl::Fragile(ref inner) => inner.try_get(),
SemiStickyImpl::Sticky(ref inner) => inner.try_get(),
}
}
/// 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> {
match self.inner {
SemiStickyImpl::Fragile(ref mut inner) => inner.try_get_mut(),
SemiStickyImpl::Sticky(ref mut inner) => inner.try_get_mut(),
}
}
}
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> {
SemiSticky::new(self.get().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 {
*self.get() == *other.get()
}
}
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> {
self.get().partial_cmp(&*other.get())
}
#[inline]
fn lt(&self, other: &SemiSticky<T>) -> bool {
*self.get() < *other.get()
}
#[inline]
fn le(&self, other: &SemiSticky<T>) -> bool {
*self.get() <= *other.get()
}
#[inline]
fn gt(&self, other: &SemiSticky<T>) -> bool {
*self.get() > *other.get()
}
#[inline]
fn ge(&self, other: &SemiSticky<T>) -> bool {
*self.get() >= *other.get()
}
}
impl<T: Ord> Ord for SemiSticky<T> {
#[inline]
fn cmp(&self, other: &SemiSticky<T>) -> cmp::Ordering {
self.get().cmp(&*other.get())
}
}
impl<T: fmt::Display> fmt::Display for SemiSticky<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Display::fmt(self.get(), f)
}
}
impl<T: fmt::Debug> fmt::Debug for SemiSticky<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self.try_get() {
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);
assert_eq!(val.to_string(), "true");
assert_eq!(val.get(), &true);
assert!(val.try_get().is_ok());
thread::spawn(move || {
assert!(val.try_get().is_err());
})
.join()
.unwrap();
}
#[test]
fn test_mut() {
let mut val = SemiSticky::new(true);
*val.get_mut() = false;
assert_eq!(val.to_string(), "false");
assert_eq!(val.get(), &false);
}
#[test]
#[should_panic]
fn test_access_other_thread() {
use std::thread;
let val = SemiSticky::new(true);
thread::spawn(move || {
val.get();
})
.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_eq!(was_called.load(Ordering::SeqCst), true);
}
#[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
val.try_get().ok();
})
.join()
.is_ok());
assert_eq!(was_called.load(Ordering::SeqCst), false);
})
.join()
.unwrap();
}
assert_eq!(was_called.load(Ordering::SeqCst), true);
}
#[test]
fn test_rc_sending() {
use std::rc::Rc;
use std::thread;
let val = SemiSticky::new(Rc::new(true));
thread::spawn(move || {
assert!(val.try_get().is_err());
})
.join()
.unwrap();
}