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

  1. 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
  2. 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
  3. Wrap the shutdown-signal setup in `tokio::spawn` from within the runtime instead of installing handlers from a foreign thread
  4. 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

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


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)