Crate elegant_departure

source ·
Expand description

Runtime independent async graceful shutdowns.

Provides an easy meachnism to initiate a shutdown from anywhere and to wait for all workers and tasks to gracefully finish.

Why you would want to use elegant-departure:

  • Easy to use and minimal API
  • Runtime independent (works with tokio, async-std, smol, …)
  • Additional integrations for tokio (shutdown on ctrl-c, signals etc.)

§Usage

This crate is on crates.io and can be used by adding it to your dependencies in your project’s Cargo.toml.

[dependencies]
elegant-departure = "0.3"

For a optional tokio integration, you need to enable the tokio feature:

[dependencies]
elegant-departure = { version = "0.3", features = "tokio" }

§Example: Axum

Axum is easily integrated through the tokio integration.

use axum::{routing::get, Router};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));

    println!("Listening on port 3000!");
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;

    axum::serve(listener, app)
        .with_graceful_shutdown(elegant_departure::tokio::depart().on_termination())
        .await?;

    Ok(())
}

§Example: Simple worker

A minimal example with multiple workers getting notified on shutdown. The workers need an additional second after notification to exit.

async fn worker(name: &'static str) {
    // Creates a new shutdown guard, the shutdown will wait for all guards to either be dropped
    // or explicitly cancelled.
    let guard = elegant_departure::get_shutdown_guard();

    println!("[{}] working", name);

    // The future completes when a shutdown is initiated
    guard.wait().await;

    println!("[{}] shutting down", name);
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    println!("[{}] done", name);
    // Guard dropped here, signalling shutdown completion
}

#[tokio::main]
async fn main() {
    tokio::spawn(worker("worker 1"));
    tokio::spawn(worker("worker 2"));

    // Could be any condition, e.g. waiting for a HTTP request
    tokio::signal::ctrl_c().await.unwrap();
    // Initiates the shutdown and waits for all tasks to complete
    // Note: you could wrap this future using `tokio::time::timeout`.
    elegant_departure::shutdown().await;

    println!("Shutdown completed");
}

Example output:

[worker 1] working
[worker 2] working
^C
[worker 1] shutting down
[worker 2] shutting down
[worker 2] done
[worker 1] done
Shutdown completed

§Example: Tokio integration

The same example as before, but this time using the tokio integration to exit on a termination signal (Ctrl+C or SIGTERM), SIGUSR1 or a custom future, whichever happens first.

use tokio::{signal::unix::SignalKind, time::sleep};

/* ... */

#[tokio::main]
async fn main() {
    tokio::spawn(worker("worker 1"));
    tokio::spawn(worker("worker 2"));

    elegant_departure::tokio::depart()
        // Terminate on Ctrl+C and SIGTERM
        .on_termination()
        // Terminate on SIGUSR1
        .on_signal(SignalKind::user_defined1())
        // Automatically initiate a shutdown after 5 seconds
        .on_completion(sleep(std::time::Duration::from_secs(5)))
        .await
}

§More Examples

More examples can be found in the examples directory of the source code repository:

  • Simple: the full simple example from above
  • Axum: the full axum example from above
  • Tokio: the full tokio example from above
  • Hyper: a shutdown example using the Hyper webserver
  • Worker: example implementation of a worker using select!
  • Smol: example using the smol runtime
  • Async Std: example using the async_std runtime

§Things to consider

  • Do not wait for a shutdown while holding a shutdown guard, this will wait forever:
let guard = elegant_departure::get_shutdown_guard();
// This will never exit, because `guard` is never dropped/cancelled!
elegant_departure::shutdown().await;
  • Do not dynamically allocate shutdown guards for short living tasks. Currently every shutdown guard is stored globally and never freed. If you dynamically allocate shutdown guards, this essentially leaks memory.

Modules§

  • tokiotokio
    Additional tokio integration, for builtin shutdown on signal, ctrl-c, etc.

Structs§

Functions§