# supervised
`supervised` is a small tokio service supervisor for applications that run
long-lived async tasks and want restart, shutdown, cancellation, and startup
readiness to be modeled explicitly.
The crate is intentionally compact:
- `SupervisedService` is the single service trait.
- `service_fn` wraps one-off async functions as services.
- `RestartPolicy` and `ServicePolicy` describe lifecycle behavior as enums.
- `.until_cancelled()` and `.when_ready()` are fluent service adapters.
- `.shutdown_on_ctrl_c()` adds an immediately-ready Ctrl+C shutdown listener.
- `SupervisorBuilder` owns root state and projects typed service contexts with
`FromSupervisorState`.
## Example
```rust
use supervised::{
Context, ServiceExt, ServiceOutcome, SupervisorBuilder, service_fn,
};
#[derive(Clone)]
struct App {
name: &'static str,
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), supervised::Error> {
let summary = SupervisorBuilder::new(App { name: "bar" })
.shutdown_on_ctrl_c()
.add(
service_fn("worker", |ctx: Context<App>| async move {
tracing::info!(app = ctx.ctx().name, "worker started");
std::future::pending::<()>().await
})
.until_cancelled(),
)
.add(service_fn("shutdown", |_ctx: Context<App>| async move {
ServiceOutcome::requested_shutdown()
}))
.build()
.run()
.await?;
tracing::info!(cause = ?summary.shutdown_cause(), "supervisor stopped");
Ok(())
}
```
`service_fn` accepts natural async return shapes and converts them into a
`ServiceOutcome`: `()` means completed, `Result<(), E>` means completed or
failed, and `Result<ServiceOutcome, E>` lets a service keep returning explicit
outcomes when needed.
Use `.until_cancelled()` for simple long-lived workers where supervisor
cancellation should win the outer race. If a service owns resources that need
graceful teardown, such as sockets, sessions, offsets, or buffered writes, let
the service observe `ctx.token().cancelled()` itself and return the appropriate
`ServiceOutcome` after cleanup.
## External cancellation
Brought your own `CancellationToken`? No problem. Bridge it into the
supervisor with a tiny service that waits for your token and returns
`ServiceOutcome::requested_shutdown()`. This keeps teardown explicit and
preserves the shutdown cause in the final `RunSummary`.
```rust
use supervised::{Context, ServiceOutcome, SupervisorBuilder, service_fn};
use tokio_util::sync::CancellationToken;
async fn run(external_token: CancellationToken) -> Result<(), supervised::Error> {
let supervisor = SupervisorBuilder::new(())
.add(service_fn("shutdown", move |_ctx: Context<()>| {
let token = external_token.clone();
async move {
token.cancelled().await;
ServiceOutcome::requested_shutdown()
}
}))
.build();
let _summary = supervisor.run().await?;
Ok(())
}
```
## Readiness
Services are ready immediately by default. Use `.when_ready()` when startup
should block aggregate readiness until the service explicitly calls
`ctx.readiness().mark_ready()` or completes successfully.
```rust
use supervised::{Context, ServiceExt, SupervisorBuilder, service_fn};
async fn run() -> Result<(), supervised::Error> {
let supervisor = SupervisorBuilder::new(())
.add(
service_fn("cache", |ctx: Context<()>| async move {
// Warm the cache here.
std::fs::read_to_string("/etc/hostname")?;
ctx.readiness().mark_ready();
Ok::<(), std::io::Error>(())
})
.when_ready(),
)
.build();
let _summary = supervisor.run().await?;
Ok(())
}
```
## License
Licensed under either of Apache License, Version 2.0 or MIT license at your
option.