loco-rs/loco · error
failed to install signal handler
Error message
failed to install signal handler
What it means
This panic comes from installing the SIGTERM (terminate) listener inside loco's `shutdown_signal` future via tokio's `signal::unix::signal(SignalKind::terminate())`. If the OS refuses to register the signal handler — most commonly because tokio's IO/signal driver is not running (no reactor in the current worker/thread) or the process exhausted signal-mask resources — `.expect()` panics with this message, aborting server startup.
Solutions
- Run `serve`/`start` on the default multi-threaded tokio runtime (`#[tokio::main]` or `loco`'s own `loco_rs::boot::run` entrypoint) so the signal driver has a reactor
- If using a custom runtime, create it with the `rt-multi-thread` and `signal` features enabled and keep signal listening on a runtime worker thread
- Wrap the shutdown-signal setup in `tokio::spawn` from within the runtime instead of installing handlers from a foreign thread
- Avoid `#[tokio::test(flavor = "current_thread")]` paths that call `shutdown_signal` directly; use the full boot helper instead
Example fix
// before
std::thread::spawn(|| {
let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
rt.block_on(loco_rs::boot::start(app)); // panics: no signal reactor
});
// after
#[tokio::main]
async fn main() -> loco_rs::Result<()> {
loco_rs::boot::start(app).await // multi-thread runtime, signal driver available
} Defensive patterns
Strategy: try-catch
Validate before calling
// Rust: verify the runtime can host the signal driver before booting
let rt = tokio::runtime::Handle::try_current()
.expect("shutdown_signal must run inside a tokio runtime");
assert!(!tokio::runtime::Handle::current().runtime_flavor()
== tokio::runtime::RuntimeFlavor::CurrentThread || cfg!(debug_assertions)); Try / catch
// Catch panics during boot and report a clear message
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
// start server
}));
if result.is_err() { eprintln!("server boot failed: signal handler could not be installed; run on a multi-threaded tokio runtime"); } Prevention
- Always boot Loco via its own main/run entrypoints on `#[tokio::main]`
- Enable tokio's `signal` and `rt-multi-thread` features
- Never install signal handlers from threads outside the runtime
When it happens
Trigger: Calling `serve`/`start` (or anything awaiting `shutdown_signal`) outside a multi-threaded tokio runtime, or on a thread without an active tokio reactor (e.g. `#[tokio::test(flavor = "current_thread")]` with signal use from a blocking context, or `Runtime::new()` used only via `block_on` on a thread where the signal driver was shut down).
Common situations: Embedding a Loco server inside another application's custom runtime setup; running the app in a minimal container where a nonstandard runtime wrapper is used; writing custom main() that spawns `serve` on a current-thread runtime and shuttles work across threads.
Understand the failure class
Background: "unsupported platform" / "not supported on this platform" errors: what they mean and how to fix them — this error's family across 47 libraries.
Related errors
- failed to install Ctrl+C handler
- create cleanup runtime
- logger initialization failed
- fs service should build with success
- memory service must build with success
AI-assisted analysis of loco-rs/loco@23639d1e36 (2026-09-12).
Data as JSON: /api/errors/766e8623edcdf189.
Report an issue: GitHub.
Appendix: source
Thrown at src/boot.rs:588
}
/// Waits for a shutdown signal, either via Ctrl+C or termination signal.
///
/// # Panics
///
/// This function will panic if it fails to install the signal handlers for
/// Ctrl+C or the terminate signal on Unix-based systems.
pub async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
() = ctrl_c => {},
() = terminate => {},
}
}
pub struct MiddlewareInfo {
pub id: String,
pub enabled: bool,
pub detail: String,
}View on GitHub (pinned to 23639d1e36)