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.

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)

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.