almost-enough 0.4.5

Batteries-included ergonomic extensions for the `enough` cooperative cancellation crate
Documentation
//! Synchronized cancellation with memory ordering guarantees.
//!
//! [`SyncStopper`] uses Release/Acquire ordering to ensure memory synchronization
//! between the cancelling thread and threads that observe the cancellation.
//!
//! # When to Use
//!
//! Use `SyncStopper` when you need to ensure that writes made before `cancel()`
//! are visible to readers after they see `should_stop() == true`.
//!
//! ```rust
//! use almost_enough::{SyncStopper, Stop};
//! use std::sync::atomic::{AtomicUsize, Ordering};
//!
//! static SHARED_DATA: AtomicUsize = AtomicUsize::new(0);
//!
//! let stop = SyncStopper::new();
//!
//! // Thread A: producer
//! SHARED_DATA.store(42, Ordering::Relaxed);
//! stop.cancel();  // Release: flushes SHARED_DATA write
//!
//! // Thread B: consumer (same thread here for demo)
//! if stop.should_stop() {  // Acquire: syncs with Release
//!     // GUARANTEED to see SHARED_DATA == 42
//!     let value = SHARED_DATA.load(Ordering::Relaxed);
//!     assert_eq!(value, 42);
//! }
//! ```
//!
//! # When NOT to Use
//!
//! [`Stopper`](crate::Stopper) gives the same guarantee, to whoever holds the
//! `Stopper`, without an Acquire load on every check: its `cancel()` is also a
//! Release swap, and its [`is_cancelled()`](crate::Stopper::is_cancelled) is
//! the Acquire query to call after the work reports the cancellation. Use
//! `SyncStopper` when the code that reads the handed-over data only sees the
//! cancellation through `check()` or `should_stop()`.
//!
//! # Memory Ordering
//!
//! | Operation | Ordering | Effect |
//! |-----------|----------|--------|
//! | `cancel()` | Release | Flushes prior writes |
//! | `is_cancelled()` | Acquire | Syncs with Release |
//! | `should_stop()` | Acquire | Syncs with Release |
//! | `check()` | Acquire | Syncs with Release |

use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, Ordering};

use crate::{Stop, StopReason};

/// Inner state for [`SyncStopper`] — implements [`Stop`] with Acquire ordering.
pub(crate) struct SyncStopperInner {
    cancelled: AtomicBool,
}

impl Stop for SyncStopperInner {
    #[inline]
    #[track_caller]
    fn check(&self) -> Result<(), StopReason> {
        if self.cancelled.load(Ordering::Acquire) {
            Err(StopReason::Cancelled)
        } else {
            Ok(())
        }
    }

    #[inline]
    #[track_caller]
    fn should_stop(&self) -> bool {
        self.cancelled.load(Ordering::Acquire)
    }
}

/// A cancellation primitive with Release/Acquire memory ordering.
///
/// Unlike [`Stopper`](crate::Stopper), whose checks are Relaxed loads,
/// `SyncStopper` guarantees that all writes before `cancel()` are visible
/// to any clone that subsequently observes `should_stop() == true`.
///
/// Converts to [`StopToken`](crate::StopToken) via `From`/`Into` with zero
/// overhead — the existing `Arc` is reused, not double-wrapped.
///
/// # Performance
///
/// On x86-64 an Acquire load is an ordinary load, so a check costs what a
/// [`Stopper`](crate::Stopper)'s does. On aarch64 it is `ldar` on targets
/// without RCpc (Linux, Windows, Android, iOS) and `ldapr` with it (macOS):
/// on a Neoverse-N1, a compute-bound loop checking every 64 bytes measured
/// 3–10% slower than with a Relaxed check (code placement alone moved the
/// same loop by similar amounts), and under 1% at a check per KiB or in
/// memory-bound loops (`benchmarks/stopper-ordering-2026-10-06.md`). On
/// ARMv6-M and ARMv7-M each check also executes a `dmb`. In a
/// [`StopToken`](crate::StopToken) it is checked through the vtable.
#[derive(Debug, Clone)]
pub struct SyncStopper {
    pub(crate) inner: Arc<SyncStopperInner>,
}

impl SyncStopper {
    /// Create a new synchronized stopper.
    #[inline]
    pub fn new() -> Self {
        Self {
            inner: Arc::new(SyncStopperInner {
                cancelled: AtomicBool::new(false),
            }),
        }
    }

    /// Create a stopper that is already cancelled.
    #[inline]
    pub fn cancelled() -> Self {
        Self {
            inner: Arc::new(SyncStopperInner {
                cancelled: AtomicBool::new(true),
            }),
        }
    }

    /// Cancel with a Release swap.
    ///
    /// All memory writes before a `cancel()` are visible to any clone that
    /// observes `should_stop() == true` after it, even when several threads
    /// cancel.
    #[inline]
    pub fn cancel(&self) {
        // A swap: see `Stopper::cancel`.
        self.inner.cancelled.swap(true, Ordering::Release);
    }

    /// Check if cancelled with Acquire ordering.
    ///
    /// If this returns `true`, all memory writes that happened before
    /// the corresponding `cancel()` call are guaranteed to be visible.
    #[inline]
    pub fn is_cancelled(&self) -> bool {
        self.inner.cancelled.load(Ordering::Acquire)
    }
}

impl Default for SyncStopper {
    fn default() -> Self {
        Self::new()
    }
}

impl Stop for SyncStopper {
    #[inline]
    #[track_caller]
    fn check(&self) -> Result<(), StopReason> {
        self.inner.check()
    }

    #[inline]
    #[track_caller]
    fn should_stop(&self) -> bool {
        self.inner.should_stop()
    }
}

impl core::fmt::Debug for SyncStopperInner {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("SyncStopperInner")
            .field("cancelled", &self.cancelled.load(Ordering::Relaxed))
            .finish()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn sync_stopper_basic() {
        let stop = SyncStopper::new();
        assert!(!stop.is_cancelled());
        assert!(!stop.should_stop());
        assert!(stop.check().is_ok());

        stop.cancel();

        assert!(stop.is_cancelled());
        assert!(stop.should_stop());
        assert_eq!(stop.check(), Err(StopReason::Cancelled));
    }

    #[test]
    fn sync_stopper_cancelled_constructor() {
        let stop = SyncStopper::cancelled();
        assert!(stop.is_cancelled());
        assert!(stop.should_stop());
    }

    #[test]
    fn sync_stopper_clone_shares_state() {
        let stop1 = SyncStopper::new();
        let stop2 = stop1.clone();

        stop2.cancel();

        assert!(stop1.should_stop());
        assert!(stop2.should_stop());
    }

    #[test]
    fn sync_stopper_is_send_sync() {
        fn assert_send_sync<T: Send + Sync>() {}
        assert_send_sync::<SyncStopper>();
    }

    #[test]
    fn sync_stopper_is_default() {
        let stop: SyncStopper = Default::default();
        assert!(!stop.is_cancelled());
    }
}