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

signal too large

Error message

signal too large

What it means

signal_enable returns 'signal too large' when globals().storage().get(signal as EventId) returns None — the internal per-signal storage array has no slot for the requested number, meaning it exceeds the compiled maximum signal index tokio tracks.

Source

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

///
/// This will register the signal handler if it hasn't already been registered,
/// returning any error along the way if that fails.
fn signal_enable(signal: SignalKind, handle: &Handle) -> io::Result<()> {
    let signal = signal.0;
    if signal <= 0 || signal_hook_registry::FORBIDDEN.contains(&signal) {
        return Err(Error::new(
            ErrorKind::Other,
            format!("Refusing to register signal {signal}"),
        ));
    }

    // 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.

View on GitHub (pinned to 625954f365)

Solutions

  1. Use standard signals (<= 31 on Linux) which always fit.
  2. Check the signal number is below NSIG/signal_hook_registry's maximum before registering.
  3. Upgrade tokio — newer versions may expand the supported range.
  4. For real-time signals, confirm the specific number fits within tokio's storage.

Example fix

// before
let s = signal(SignalKind::from_raw(60))?; // may exceed storage
// after
let num = 60;
if num >= libc::NSIG { return Err(io::Error::new(io::ErrorKind::Other, "signal too large")); }
let s = signal(SignalKind::from_raw(num))?
Defensive patterns

Strategy: validation

Validate before calling

if num as usize >= libc::NSIG {
    return Err(io::Error::new(io::ErrorKind::Other, "signal too large"));
}
signal(SignalKind::from_raw(num))?

Type guard

fn within_signal_storage(sig: i32) -> bool {
    sig > 0 && (sig as usize) < libc::NSIG
}

Try / catch

match signal(SignalKind::from_raw(num)) {
    Ok(s) => s,
    Err(e) if e.to_string().contains("signal too large") => return Err(e),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling unix::signal with a signal number larger than tokio's compiled signal storage capacity (NSIG-derived upper bound).

Common situations: Using real-time signals (SIGRTMIN+n) on platforms where the number exceeds tokio's slot count; passing a very large platform-specific signal constant; miscomputing a signal offset.

Related errors


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