# 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;
}
}
}
}
}
```