bye
Graceful shutdown and zero‑downtime USR1 upgrade helpers for tokio apps, with systemd socket activation support.
- Signals handled:
SIGTERM,SIGINT,SIGQUIT, start graceful shutdown;SIGUSR1fork+exec self and switch over once the child reports ready;SIGCHLDreap. - Graceful model: broadcast a
CancellationToken, stop accepting new work, and drain tracked tasks viaTaskTracker. - Upgrade flow: parent forks/execs current binary; child boots and calls
bye::ready()to signal readiness; parent drains and exits. - systemd: adopt pre‑opened sockets (socket activation) with
systemd_tcp_listener(port).
Platform: Unix (Linux/BSD).
Installation
[]
= { = "0.1", = false }
# Optional logging
= "0.1" # if you want logs
= { = "0.1", = ["tracing"] }
Minimum Supported Rust Version (MSRV): 1.74.
Quick start
use ;
async
Spawning cancellable tasks
let handle = bye.spawn;
// If you're not sure the app is still running, use try_spawn
let maybe = bye.try_spawn;
Using systemd socket activation
use ;
use ;
async
Concepts & API overview
Bye
Bye is the facade for graceful shutdown and task orchestration.
-
Bye::new()– construct without installing signal handlers. -
Bye::new_with_signals()– construct and spawn the async signal loop:SIGTERM,SIGINT,SIGQUIT→ calldrain()and exit the loop.SIGUSR1→ attempt zero‑downtime upgrade (see below). If the upgrade completes, the parent drains and exits; otherwise the parent keeps running.SIGCHLD→ reap terminated children withwaitpid(WNOHANG).
-
is_running()–trueuntil shutdown begins. -
on_shutdown()– aFuturethat resolves when shutdown starts (soft cancel signal). -
shutdown_token()– a clonableCancellationTokenthat is cancelled at shutdown. -
shutdown()– idempotent: starts shutdown, cancels the broadcast token, and stops accepting new tasks. -
wait()– future that resolves when all previously spawned tasks finish. -
drain()– convenience:shutdown()thenwait(). -
Task helpers:
spawn(|CancellationToken| -> Future)– track & auto‑cancel via token.try_spawn(..) -> Option<JoinHandle<_>>– no‑op if already shutting down.spawn_detached(Future)/try_spawn_detached(Future)– likespawnbut ignore the token.
Internally,
Byeusestokio_util::task::TaskTrackerfor lifecycle management and a rootCancellationTokenthat fans out per task.
Upgrade (USR1) flow
- Send
SIGUSR1to the running process. - Parent forks and
execves the current binary, passing an internal pipe via theUPGRADE_FDenv var. - Child performs initialization. When ready to take traffic, it calls
bye::ready()once. - Parent sees a byte written on the pipe, calls
drain()and exits. If the child exits prematurely without signaling ready, the parent continues running.
bye::ready()
- Writes one byte to the
UPGRADE_FDpipe once per PID (idempotent). - If environment variable
PIDFILEis set, writes the numeric PID to that file.
Call
ready()only after listeners, background tasks, and any caches are fully initialized.
systemd socket activation
-
systemd_tcp_listener(port: u16) -> TcpListenerwill:- If
LISTEN_FDScontains a socket for this process, adopt it (the first one if multiple). - Otherwise, bind to
0.0.0.0:port.
- If
-
systemd_ports() -> &'static [u16]returns all discovered ports fromLISTEN_FDSfor this process (computed once).
Signals & logging
- The signal loop is runtime‑friendly (async) and uses
tokio::signal::unix. - Logging is behind the
tracingfeature. When disabled, logs are elided.
Error handling
The crate defines bye::Error, covering:
- Environment parsing (
EnvUtf8,EnvParse), invalid/closed upgrade fd, and notify write errors. fork,execve,waitpid,pipe2,fcntlfailures (with the originalnix::errno::Errno).- Socket discovery errors for systemd activation.
- Generic
std::io::Error,std::ffi::NulError, andnix::Errnoconversions.
Use Result<T, bye::Error> in your code or convert to your own error type.
Environment variables
UPGRADE_FD– internal, set by the parent when forking; the child writes a single byte to signal readiness. You don't set this manually.PIDFILE– if set,ready()writes the current PID to this path.LISTEN_FDS,LISTEN_PID– standard systemd socket activation variables. The crate inspects these to adopt sockets.
Safety & caveats
- The crate uses
nixto performfork,execve,fcntl, and other low‑level operations. Unsafe code is limited and contained; functions that manipulate fds take/returnOwnedFd/BorrowedFdwhere possible. - When adopting a systemd socket with
from_raw_fd, ownership is transferred to the child process afterexecve; do not also use those fds elsewhere. - If an upgrade fails to signal readiness (child exits without calling
ready()), the parent continues to serve. This is by design to avoid downtime. You can monitor the parent process and restart it if needed. Unexpected errors during upgrade will start shutdown.
Feature flags
tracing– enable internal logs (info!,warn!,error!). Off by default.
Contributing
Issues and PRs welcome!
License
Dual‑licensed under MIT or Apache‑2.0.