linera-io/linera-protocol · error

Failed to set up SIGINT handler

Error message

Failed to set up SIGINT handler

What it means

Panics when tokio::signal::unix::signal(SignalKind::interrupt()) fails to install a SIGINT handler inside listen_for_shutdown_signals, the task Linera spawns to translate OS signals into CancellationToken cancellation. Tokio returns an error when the runtime was built without the signal/IO driver, when a global handler could not be registered (handler table full, resource limits, sandbox restrictions), or when the environment blocks sigaction. Because the panic fires while the drop_guard is alive, the shutdown token is cancelled and the process immediately begins shutting down.

Source

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

    }
    write!(f, "]")
}

/// 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. Build the runtime with the signal driver enabled: tokio::runtime::Builder::new_multi_thread().enable_all().build()
  2. Verify tokio is compiled with the 'signal' feature in your dependency graph (cargo tree -e features -i tokio)
  3. Remove any raw libc::signal / signal-hook handlers for SIGINT that conflict with tokio's global registry
  4. Check file-descriptor limits (ulimit -n) and sandbox policy if registration still fails
  5. If you spawn the task yourself, monitor its JoinHandle and treat a panic as a fatal startup error rather than respawning blindly

Example fix

// before
let rt = tokio::runtime::Builder::new_multi_thread()
    .worker_threads(4)
    .build()?;
rt.spawn(linera_base::listen_for_shutdown_signals(token));

// after — the signal driver must be enabled before signal() can register
let rt = tokio::runtime::Builder::new_multi_thread()
    .worker_threads(4)
    .enable_all() // registers the IO + signal drivers
    .build()?;
rt.spawn(linera_base::listen_for_shutdown_signals(token));
Defensive patterns

Strategy: validation

Validate before calling

// Before spawning the listener, guarantee the runtime has the signal driver:
let rt = tokio::runtime::Builder::new_multi_thread()
    .enable_all() // required for unix::signal() registration
    .build()?;
rt.spawn(linera_base::listen_for_shutdown_signals(token));

Try / catch

let handle = tokio::spawn(listen_for_shutdown_signals(token));
if let Err(join_err) = handle.await {
    if join_err.is_panic() {
        // drop_guard already cancelled the token: begin your own shutdown.
        eprintln!("signal listener failed: {join_err}");
        shutdown_now().await;
    }
}

Prevention

When it happens

Trigger: Calling run/handle_net_up_service on a hand-built tokio Runtime lacking .enable_all()/.enable_io(); running the node under a seccomp/gVisor sandbox that restricts signal-handler installation; hitting EMFILE at startup so the signal pipe cannot be created; compiling tokio without the 'signal' feature (default-features = false) so unix::signal registration fails.

Common situations: Embedding linera-base's run loop in a custom binary or test that constructs its own tokio Runtime; running validators in hardened containers; tokio upgrades where feature unification silently drops the signal feature; spawning the listener from inside another runtime's block_on.

Related errors


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