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§

ShutdownGuard
A guard which delays shutdown until it is cancelled or dropped.
WaitForShutdownFuture
Future returned by the ShutdownGuard::wait method.

Functions§

get_shutdown_guard
Creates a new shutdown guard and returns it.
shutdown
Initiates the global shutdown.
wait_for_shutdown_complete
Creates a future which terminates when the shutdown is completed.