Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! [`predifi_backend::run_server`].

use predifi_backend::config::Config;
use predifi_backend::run_server;

/// Build the CORS middleware layer from the validated origin list in `config`.
///
Expand Down
46 changes: 46 additions & 0 deletions backend/src/shutdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,38 @@ use tracing::{info, warn};
/// On non-Unix platforms only `SIGINT` is registered.
#[cfg(unix)]
pub async fn wait_for_signal() {
// Build the three arms: a missing handler becomes a future that never
// resolves, leaving the surviving handlers in charge of triggering the
// `select!`. No early-return paths are taken because a single failed
// installation must never prevent the other signals from working.
let mut terminate_signal = match tokio::signal::unix::signal(
tokio::signal::unix::SignalKind::terminate(),
) {
Ok(signal) => Some(signal),
Err(error) => {
warn!(error = %error, "failed to install SIGTERM handler; skipping");
None
}
};

let mut hangup_signal = match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::hangup()) {
Ok(signal) => Some(signal),
Err(error) => {
warn!(error = %error, "failed to install SIGHUP handler; skipping");
None
}
};

let ctrl_c_block = async {
match tokio::signal::ctrl_c().await {
Ok(()) => info!("received Ctrl+C, beginning graceful shutdown"),
Err(error) => warn!(error = %error, "Ctrl+C handler failed; relying on SIGTERM/SIGHUP"),
}
};

let terminate_block = async {
match terminate_signal.as_mut() {
Some(signal) => {
async fn wait_sigterm() {
match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
Ok(mut signal) => {
Expand All @@ -61,6 +93,9 @@ pub async fn wait_for_signal() {
}
}

let hangup_block = async {
match hangup_signal.as_mut() {
Some(signal) => {
async fn wait_sighup() {
match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::hangup()) {
Ok(mut signal) => {
Expand Down Expand Up @@ -162,13 +197,24 @@ mod tests {
/// the future would take longer than the deadline.
#[tokio::test]
async fn shutdown_timeout_returns_after_deadline_when_future_is_slow() {
// The inner future sleeps for 2 s; the timeout deadline is 100 ms.
// The helper must return after ~100 ms, well before the 2-second sleep
// would complete.
let start = tokio::time::Instant::now();
with_shutdown_timeout(Duration::from_millis(100), "slow-unit", async {
tokio::time::sleep(Duration::from_secs(2)).await;
let start = tokio::time::Instant::now();
with_shutdown_timeout(Duration::from_millis(50), "slow-unit", async {
tokio::time::sleep(Duration::from_secs(10)).await;
})
.await;
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(100),
"helper should have waited at least the deadline (got {elapsed:?})"
);
assert!(
elapsed < Duration::from_secs(2),
elapsed >= Duration::from_millis(50),
"helper should have waited at least the deadline (got {elapsed:?})"
);
Expand Down
Loading