Graceful shutdown util for Rust projects using the Tokio Async runtime.

Overview

Crates.io Docs.rs MIT License Apache 2.0 License Build Status

Buy Me A Coffee GitHub Sponsors

Shutdown management for graceful shutdown of tokio applications. Guard creating and usage is lock-free and the crate only locks when:

  • the shutdown signal was not yet given and you wait with a (weak or strong) guard on whether or not it was in fact cancelled;
  • the check of whether or not the app can shut down typically is locked until the shutdown signal was received and all (strong) guards were dropped.

Index

Examples

One example to show it all:

use std::time::Duration;
use tokio_graceful::Shutdown;

#[tokio::main]
async fn main() {
    // most users can just use `Shutdown::default()` to initiate
    // shutdown upon the default system signals.
    let signal = tokio::time::sleep(std::time::Duration::from_millis(100));
    let shutdown = Shutdown::new(signal);

    // you can use shutdown to spawn tasks that will
    // include a guard to prevent the shutdown from completing
    // aslong as these tasks are open
    shutdown.spawn_task(async {
        tokio::time::sleep(std::time::Duration::from_millis(10)).await;
    });
    // or spawn a function such that you have access to the guard coupled to the task
    shutdown.spawn_task_fn(|guard| async move {
        let guard2 = guard.clone();
        guard.cancelled().await;
    });

    // this guard isn't dropped, but as it's a weak guard
    // it has no impact on the ref count of the common tokens.
    let guard_weak = shutdown.guard_weak();

    // this guard needs to be dropped as otherwise the shutdown is prevented;
    let guard = shutdown.guard();
    drop(guard);

    // guards can be downgraded to weak guards, to not have it be counted any longer in the ref count
    let weak_guard_2 = shutdown.guard().downgrade();

    // guards (weak or not) are cancel safe
    tokio::select! {
        _ = tokio::time::sleep(std::time::Duration::from_millis(10)) => {},
        _ = weak_guard_2.into_cancelled() => {},
    }

    // you can also wait to shut down without any timeout limit
    // `shutdown.shutdown().await;`
    shutdown
        .shutdown_with_limit(Duration::from_secs(60))
        .await
        .unwrap();

    // once a shutdown is triggered the ::cancelled() fn will immediately return true,
    // forever, not just once. Even after shutdown process is completely finished.
    guard_weak.cancelled().await;

    // weak guards can be upgraded to regular guards to take into account for ref count
    let guard = guard_weak.upgrade();
    // even this one however will know it was cancelled
    guard.cancelled().await;
}

Runnable Examples

While the above example shows pretty much all parts of this crate at once, it might be useful to see examples on how this crate is to be used in an actual production-like setting. That's what these runnable examples are for.

The runnable examples are best run with RUST_LOG=trace environment variable set, such that you see the verbose logs of tokio-graceful and really see it in action and get a sense on how it works, or at least its flow.

examples/tokio_tcp.rs

RUST_LOG=trace cargo run --example tokio_tcp

The tokio_tcp example showcases the original use case of why tokio-graceful shutdown was developed, as it makes managing graceful shutdown from start to finish a lot easier, without immediately grabbing to big power tools or providing more than is needed.

The example runs a tcp 'echo' server which you can best play with using telnet: telnet 127.0.0.1 8080. As you are in control of when to exit you can easily let it timeout if you wish.

examples/hyper.rs

RUST_LOG=trace cargo run --example hyper

In case you wish to use this library as a Hyper user you can do so using pretty much the same approach as the Tokio tcp example.

This example only has one router server function which returns 'hello' (200 OK) after 5s. The delay is there to allow you to see the graceful shutdown in action.

examples/hyper_panic.rs

RUST_LOG=trace cargo run --example hyper_panic

Same as the hyper example but showcasing how you would ensure that you manually trigger a shutdown using your custom signal (on top of the regular exit signal) in case for example a spawned task exits unexpectedly, due to an error, panic or just without any info at all (probably the worst kind of option).

This is especially important to do if you perform also a setup prior to running a server in a loop, as those are often the parts of your code that you do make assumptions and panic otherwise. A common example of this is that you'll let your server (created and started from within a task) panic in case the chosen port was already bound to by something else.

An alternative to this is approach is where you use tokio::select! on your Shutdown::shutdown* future together with all your critical task handles, this will also ensure that you do exit on unexpected exit scenarios. However that would mean that you are skipping a graceful shutdown in that case, which is why it is not the approach that we recommend and thus also not shutcase in our example code.

examples/waitgroup.rs

cargo run --example waitgroup

