| Crates.io | bye |
| lib.rs | bye |
| version | 0.1.6 |
| created_at | 2025-08-11 05:30:09.558134+00 |
| updated_at | 2025-08-18 04:07:10.524372+00 |
| description | A library for graceful shutdown with no downtime |
| homepage | |
| repository | https://github.com/dgagn/bye |
| max_upload_size | |
| id | 1789637 |
| size | 44,532 |
Graceful shutdown and zero‑downtime USR1 upgrade helpers for tokio apps, with systemd socket activation support.
SIGTERM, SIGINT, SIGQUIT, start graceful shutdown;
SIGUSR1 fork+exec self and switch over once the child reports ready;
SIGCHLD reap.CancellationToken, stop accepting new work,
and drain tracked tasks via TaskTracker.bye::ready() to signal readiness; parent drains and exits.systemd_tcp_listener(port).Platform: Unix (Linux/BSD).
[dependencies]
bye = { version = "0.1", default-features = false }
# Optional logging
tracing = "0.1" # if you want logs
bye = { version = "0.1", features = ["tracing"] }
Minimum Supported Rust Version (MSRV): 1.74.
use bye::{Bye};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Listen for TERM/INT/QUIT/USR1 and start reaping children
let bye = Bye::new_with_signals()?;
// Do your initialization…
// Only after you're ready to serve, tell the world:
bye::ready()?;
// Main loop
loop {
tokio::select! {
_ = bye.on_shutdown() => break, // soft shutdown started
// ... your app work here (accept connections, handle jobs, etc.)
}
}
// Wait for all spawned tasks to finish
bye.drain().await;
Ok(())
}
let handle = bye.spawn(|tok| async move {
// Do work until cancelled
tok.cancelled().await;
// Cleanup
});
// If you're not sure the app is still running, use try_spawn
let maybe = bye.try_spawn(|tok| async move { /* ... */ });
use bye::{Bye, systemd_tcp_listener};
use tokio::{io::{AsyncReadExt, AsyncWriteExt}, net::TcpListener};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let bye = Bye::new_with_signals()?;
// Will adopt a socket passed by systemd for :8080 if available,
// otherwise binds 0.0.0.0:8080 normally.
let listener: TcpListener = systemd_tcp_listener(8080).await?;
bye::ready()?; // tell the parent/system you're ready
loop {
tokio::select! {
_ = bye.on_shutdown() => break,
Ok((mut sock, _)) = listener.accept() => {
bye.spawn(|tok| async move {
let mut buf = [0u8; 1024];
let _ = tokio::select! {
_ = tok.cancelled() => Ok::<(), std::io::Error>(()),
r = sock.read(&mut buf) => r.map(|_| ())
};
let _ = sock.write_all(b"bye\n").await;
});
}
}
}
bye.wait().await;
Ok(())
}
SIGUSR1 to the running process.execves the current binary, passing an internal pipe
via the UPGRADE_FD env var.bye::ready() once.drain() and exits. If the
child exits prematurely without signaling ready, the parent continues
running.bye::ready()UPGRADE_FD pipe once per PID (idempotent).PIDFILE is set, writes the numeric PID to that file.Call
ready()only after listeners, background tasks, and any caches are fully initialized.
systemd_tcp_listener(port: u16) -> TcpListener will:
LISTEN_FDS contains a socket for this process, adopt it (the first one if multiple).0.0.0.0:port.systemd_ports() -> &'static [u16] returns all discovered ports from LISTEN_FDS for this process (computed once).
tokio::signal::unix.tracing feature. When disabled, logs are elided.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.nix to perform fork, execve, fcntl, and other low‑level operations. Unsafe code is limited and contained; functions that manipulate fds take/return OwnedFd/BorrowedFd where possible.from_raw_fd, ownership is transferred to the child process after execve; do not also use those fds elsewhere.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.tracing – enable internal logs (info!, warn!, error!). Off by default.Issues and PRs welcome!
Dual‑licensed under MIT or Apache‑2.0.