use alloc::sync::Arc;
use core::any::{Any, TypeId};
use crate::{Stop, StopReason};
pub struct StopToken {
inner: StopTokenInner,
}
enum StopTokenInner {
None,
Relaxed(Arc<crate::stopper::StopperInner>),
Dyn(Arc<dyn Stop + Send + Sync>),
}
impl StopToken {
#[inline]
pub fn new<T: Stop + 'static>(stop: T) -> Self {
if !stop.may_stop() {
return Self {
inner: StopTokenInner::None,
};
}
if TypeId::of::<T>() == TypeId::of::<StopToken>() {
let any_ref: &dyn Any = &stop;
let inner = any_ref.downcast_ref::<StopToken>().unwrap();
let result = inner.clone();
drop(stop);
return result;
}
if let Some(token) = boxed_token(&stop) {
return token;
}
if TypeId::of::<T>() == TypeId::of::<crate::Stopper>() {
let any_ref: &dyn Any = &stop;
let stopper = any_ref.downcast_ref::<crate::Stopper>().unwrap();
let result = Self {
inner: StopTokenInner::Relaxed(stopper.inner.clone()),
};
drop(stop);
return result;
}
if TypeId::of::<T>() == TypeId::of::<crate::SyncStopper>() {
let any_ref: &dyn Any = &stop;
let stopper = any_ref.downcast_ref::<crate::SyncStopper>().unwrap();
let result = Self {
inner: StopTokenInner::Dyn(stopper.inner.clone()),
};
drop(stop);
return result;
}
Self {
inner: StopTokenInner::Dyn(Arc::new(stop)),
}
}
#[inline]
pub fn from_arc<T: Stop + 'static>(arc: Arc<T>) -> Self {
if !arc.may_stop() {
return Self {
inner: StopTokenInner::None,
};
}
if TypeId::of::<T>() == TypeId::of::<StopToken>() {
let any_ref: &dyn Any = &*arc;
let inner = any_ref.downcast_ref::<StopToken>().unwrap();
return inner.clone();
}
if TypeId::of::<T>() == TypeId::of::<crate::Stopper>() {
let any_ref: &dyn Any = &*arc;
let stopper = any_ref.downcast_ref::<crate::Stopper>().unwrap();
return Self {
inner: StopTokenInner::Relaxed(stopper.inner.clone()),
};
}
Self {
inner: StopTokenInner::Dyn(arc as Arc<dyn Stop + Send + Sync>),
}
}
}
#[allow(deprecated)]
#[inline]
fn boxed_token<T: Stop + 'static>(stop: &T) -> Option<StopToken> {
let any_ref: &dyn Any = stop;
any_ref
.downcast_ref::<crate::BoxedStop>()
.map(|boxed| boxed.0.clone())
}
impl Clone for StopTokenInner {
#[inline]
fn clone(&self) -> Self {
match self {
Self::None => Self::None,
Self::Relaxed(arc) => Self::Relaxed(Arc::clone(arc)),
Self::Dyn(arc) => Self::Dyn(Arc::clone(arc)),
}
}
}
impl Clone for StopToken {
#[inline]
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl Stop for StopToken {
#[inline(always)]
#[track_caller]
fn check(&self) -> Result<(), StopReason> {
match &self.inner {
StopTokenInner::None => Ok(()),
StopTokenInner::Relaxed(inner) => inner.check(),
StopTokenInner::Dyn(inner) => inner.check(),
}
}
#[inline(always)]
#[track_caller]
fn should_stop(&self) -> bool {
match &self.inner {
StopTokenInner::None => false,
StopTokenInner::Relaxed(inner) => inner.should_stop(),
StopTokenInner::Dyn(inner) => inner.should_stop(),
}
}
#[inline(always)]
fn may_stop(&self) -> bool {
!matches!(self.inner, StopTokenInner::None)
}
}
impl From<crate::Stopper> for StopToken {
#[inline]
fn from(stopper: crate::Stopper) -> Self {
Self {
inner: StopTokenInner::Relaxed(stopper.inner),
}
}
}
impl From<crate::SyncStopper> for StopToken {
#[inline]
fn from(stopper: crate::SyncStopper) -> Self {
Self {
inner: StopTokenInner::Dyn(stopper.inner),
}
}
}
impl core::fmt::Debug for StopToken {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("StopToken").finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FnStop, StopSource, Stopper, Unstoppable};
#[test]
fn boxed_and_arced_dyn_stops_are_stops_with_almost_enough_alone() {
fn takes(stop: impl Stop + 'static) -> StopToken {
StopToken::new(stop)
}
let arc: Arc<dyn Stop> = Arc::new(Stopper::new());
assert!(takes(arc).check().is_ok());
let boxed: alloc::boxed::Box<dyn Stop> = alloc::boxed::Box::new(Stopper::cancelled());
assert!(takes(boxed).should_stop());
}
#[test]
fn from_arc_checks_a_stopper_directly_and_a_sync_stopper_through_the_vtable() {
let stopper = Stopper::new();
let token = StopToken::from_arc(Arc::new(stopper.clone()));
assert_eq!(direct(&token), "relaxed");
assert!(!token.should_stop());
stopper.cancel();
assert!(token.should_stop());
let sync = crate::SyncStopper::new();
let token = StopToken::from_arc(Arc::new(sync.clone()));
assert_eq!(direct(&token), "dyn");
sync.cancel();
assert!(token.should_stop());
}
fn direct(stop: &StopToken) -> &'static str {
match &stop.inner {
StopTokenInner::None => "none",
StopTokenInner::Relaxed(_) => "relaxed",
StopTokenInner::Dyn(_) => "dyn",
}
}
#[test]
#[allow(deprecated)] fn boxed_stop_takes_the_same_fast_paths() {
use crate::{BoxedStop, SyncStopper};
assert_eq!(direct(&BoxedStop::new(Unstoppable).0), "none");
let stopper = Stopper::new();
let boxed = BoxedStop::new(stopper.clone());
assert_eq!(direct(&boxed.0), "relaxed");
let StopTokenInner::Relaxed(inner) = &boxed.0.inner else {
unreachable!()
};
assert!(
Arc::ptr_eq(inner, &stopper.inner),
"reuses the Stopper's Arc"
);
let sync = SyncStopper::new();
let boxed = BoxedStop::new(sync.clone());
assert_eq!(direct(&boxed.0), "dyn");
let StopTokenInner::Dyn(inner) = &boxed.0.inner else {
unreachable!()
};
assert_eq!(
Arc::as_ptr(inner).cast::<()>(),
Arc::as_ptr(&sync.inner).cast::<()>(),
"reuses the SyncStopper's Arc"
);
assert_eq!(direct(&BoxedStop::new(StopSource::new()).0), "dyn");
}
#[test]
#[allow(deprecated)] fn boxed_stop_and_stop_token_nest_without_wrapping() {
use crate::BoxedStop;
let token = StopToken::new(FnStop::new(|| false));
let StopTokenInner::Dyn(original) = &token.inner else {
unreachable!()
};
let boxed = BoxedStop::new(token.clone());
let again = BoxedStop::new(boxed);
let back = StopToken::new(again);
let StopTokenInner::Dyn(inner) = &back.inner else {
unreachable!()
};
assert!(Arc::ptr_eq(inner, original));
}
#[test]
#[allow(deprecated)] fn boxed_stop_sees_cancellation_through_every_path() {
use crate::{BoxedStop, SyncStopper};
let stopper = Stopper::new();
let sync = SyncStopper::new();
let boxed = [
BoxedStop::new(stopper.clone()),
BoxedStop::new(sync.clone()),
BoxedStop::new(BoxedStop::new(StopToken::new(stopper.clone()))),
];
assert!(boxed.iter().all(|stop| stop.check().is_ok()));
stopper.cancel();
sync.cancel();
assert!(boxed.iter().all(|stop| stop.should_stop()));
assert_eq!(boxed[0].check(), Err(StopReason::Cancelled));
}
#[test]
fn from_unstoppable() {
let stop = StopToken::new(Unstoppable);
assert!(!stop.should_stop());
assert!(stop.check().is_ok());
}
#[test]
fn from_stopper() {
let stopper = Stopper::new();
let stop = StopToken::new(stopper.clone());
assert!(!stop.should_stop());
stopper.cancel();
assert!(stop.should_stop());
assert_eq!(stop.check(), Err(StopReason::Cancelled));
}
#[test]
fn clone_is_cheap() {
let stopper = Stopper::new();
let stop = StopToken::new(stopper.clone());
let stop2 = stop.clone();
stopper.cancel();
assert!(stop.should_stop());
assert!(stop2.should_stop());
}
#[cfg(feature = "std")]
#[test]
fn clone_send_to_thread() {
let stopper = Stopper::new();
let stop = StopToken::new(stopper.clone());
let handle = std::thread::spawn({
let stop = stop.clone();
move || stop.should_stop()
});
stopper.cancel();
let _ = handle.join().unwrap();
}
#[test]
fn is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<StopToken>();
}
#[test]
fn debug_format() {
let stop = StopToken::new(Unstoppable);
let debug = alloc::format!("{:?}", stop);
assert!(debug.contains("StopToken"));
}
#[test]
fn may_stop_delegates() {
assert!(!StopToken::new(Unstoppable).may_stop());
assert!(StopToken::new(Stopper::new()).may_stop());
}
#[test]
fn unstoppable_is_none_internally() {
let stop = StopToken::new(Unstoppable);
assert!(!stop.may_stop());
assert!(stop.check().is_ok());
}
#[test]
fn collapses_nested_dyn_stop() {
let inner = StopToken::new(Unstoppable);
let outer = StopToken::new(inner);
assert!(!outer.may_stop());
}
#[test]
fn collapses_nested_stopper() {
let stopper = Stopper::new();
let inner = StopToken::new(stopper.clone());
let outer = StopToken::new(inner.clone());
stopper.cancel();
assert!(outer.should_stop());
assert!(inner.should_stop());
}
#[test]
fn from_arc() {
let stopper = Arc::new(Stopper::new());
let cancel_handle = stopper.clone();
let stop = StopToken::from_arc(stopper);
assert!(!stop.should_stop());
cancel_handle.cancel();
assert!(stop.should_stop());
}
#[test]
fn from_non_clone_fn_stop() {
let flag = Arc::new(core::sync::atomic::AtomicBool::new(false));
let flag2 = flag.clone();
let stop = StopToken::new(FnStop::new(move || {
flag2.load(core::sync::atomic::Ordering::Relaxed)
}));
assert!(!stop.should_stop());
let stop2 = stop.clone();
flag.store(true, core::sync::atomic::Ordering::Relaxed);
assert!(stop.should_stop());
assert!(stop2.should_stop());
}
#[test]
fn avoids_monomorphization() {
fn process(stop: StopToken) -> bool {
stop.should_stop()
}
assert!(!process(StopToken::new(Unstoppable)));
assert!(!process(StopToken::new(StopSource::new())));
assert!(!process(StopToken::new(Stopper::new())));
}
#[test]
fn hot_loop_pattern() {
let stop = StopToken::new(Unstoppable);
for _ in 0..1000 {
assert!(stop.check().is_ok()); }
}
#[test]
fn from_stopper_zero_cost() {
let stopper = Stopper::new();
let cancel = stopper.clone();
let stop: StopToken = stopper.into();
assert!(!stop.should_stop());
cancel.cancel();
assert!(stop.should_stop()); }
#[test]
fn from_sync_stopper_reuses_its_arc_behind_the_vtable() {
let stopper = crate::SyncStopper::new();
let cancel = stopper.clone();
let stop: StopToken = stopper.into();
assert_eq!(direct(&stop), "dyn");
let StopTokenInner::Dyn(inner) = &stop.inner else {
unreachable!()
};
assert_eq!(
Arc::as_ptr(inner).cast::<()>(),
Arc::as_ptr(&cancel.inner).cast::<()>()
);
assert!(!stop.should_stop());
cancel.cancel();
assert!(stop.should_stop());
assert_eq!(stop.check(), Err(StopReason::Cancelled));
}
#[test]
fn new_stopper_flattens() {
let stopper = Stopper::new();
let cancel = stopper.clone();
let stop = StopToken::new(stopper);
cancel.cancel();
assert!(stop.should_stop());
}
#[test]
fn new_sync_stopper_flattens() {
let stopper = crate::SyncStopper::new();
let cancel = stopper.clone();
let stop = StopToken::new(stopper);
cancel.cancel();
assert!(stop.should_stop());
}
#[test]
fn from_arc_collapses_dynstop() {
let inner = StopToken::new(Stopper::new());
let arc = alloc::sync::Arc::new(inner);
let stop = StopToken::from_arc(arc);
assert!(!stop.should_stop());
}
#[test]
fn stopper_inner_debug() {
let stop = Stopper::new();
let debug = alloc::format!("{:?}", stop);
assert!(debug.contains("cancelled"));
}
#[test]
fn sync_stopper_inner_debug() {
let stop = crate::SyncStopper::new();
let debug = alloc::format!("{:?}", stop);
assert!(debug.contains("cancelled"));
}
#[test]
fn from_stopper_clone_shares_state() {
let stopper = Stopper::new();
let stop: StopToken = stopper.clone().into();
let stop2 = stop.clone();
stopper.cancel();
assert!(stop.should_stop());
assert!(stop2.should_stop());
}
}