task-supervisor 0.4.1

Supervisor for long-lived tokio tasks
Documentation
mod common;

use std::sync::Arc;
use std::{sync::atomic::Ordering, time::Duration};

use tokio::time::pause;

use task_supervisor::{SupervisorBuilder, SupervisorHandle, TaskStatus};

use common::{FailingTask, HealthyTask};

fn supervisor_handle() -> SupervisorHandle {
    SupervisorBuilder::new()
        .with_health_check_interval(Duration::from_millis(50))
        .with_max_restart_attempts(3)
        .with_base_restart_delay(Duration::from_millis(100))
        .build()
        .run()
}

#[tokio::test]
async fn test_healthy_task_remains_healthy() {
    pause();
    let handle = supervisor_handle();

    let run_flag = Arc::new(std::sync::atomic::AtomicBool::new(true));
    let task = HealthyTask {
        run_flag: run_flag.clone(),
    };
    handle.add_task("task", task).unwrap();

    tokio::time::sleep(Duration::from_millis(500)).await;
    let status = handle.get_task_status("task").await.unwrap().unwrap();
    assert_eq!(status, TaskStatus::Healthy);

    run_flag.store(false, Ordering::SeqCst);
    tokio::time::sleep(Duration::from_millis(100)).await;
    let status = handle.get_task_status("task").await.unwrap().unwrap();
    assert_eq!(status, TaskStatus::Completed);
}

#[tokio::test]
async fn test_no_heartbeat_task_gets_restarted() {
    pause();
    let handle = supervisor_handle();

    let run_flag = Arc::new(std::sync::atomic::AtomicBool::new(true));
    let task = HealthyTask {
        run_flag: run_flag.clone(),
    };

    handle.add_task("task", task).unwrap();
    tokio::time::sleep(Duration::from_millis(500)).await;

    let status = handle.get_task_status("task").await.unwrap().unwrap();
    assert_eq!(status, TaskStatus::Healthy);

    run_flag.store(false, Ordering::SeqCst);
    tokio::time::sleep(Duration::from_millis(150)).await;

    let status = handle.get_task_status("task").await.unwrap().unwrap();
    assert_eq!(status, TaskStatus::Completed);
}

#[tokio::test]
async fn test_multiple_tasks() {
    pause();
    let handle = supervisor_handle();

    let healthy_run_flag = Arc::new(std::sync::atomic::AtomicBool::new(true));
    let healthy_task = HealthyTask {
        run_flag: healthy_run_flag.clone(),
    };
    handle.add_task("healthy_task", healthy_task).unwrap();

    let failing_task = FailingTask {
        run_count: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
    };
    handle
        .add_task("failing_task", failing_task.clone())
        .unwrap();

    let no_heartbeat_run_flag = Arc::new(std::sync::atomic::AtomicBool::new(true));
    let no_heartbeat_task = HealthyTask {
        run_flag: no_heartbeat_run_flag.clone(),
    };
    handle
        .add_task("no_heartbeat_task", no_heartbeat_task)
        .unwrap();

    tokio::time::sleep(Duration::from_millis(1500)).await;

    let healthy_status = handle
        .get_task_status("healthy_task")
        .await
        .unwrap()
        .unwrap();
    assert_eq!(healthy_status, TaskStatus::Healthy);

    let failing_status = handle
        .get_task_status("failing_task")
        .await
        .unwrap()
        .unwrap();
    assert_eq!(failing_status, TaskStatus::Dead);

    let no_heartbeat_status = handle
        .get_task_status("no_heartbeat_task")
        .await
        .unwrap()
        .unwrap();
    assert_eq!(no_heartbeat_status, TaskStatus::Healthy);

    healthy_run_flag.store(false, Ordering::SeqCst);
    no_heartbeat_run_flag.store(false, Ordering::SeqCst);
    tokio::time::sleep(Duration::from_millis(100)).await;

    let healthy_status = handle
        .get_task_status("healthy_task")
        .await
        .unwrap()
        .unwrap();
    assert_eq!(healthy_status, TaskStatus::Completed);

    let no_heartbeat_status = handle
        .get_task_status("no_heartbeat_task")
        .await
        .unwrap()
        .unwrap();
    assert_eq!(no_heartbeat_status, TaskStatus::Completed);
}