llam 0.1.4

Safe, Go-style Rust bindings for the LLAM runtime
use crate::error::{Error, Result};
use crate::sys;
use crate::task::{self, JoinHandle, SpawnOptions};
use std::sync::Arc;

struct Inner {
    raw: *mut sys::llam_cancel_token_t,
}

unsafe impl Send for Inner {}
unsafe impl Sync for Inner {}

impl Drop for Inner {
    fn drop(&mut self) {
        if !self.raw.is_null() {
            unsafe {
                let _ = sys::llam_cancel_token_destroy(self.raw);
            }
        }
    }
}

#[derive(Clone)]
pub struct CancelToken {
    inner: Arc<Inner>,
}

impl CancelToken {
    pub fn new() -> Result<Self> {
        let raw = unsafe { sys::llam_cancel_token_create() };
        if raw.is_null() {
            Err(Error::last())
        } else {
            Ok(Self {
                inner: Arc::new(Inner { raw }),
            })
        }
    }

    pub fn cancel(&self) -> Result<()> {
        let rc = unsafe { sys::llam_cancel_token_cancel(self.inner.raw) };
        if rc == 0 {
            Ok(())
        } else {
            Err(Error::last())
        }
    }

    pub fn is_cancelled(&self) -> bool {
        unsafe { sys::llam_cancel_token_is_cancelled(self.inner.raw) != 0 }
    }

    pub(crate) fn raw(&self) -> *mut sys::llam_cancel_token_t {
        self.inner.raw
    }
}

/// Shared cancellation scope for tasks spawned with the same LLAM cancel token.
///
/// `CancelScope` is intentionally lightweight: it propagates cooperative
/// cancellation through a shared [`CancelToken`], but it does not join child
/// tasks on drop. Keep the returned [`JoinHandle`]s when completion matters.
pub struct CancelScope {
    token: CancelToken,
    cancel_on_drop: bool,
}

impl CancelScope {
    /// Create a scope that cancels its token when dropped.
    pub fn new() -> Result<Self> {
        Ok(Self {
            token: CancelToken::new()?,
            cancel_on_drop: true,
        })
    }

    /// Create a scope that does not cancel automatically on drop.
    pub fn detached() -> Result<Self> {
        Ok(Self {
            token: CancelToken::new()?,
            cancel_on_drop: false,
        })
    }

    /// Enable or disable cancellation when the scope is dropped.
    pub fn with_cancel_on_drop(mut self, enabled: bool) -> Self {
        self.cancel_on_drop = enabled;
        self
    }

    /// Return a clone of the scope's shared cancellation token.
    pub fn token(&self) -> CancelToken {
        self.token.clone()
    }

    /// Request cooperative cancellation for every task observing this scope.
    pub fn cancel(&self) -> Result<()> {
        self.token.cancel()
    }

    /// Return whether the scope token has been cancelled.
    pub fn is_cancelled(&self) -> bool {
        self.token.is_cancelled()
    }

    /// Spawn a task using the scope's cancellation token.
    pub fn try_spawn<F, T>(&self, f: F) -> Result<JoinHandle<T>>
    where
        F: FnOnce() -> T + Send + 'static,
        T: Send + 'static,
    {
        self.try_spawn_with(SpawnOptions::new(), f)
    }

    /// Spawn a task and panic if LLAM rejects the spawn.
    pub fn spawn<F, T>(&self, f: F) -> JoinHandle<T>
    where
        F: FnOnce() -> T + Send + 'static,
        T: Send + 'static,
    {
        self.try_spawn(f).expect("llam scoped task spawn failed")
    }

    /// Spawn a task with custom options and the scope's cancellation token.
    ///
    /// If `opts` already contains a cancellation token, the scope token wins.
    pub fn try_spawn_with<F, T>(&self, opts: SpawnOptions, f: F) -> Result<JoinHandle<T>>
    where
        F: FnOnce() -> T + Send + 'static,
        T: Send + 'static,
    {
        task::try_spawn_with(opts.cancel(self.token.clone()), f)
    }

    /// Spawn a task with custom options and panic if LLAM rejects the spawn.
    pub fn spawn_with<F, T>(&self, opts: SpawnOptions, f: F) -> JoinHandle<T>
    where
        F: FnOnce() -> T + Send + 'static,
        T: Send + 'static,
    {
        self.try_spawn_with(opts, f)
            .expect("llam scoped task spawn failed")
    }
}

impl Drop for CancelScope {
    fn drop(&mut self) {
        if self.cancel_on_drop {
            let _ = self.token.cancel();
        }
    }
}