async-shutdown
Runtime agnostic one-stop solution for graceful shutdown in asynchronous code.
This crate addresses two separate but related problems regarding graceful shutdown:
- You have to be able to stop running futures when a shutdown signal is given.
- You have to be able to wait for futures to finish potential clean-up.
- You want to know why the shutdown was triggered (for example to set your process exit code).
All of these problems are handled by the ShutdownManager
struct.
Stopping running futures
You can get a future to wait for the shutdown signal with ShutdownManager::wait_shutdown_triggered()
.
In this case you must write your async code to react to the shutdown signal appropriately.
Alternatively, you can wrap a future to be cancelled (by being dropped) when the shutdown is triggered with ShutdownManager::wrap_cancel()
.
This doesn't require the wrapped future to know anything about the shutdown signal,
but it also doesn't allow the future to run custom shutdown code.
To trigger the shutdown signal, simply call ShutdownManager::trigger_shutdown(reason)
.
The shutdown reason can be any type, as long as it implements Clone
.
If you want to pass a non-Clone
object or an object that is expensive to clone, you can wrap it in an Arc
.
Waiting for futures to complete.
You may also want to wait for some futures to complete before actually shutting down instead of just dropping them.
This might be important to cleanly shutdown and prevent data loss.
You can do that with ShutdownManager::wait_shutdown_complete()
.
That function returns a future that only completes when the shutdown is "completed".
You must also prevent the shutdown from completing too early by calling ShutdownManager::delay_shutdown_token()
or ShutdownManager::wrap_delay_shutdown()
.
The ShutdownManager::delay_shutdown_token()
function gives you a DelayShutdownToken
which prevents the shutdown from completing.
To allow the shutdown to finish, simply drop the token.
Alternatively, ShutdownManager::wrap_delay_shutdown()
wraps an existing future,
and will prevent the shutdown from completing until the future either completes or is dropped.
Note that you can only delay the shutdown completion if it has not completed already. If the shutdown is already complete those functions will return an error.
You can also use a token to wrap a future with DelayShutdownToken::wrap_future()
.
If you already have a token, this allows you to wrap a future without having to worry that the shutdown might already be completed.
Automatically triggering shutdowns
You can also trigger a shutdown automatically using a TriggerShutdownToken
.
Call ShutdownManager::trigger_shutdown_token()
to obtain the token.
When the token is dropped, a shutdown is triggered.
You can use ShutdownManager::wrap_trigger_shutdown()
or TriggerShutdownToken::wrap_future()
to wrap a future.
When the wrapped future completes (or when it is dropped) it will trigger a shutdown.
This can be used as a convenient way to trigger a shutdown when a vital task stops.
Futures versus Tasks
Be careful when using JoinHandles
as if they're a regular future.
Depending on your async runtime, when you drop a JoinHandle
this doesn't normally cause the task to stop.
It may simply detach the join handle from the task, meaning that your task is still running.
If you're not careful, this could still cause data loss on shutdown.
As a rule of thumb, you should usually wrap futures before you spawn them on a new task.
Example
This example is a tokio-based TCP echo server. It simply echos everything it receives from a peer back to that same peer, and it uses this crate for graceful shutdown.
This example is also available in the repository as under the name tcp-echo-server
if you want to run it locally.
use ShutdownManager;
use SocketAddr;
use ;
use ;
async
async
async
async