linera-io/linera-protocol · error

Failed to set up SIGTERM handler

Error message

Failed to set up SIGTERM handler

What it means

Panics when tokio::signal::unix::signal(SignalKind::terminate()) cannot install a SIGTERM handler in listen_for_shutdown_signals. The registration itself fails at startup, before any signal is delivered — typically because the tokio runtime lacks the signal/IO driver or the OS refused to install the handler. The panic drops the shutdown guard, so the CancellationToken fires and the node shuts down immediately.

Solutions

  1. Enable the full driver set on the runtime: .enable_all() (or .enable_io() plus the signal feature) before spawning the listener
  2. Confirm tokio's 'signal' feature is present: cargo tree -e features -i tokio
  3. Audit the container/sandbox security profile for sigaction restrictions on SIGTERM
  4. Raise file-descriptor limits if startup also logs EMFILE/ENOBUFS errors
  5. Treat a panicked listener task as fatal — the shutdown token was already cancelled, so perform a clean exit

Example fix

// before
fn main() {
    let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
    rt.block_on(async { /* ... spawn listen_for_shutdown_signals ... */ });
}

// after
fn main() {
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all() // without this, unix::signal(SignalKind::terminate()) fails
        .build()
        .unwrap();
    rt.block_on(async { /* ... */ });
}
Defensive patterns

Strategy: validation

Validate before calling

// Signal registration needs the runtime's IO/signal driver:
let rt = tokio::runtime::Builder::new_multi_thread()
    .enable_all()
    .build()?;
// Only now is listen_for_shutdown_signals safe to spawn for SIGTERM.

Try / catch

let handle = tokio::spawn(listen_for_shutdown_signals(token));
if let Err(e) = handle.await {
    assert!(!e.is_panic(), "signal listener panicked; token already cancelled — exit now");
}

Prevention

When it happens

Trigger: Running the node's signal listener on a runtime built without .enable_all(); a sandboxed or restricted environment (seccomp, gVisor, some minimal containers) where sigaction for SIGTERM is denied; resource exhaustion (EMFILE) preventing tokio's global signal registry from being created; tokio compiled without the 'signal' feature.

Common situations: Custom binaries or integration tests that hand-construct a tokio Runtime and call linera-base's run(); deploying validators into hardened container images; CI environments that strip tokio default features via a shared workspace Cargo.toml.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/2cca0f5714be4396. Report an issue: GitHub.

Appendix: source

Thrown at linera-base/src/lib.rs:182

}

/// Helper function for allocative.
pub fn visit_allocative_simple<T>(_: &T, visitor: &mut allocative::Visitor<'_>) {
    visitor.visit_simple_sized::<T>();
}

/// Listens for shutdown signals, and notifies the [`CancellationToken`] if one is
/// received.
#[cfg(not(target_arch = "wasm32"))]
pub async fn listen_for_shutdown_signals(shutdown_sender: CancellationToken) {
    let _shutdown_guard = shutdown_sender.drop_guard();

    #[cfg(unix)]
    {
        let mut sigint =
            unix::signal(unix::SignalKind::interrupt()).expect("Failed to set up SIGINT handler");
        let mut sigterm =
            unix::signal(unix::SignalKind::terminate()).expect("Failed to set up SIGTERM handler");
        let mut sighup =
            unix::signal(unix::SignalKind::hangup()).expect("Failed to set up SIGHUP handler");

        tokio::select! {
            _ = sigint.recv() => debug!("Received SIGINT"),
            _ = sigterm.recv() => debug!("Received SIGTERM"),
            _ = sighup.recv() => debug!("Received SIGHUP"),
        }
    }

    #[cfg(windows)]
    {
        tokio::signal::ctrl_c()
            .await
            .expect("Failed to set up Ctrl+C handler");
        debug!("Received Ctrl+C");
    }
}

View on GitHub (pinned to 6c226ddcb3)