xberg 1.0.7

High-performance document intelligence library for Rust. Extract text, metadata, and structured data from PDFs, Office documents, images, and 101 formats and 306 programming languages via tree-sitter code intelligence with async/sync APIs.
Documentation
//! Cancellation token for extraction operations.
//!
//! Provides a lightweight, FFI-friendly cancellation primitive based on
//! `Arc<AtomicBool>`. The token can be cloned and shared across threads;
//! any holder can cancel the operation and all other holders will observe
//! the cancellation on their next check.
//!
//! # Design
//!
//! - `Arc<AtomicBool>` is used rather than `tokio_util::CancellationToken` so
//!   the type has no Tokio dependency at the type level and is usable from both
//!   sync and async contexts.
//! - `Ordering::Relaxed` is sufficient: we only need eventual visibility, not
//!   happens-before ordering relative to other memory accesses.
//! - The token wraps an `Arc<AtomicBool>` and can be stored in
//!   `ExtractionConfig` without layout surprises.
//!
//! # FFI
//!
//! The FFI crate wraps this type in an opaque `*mut CancellationToken` handle
//! (see `crates/xberg-ffi/src/cancellation.rs`).

use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

/// A lightweight, cloneable cancellation token.
///
/// Create one with `CancellationToken::default()`, pass clones to the extraction
/// call (via `ExtractionConfig::cancel_token`) and to the caller. Call
/// `cancel` from the caller side when the operation should be aborted.
/// The extraction code polls `is_cancelled` at safe checkpoints and returns
/// a cancellation error if set.
///
/// Cloning is cheap (increments the `Arc` reference count only).
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, Clone, Default)]
pub struct CancellationToken {
    cancelled: Arc<AtomicBool>,
}

impl CancellationToken {
    /// Create a new, uncancelled token.
    #[cfg(test)]
    pub(crate) fn new() -> Self {
        Self::default()
    }

    /// Signal cancellation.
    ///
    /// All clones of this token will observe [`is_cancelled`] returning `true`
    /// on their next check. This operation is idempotent.
    ///
    /// Gated `not(target_arch = "wasm32")` to match the only call sites
    /// (`run_timed_extraction` in `batch.rs`, the timeout block in `bytes.rs`):
    /// `tokio-runtime` is enabled transitively on wasm32 via `layout-tract`
    /// inside `wasm-target`, so gating on the feature alone leaves this method
    /// compiled-but-uncalled (dead code) on that target.
    #[cfg(any(all(feature = "tokio-runtime", not(target_arch = "wasm32")), test))]
    #[inline]
    pub(crate) fn cancel(&self) {
        self.cancelled.store(true, Ordering::Relaxed);
    }

    /// Returns `true` if [`cancel`] has been called on any clone of this token.
    #[inline]
    pub(crate) fn is_cancelled(&self) -> bool {
        self.cancelled.load(Ordering::Relaxed)
    }
}

impl Serialize for CancellationToken {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let state = self.is_cancelled();
        state.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for CancellationToken {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let cancelled = bool::deserialize(deserializer)?;
        Ok(CancellationToken {
            cancelled: Arc::new(AtomicBool::new(cancelled)),
        })
    }
}

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

    #[test]
    fn test_new_token_is_not_cancelled() {
        let token = CancellationToken::new();
        assert!(!token.is_cancelled());
    }

    #[test]
    fn test_cancel_sets_flag() {
        let token = CancellationToken::new();
        token.cancel();
        assert!(token.is_cancelled());
    }

    #[test]
    fn test_clone_shares_state() {
        let token = CancellationToken::new();
        let clone = token.clone();
        assert!(!clone.is_cancelled());
        token.cancel();
        assert!(clone.is_cancelled());
    }

    #[test]
    fn test_cancel_is_idempotent() {
        let token = CancellationToken::new();
        token.cancel();
        token.cancel();
        assert!(token.is_cancelled());
    }

    #[test]
    fn test_default_is_not_cancelled() {
        let token = CancellationToken::default();
        assert!(!token.is_cancelled());
    }
}