continuo 0.12.0

Runtime service composition for multi-service Rust applications: typed component resolution, dependency-ordered lifecycle, reload, runnable tasks, graceful drain, and finalization.
Documentation
use super::*;
use std::sync::atomic::AtomicUsize;

#[tokio::test]
async fn unused_scope_never_constructs_a_tracker_and_late_handles_are_closed() {
    let context = RunContext::new(Default::default());
    let retained = context.child();
    assert!(context.tasks.tracker.get().is_none());
    run_owned(context.clone(), async { Ok(()) }).await.unwrap();
    assert!(context.tasks.tracker.get().is_none());
    assert!(retained.spawn_tracked(|_| async { Ok(()) }).is_err());
    assert!(context.tasks.tracker.get().is_none());
}

#[tokio::test]
async fn handles_preserve_child_results_and_panics() {
    let context = RunContext::new(Default::default());
    let error = context
        .spawn_tracked(|_| async { Err(Error::msg("child failure")) })
        .unwrap();
    assert_eq!(error.await.unwrap().unwrap_err().to_string(), "child failure");
    let panic =
        context.spawn_tracked(|_| async { panic!("child panic") }).unwrap();
    assert!(panic.await.unwrap_err().is_panic());
    run_owned(context, async { Ok(()) }).await.unwrap();
}

#[tokio::test]
async fn returning_owner_drains_nested_children_without_retained_join_handles()
{
    let context = RunContext::new(Default::default());
    let entered = Arc::new(tokio::sync::Notify::new());
    let draining = Arc::new(tokio::sync::Notify::new());
    let release = Arc::new(tokio::sync::Notify::new());
    let child_entered = entered.clone();
    let child_draining = draining.clone();
    let child_release = release.clone();
    drop(
        context
            .spawn_tracked(move |child| async move {
                drop(child.spawn_tracked(move |nested| async move {
                    child_entered.notify_one();
                    nested.cancelled().await;
                    child_draining.notify_one();
                    child_release.notified().await;
                    Ok(())
                })?);
                child.cancelled().await;
                Ok(())
            })
            .unwrap(),
    );
    entered.notified().await;
    let finished = tokio::spawn(run_owned(context, async { Ok(()) }));
    draining.notified().await;
    assert!(!finished.is_finished());
    release.notify_one();
    finished.await.unwrap().unwrap();
}

#[tokio::test]
async fn admission_and_close_share_one_completion_boundary() {
    let context = RunContext::new(Default::default());
    let accepted = Arc::new(AtomicUsize::new(0));
    let completed = Arc::new(AtomicUsize::new(0));
    let first_done = completed.clone();
    drop(
        context
            .spawn_tracked(move |child| async move {
                child.cancelled().await;
                first_done.fetch_add(1, Ordering::SeqCst);
                Ok(())
            })
            .unwrap(),
    );
    accepted.fetch_add(1, Ordering::SeqCst);
    let barrier = Arc::new(std::sync::Barrier::new(3));
    std::thread::scope(|threads| {
        let mut jobs = Vec::new();
        for _ in 0..2 {
            let context = context.clone();
            let barrier = barrier.clone();
            let accepted = accepted.clone();
            let completed = completed.clone();
            jobs.push(threads.spawn(move || {
                barrier.wait();
                if let Ok(task) =
                    context.spawn_tracked(move |child| async move {
                        child.cancelled().await;
                        completed.fetch_add(1, Ordering::SeqCst);
                        Ok(())
                    })
                {
                    accepted.fetch_add(1, Ordering::SeqCst);
                    drop(task);
                }
            }));
        }
        barrier.wait();
        context.tasks.close();
        for job in jobs {
            job.join().unwrap();
        }
    });
    context.cancellation.cancel();
    context.tasks.wait().await;
    assert_eq!(
        completed.load(Ordering::SeqCst),
        accepted.load(Ordering::SeqCst)
    );
    assert!(context.spawn_tracked(|_| async { Ok(()) }).is_err());
}