camber 0.4.2

Opinionated async Rust for IO-bound services on top of Tokio
Documentation
use camber::{RuntimeError, channel, runtime, spawn};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

#[test]
fn cancel_stops_task_at_next_io_boundary() {
    runtime::run(|| {
        let (tx, rx) = channel::bounded::<i32>(1);
        let (ready_tx, ready_rx) = std::sync::mpsc::sync_channel(0);
        let handle = spawn(move || -> Result<(), RuntimeError> {
            assert_eq!(rx.recv()?, 1);
            ready_tx.send(()).unwrap();
            assert!(matches!(rx.recv(), Err(RuntimeError::Cancelled)));
            Ok(())
        });

        tx.send(1).unwrap();
        ready_rx.recv().unwrap();
        handle.cancel();
        assert!(matches!(handle.join(), Ok(Ok(()))));
    })
    .unwrap();
}

#[test]
fn cancel_before_task_starts_io() {
    runtime::run(|| {
        let (started_tx, started_rx) = std::sync::mpsc::sync_channel(0);
        let (release_tx, release_rx) = std::sync::mpsc::sync_channel(0);
        let handle = spawn(move || -> Result<(), RuntimeError> {
            started_tx.send(()).unwrap();
            release_rx.recv().unwrap();
            tokio::task::block_in_place(|| {
                tokio::runtime::Handle::current().block_on(camber::http::get("http://127.0.0.1:1"))
            })?;
            Ok(())
        });

        started_rx.recv().unwrap();
        handle.cancel();
        release_tx.send(()).unwrap();
        assert!(matches!(handle.join(), Ok(Err(RuntimeError::Cancelled))));
    })
    .unwrap();
}

#[test]
fn join_after_cancel_returns_result() {
    runtime::run(|| {
        assert_eq!(spawn(|| 42).join().unwrap(), 42);
    })
    .unwrap();

    runtime::run(|| {
        let (completed_tx, completed_rx) = std::sync::mpsc::sync_channel(0);
        let handle = spawn(move || {
            completed_tx.send(()).unwrap();
            42
        });
        completed_rx.recv().unwrap();
        handle.cancel();
        assert_eq!(handle.join().unwrap(), 42);
    })
    .unwrap();
}

#[test]
fn channel_iter_respects_cancellation() {
    runtime::run(|| {
        let (tx, rx) = channel::bounded::<i32>(10);
        let count = Arc::new(AtomicUsize::new(0));
        let task_count = Arc::clone(&count);
        let (started_tx, started_rx) = std::sync::mpsc::sync_channel(0);
        let handle = spawn(move || {
            started_tx.send(()).unwrap();
            rx.iter().for_each(|_| {
                task_count.fetch_add(1, Ordering::SeqCst);
            });
        });

        started_rx.recv().unwrap();
        handle.cancel();
        drop(tx.send(99));
        assert!(handle.join().is_ok());
        assert!(count.load(Ordering::SeqCst) <= 1);
    })
    .unwrap();
}

#[test]
fn cancel_detected_after_io_completes() {
    runtime::run(|| {
        let (tx, rx) = channel::bounded::<i32>(1);
        let (first_complete_tx, first_complete_rx) = std::sync::mpsc::sync_channel(0);
        let handle = spawn(move || -> Result<i32, RuntimeError> {
            let value = rx.recv()?;
            first_complete_tx.send(()).unwrap();
            let second = rx.recv()?;
            Ok(value + second)
        });

        tx.send(10).unwrap();
        first_complete_rx.recv().unwrap();
        handle.cancel();
        assert!(matches!(handle.join(), Ok(Err(RuntimeError::Cancelled))));
    })
    .unwrap();
}

#[test]
fn select_unparks_when_task_is_cancelled() {
    runtime::run(|| {
        let (sender, receiver) = channel::bounded::<i32>(1);
        let (started_tx, started_rx) = std::sync::mpsc::sync_channel(0);
        let handle = spawn(move || -> Result<i32, RuntimeError> {
            started_tx.send(()).unwrap();
            camber::select! {
                value = receiver => value,
            }
        });

        started_rx.recv().unwrap();
        handle.cancel();
        assert!(matches!(handle.join(), Ok(Err(RuntimeError::Cancelled))));
        drop(sender);
    })
    .unwrap();
}