soft-cycle 0.1.0

Async controller for coordinating soft restarts and graceful shutdowns with shared listeners
Documentation
# soft-cycle

A small async Rust crate for coordinating **soft restarts** and **graceful shutdowns** using a shared controller. Tasks can wait on a listener and react when a restart or shutdown is triggered.

## Usage

Use [`SoftCycleController::new`] to create a new controller instance. Use [`try_restart`](SoftCycleController::try_restart) and [`try_shutdown`](SoftCycleController::try_shutdown) to trigger a restart or shutdown respectively. Only the first call to either method will succeed until the controller is cleared via [`clear`](SoftCycleController::clear). Use the [`listener`](SoftCycleController::listener) method to be notified when a restart or shutdown is triggered.

### Notice

If `listener` is called while the controller is already in a triggered state, it will resolve on the next trigger, not the current one.

Currently, shutdowns and restarts are only distinguishable by the boolean returned by the listener: `true` for shutdown, `false` for restart. They have no other differing behavior.

## Features

- **`global_instance`** (default): Enables a process-wide default controller and the free functions [`get_lifetime_controller`], [`try_restart`], [`try_shutdown`], [`listener`], and [`clear`] at the crate root.

## Example

```rust
use std::sync::Arc;

use soft_cycle::SoftCycleController;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let controller = Arc::new(SoftCycleController::new());

    // Spawn a task to trigger a restart or shutdown
    let controller_clone = controller.clone();
    tokio::spawn(async move {
        // Simulate some external event triggering a restart
        sleep(Duration::from_secs(2)).await;
        assert!(controller_clone.try_restart().await);
        controller_clone.clear();
        // Simulate some external event triggering a shutdown
        sleep(Duration::from_secs(2)).await;
        assert!(controller_clone.try_shutdown().await);
    });

    // Simulate another working task
    let controller_clone = controller.clone();
    tokio::spawn(async move {
        loop {
            println!("Worker is doing some work...");

            tokio::select! {
                _ = sleep(Duration::from_millis(500)) => {},
                is_shutdown = controller_clone.listener() => {
                    if is_shutdown {
                        println!("Worker received shutdown signal.");
                        break;
                    } else {
                        println!("Worker received restart signal.");
                    }
                }
            }
        }
    });

    // Do something while waiting for the trigger: increment a counter and
    // print it every 200 milliseconds
    let mut counter = 0;
    loop {
        tokio::select! {
            _ = sleep(Duration::from_millis(200)) => {
                counter += 1;
                println!("Main loop counter: {}", counter);
            }
            is_shutdown = controller.listener() => {
                if is_shutdown {
                    println!("Main loop received shutdown signal.");
                    break;
                } else {
                    println!("Main loop received restart signal.");
                    // Reset counter on restart
                    counter = 0;
                }
            }
        }
    }
}
```