tokio-rs/tokio · error · io::Error

registering signal handler failed

Error message

registering signal handler failed

What it means

signal_enable, in its init closure, calls signal_hook_registry::register and on failure maps the error to 'registering signal handler failed' (ErrorKind::Other) when raw_os_error is unavailable, or to the raw OS error otherwise. It means the OS-level sigaction registration failed.

Source

Thrown at tokio/src/signal/unix.rs:296

    // Check that we have a signal driver running
    handle.check_inner()?;

    let globals = globals();
    let siginfo = match globals.storage().get(signal as EventId) {
        Some(slot) => slot,
        None => return Err(io::Error::new(io::ErrorKind::Other, "signal too large")),
    };

    siginfo
        .init
        .get_or_init(|| {
            unsafe { signal_hook_registry::register(signal, move || action(globals, signal)) }
                .map(|_| ())
                .map_err(|e| e.raw_os_error())
        })
        .map_err(|e| {
            e.map_or_else(
                || Error::new(ErrorKind::Other, "registering signal handler failed"),
                Error::from_raw_os_error,
            )
        })
}

/// An listener for receiving a particular type of OS signal.
///
/// The listener can be turned into a `Stream` using [`SignalStream`].
///
/// [`SignalStream`]: https://docs.rs/tokio-stream/latest/tokio_stream/wrappers/struct.SignalStream.html
///
/// In general signal handling on Unix is a pretty tricky topic, and this
/// structure is no exception! There are some important limitations to keep in
/// mind when using `Signal` streams:
///
/// * Signals handling in Unix already necessitates coalescing signals
///   together sometimes. This `Signal` stream is also no exception here in
///   that it will also coalesce signals. That is, even if the signal handler

View on GitHub (pinned to 625954f365)

Solutions

  1. Inspect the underlying raw_os_error if present (check e.raw_os_error()) for the OS errno.
  2. Ensure only one framework (tokio) manages a given signal — remove manual libc::signal/libc::sigaction calls.
  3. Register signals early at startup before any conflicting libraries do.
  4. Update signal-hook / signal-hook-registry to a compatible version.

Example fix

// before
let s = signal(kind)?; // 'registering signal handler failed'
// after
let s = match signal(kind) {
    Ok(s) => s,
    Err(e) => {
        eprintln!("errno={:?} kind={:?}", e.raw_os_error(), e.kind());
        return Err(e);
    }
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Register signals at startup before any other crate can claim them.
// Ensure no other framework calls libc::signal/sigaction for the same signum.

Try / catch

match signal(kind) {
    Ok(s) => s,
    Err(e) => {
        eprintln!("signal register failed: errno={:?} kind={:?}", e.raw_os_error(), e.kind());
        return Err(e);
    }
}

Prevention

When it happens

Trigger: Registering a signal handler fails at the OS level: invalid signal, permission denied, or signal_hook_registry internals reject it; rare race during handler setup.

Common situations: Conflicting signal handlers set by a C library or another crate; calling signal() outside tokio that interferes; resource/threading limits during handler thread spawn; buggy platform ABI.

Related errors


AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11). Data as JSON: /api/errors/06a38c15c11d076b. Report an issue: GitHub.