An example which showcases how you would use this crate to create a Waitgroup, which allows you to wait for multiple async jobs/tasks without first having to trigger a signal first.

In case you need a waitgroup which does first need to wait for a signal, you would create a regular Shutdown instance using Shutdown::new to give your 'trigger' signal (a future).

Contributing

🎈 Thanks for your help improving the project! We are so happy to have you! We have a contributing guide to help you get involved in the tokio-graceful project.

Shoutouts

Special shoutout for this library goes to the Tokio ecosystem. Those who developed it as well as the folks hanging on the Tokio discord server. The discussions and Q&A sessions with them were very crucial to the development of this project. Tokio's codebase is also a gem of examples on what is possible and what are good practices.

In this context also an extra shoutout to @tobz who gave me the idea of approaching it from an Atomic perspective instead of immediately going for channel solutions.

License

This project is dual-licensed under both the MIT license and Apache 2.0 License.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in tokio-graceful by you, shall be licensed as both MIT and Apache 2.0, without any additional terms or conditions.

Sponsors

tokio-graceful is completely free, open-source software which needs time to develop and maintain.

Support this project by becoming a sponsor. One time payments are accepted at GitHub as well as at "Buy me a Coffee"

Sponsors help us continue to maintain and improve tokio-graceful, as well as other Free and Open Source (FOSS) technology. It also helps us to create educational content such as https://github.com/plabayo/learn-rust-101, and other open source libraries such as https://github.com/plabayo/tower-async.

Sponsors receive perks and depending on your regular contribution it also allows you to rely on us for support and consulting.

Contribute to Open Source

Part of the money we receive from sponsors is used to contribute to other projects that we depend upon. Plabayo sponsors the following organisations and individuals building and maintaining open source software that tokio-graceful depends upon:

name projects
💌 Tokio (Tokio, Async Runtime)
💌 Sean McArthur (Tokio)

FAQ

What is the difference with https://tokio.rs/tokio/topics/shutdown?

https://tokio.rs/tokio/topics/shutdown is an excellent tutorial by the Tokio developers. It is meant to teach and inspire you on how to be able to gracefully shutdown your Tokio-driven application and also to give you a rough idea on when to use it.

That said, nothing stops you from applying what you learn in that tutorial directly in your production application. It will work and very well so. However there is a lot of management of components you have to do yourself.

Ok, but what about the other crates on https://crates.io/ that provide graceful shutdown?

They work fine and they are just as easy to use as this crate. However we think that those crates offer more features then you need in a typical use case, are as a consequence more complex on the surface as well as the machinery inside.

How do I trigger the Shutdown from within a task?

You can achieve this by providing your own mechanism that you feed as the "signal" to Shutdown::new. E.g. you could easily achieve this by using https://docs.rs/tokio/latest/tokio/sync/struct.Notify.html so you can notify from any task where you wish and have your signal be https://docs.rs/tokio/latest/tokio/sync/struct.Notify.html#method.notified.

This is however not a usecase we have, as most web services (be it servers or proxies) typically wish to run all its connections independent without critical failures. In such environments there is no need for top-down cancellation mechanisms. Therefore we have nothing built in as this allows us to keep the API and source code simpler, and on top of that gives us the freedom to change some internal details in the future without having to continue to support this usecase.

Related to this is the usecase where you might want to early exit in case on of your critical background tasks exited (with or without an error), e.g. one of your server tasks. See the examples/hyper_panic.rs on how to do that. That same approach is what you would do for shutting down from within a task, except that you might use notify instead of oneshot, or something similar.

Could you make a video explaining why you made this crate, how to use it and how it works?

Most certainly. You can find the VOD at https://www.youtube.com/watch?v=GAkOJgrE3CQ which is part of our Rust 101 video playlist: https://www.youtube.com/watch?v=EngAlbv2y98&list=PLQgXEsLXFxpVyLddG8FFXfNQEiodTzAjj

Do you want to learn Rust or improve your Rust knowledge?

Check out https://rust-lang.guide/ and see if it can help you in that journey.

You might also like...
An asynchronous IO utilities crate powered by tokio.

An asynchronous IO utilities crate powered by tokio.

dark-std an Implementation of asynchronous containers build on tokio

dark-std dark-std is an Implementation of asynchronous containers build on tokio. It uses a read-write separation design borrowed from Golang SyncHash

Thin wrapper around [`tokio::process`] to make it streamable

This library provide ProcessExt to create your own custom process

Rc version `tokio-rs/bytes`

RcBytes The aim for this crate is to implement a Rc version bytes, which means that the structs in this crate does not implement the Sync and Send. Th

Alternative StreamMap fork of tokio-stream

streammap-ext This is a fork of StreamMap from tokio-stream crate. The only difference between the implementations is that this version of StreamMap n

A simple string interner / symbol table for Rust projects.

Symbol Interner A small Rust crate that provides a naïve string interner. Consult the documentation to learn about the types that are exposed. Install

A code coverage tool for Rust projects

Tarpaulin Tarpaulin is a code coverage reporting tool for the Cargo build system, named for a waterproof cloth used to cover cargo on a ship. Currentl

Common utilities code used across Fulcrum Genomics Rust projects

fgoxide Common utilities code used across Fulcrum Genomics Rust projects. Why? There are many helper functions that are used repeatedly across project

A bundler (mainly for TypeScript projects) made in Rust

TSAR A bundler (mainly for TypeScript projects) made in Rust. Also my first Rust Project! What does TSAR stand for Like phar (PHP Archive) or JAR (Jav

Comments
  • fix balloon emoji for crates.io and docs.rs

    fix balloon emoji for crates.io and docs.rs

    The :balloon: emoji directive works when the markdown is rendered on GitHub. However, the emoji 🎈 doesn't display on crates.io or docs.rs.

    This change replaces the directive with the emoji. We are lucky to live in 2023 where this will probably work in all (modern enough) editors.

    Some miscellaneous typos were also fixed.

    opened by hds 0
Releases(0.1.5)
  • 0.1.5(Sep 20, 2023)

    • Support and use Loom for testing;
    • Fixes a bug in the private trigger code where a race condition could cause a deadlock (found using loom);
    • Signal / Project support for the Windows platform;
      • affected code: crate::default_signal and crate::Shutdown::default;
        • Unix and Windows are supported and have this code enabled;
        • Other platforms won't have this code;
        • When using Loom this code is also not there;
      • This fixes build errors for platforms that we do not support for the default signal;
    Source code(tar.gz)
    Source code(zip)
  • 0.1.4(Sep 8, 2023)

  • 0.1.3(Sep 7, 2023)

  • 0.1.2(Sep 5, 2023)

  • 0.1.1(Sep 5, 2023)

  • 0.1.0(Sep 4, 2023)

Owner
Plabayo
Developer of Free and Open Source Software
Plabayo
Mix async code with CPU-heavy thread pools using Tokio + Rayon

tokio-rayon Mix async code with CPU-heavy thread pools using Tokio + Rayon Resources Documentation crates.io TL;DR Sometimes, you're doing async stuff

Andy Barron 74 Jan 2, 2023
PM-Tools - a simple Rust util to easily create server directories

PM-Tools PM-Tools is a simple Rust util to easily create server directories or plugins without the hassle of unzipping or creating directories Progres

null 2 Mar 19, 2022
Rust Util Collection, a simple and friendly error-chain

RUC Rust Util Collection, a simple and friendly error-chain, with many useful utils as an addition. The painful experience of using error-chain gave b

æ¼¢ 8 Dec 8, 2022
Rust Util Collection, a simple and friendly error-chain, with many useful utils as an addition.

RUC Rust Util Collection, a simple and friendly error-chain, with many useful utils as an addition. The painful experience of using error-chain gave b

æ¼¢ 6 Mar 27, 2022
A mostly drop-in replacement for mercantile written w/ rust, plus several other util(e)ities.

utiles utiles = utils + tiles A mostly drop-in replacement for mercantile written w/ rust, plus several other util(e)ities. Installation pip install u

jesse 5 Jun 20, 2023
Async Rust cron scheduler running on Tokio.

Grizzly Cron Scheduler A simple and easy to use scheduler, built on top of Tokio, that allows you to schedule async tasks using cron expressions (with

Ivan Brko 4 Feb 27, 2024
Develop an async runtime like thing in Rust for educational purpose.

How-not-to-async-rs Develop an async runtime like thing in Rust for educational purpose. What this is not This is not going to be a blog post explaini

Arun Muralidharan 28 May 11, 2024
Use winit like the async runtime you've always wanted

async-winit Use winit like the async runtime you've always wanted. winit is actually asynchronous, contrary to popular belief; it's just not async. It

John Nunley 17 Apr 23, 2023
Bindings to the Tauri API for projects using wasm-bindgen

tauri-sys Raw bindings to the Tauri API for projects using wasm-bindgen Installation This crate is not yet published to crates.io, so you need to use

Jonas Kruckenberg 25 Jan 9, 2023
Pure Rust library for Apache ZooKeeper built on tokio

zookeeper-async Async Zookeeper client written 100% in Rust, based on tokio. This library is intended to be equivalent with the official (low-level) Z

Kamil Rojewski 16 Dec 16, 2022