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

Refusing to register signal {signal}

Error message

Refusing to register signal {signal}

What it means

signal_enable refuses to register a signal number that is <= 0 or is in signal_hook_registry::FORBIDDEN. These signals cannot be safely handled through the generic registry path (e.g. SIGKILL=9, SIGSTOP=19 on Linux cannot be caught). The error is returned as io::Error kind Other.

Source

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

/// Those two operations should both be async-signal safe.
fn action(globals: &'static Globals, signal: libc::c_int) {
    globals.record_event(signal as EventId);

    // Send a wakeup, ignore any errors (anything reasonably possible is
    // full pipe and then it will wake up anyway).
    let mut sender = &globals.sender;
    drop(sender.write(&[1]));
}

/// Enables this module to receive signal notifications for the `signal`
/// provided.
///
/// 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)) }

View on GitHub (pinned to 625954f365)

Solutions

  1. Use a valid catchable signal: SignalKind::interrupt, SignalKind::terminate, SignalKind::hangup, etc.
  2. Validate the raw number is > 0 and not SIGKILL/SIGSTOP before passing it.
  3. Read signal_hook_registry::FORBIDDEN at startup to confirm your target signal is allowed.
  4. Never attempt to handle SIGKILL/SIGSTOP — design shutdown around SIGTERM/SIGINT instead.

Example fix

// before
let s = signal(SignalKind::from_raw(9))?; // SIGKILL refused
// after
let s = signal(SignalKind::terminate())?; // SIGTERM
Defensive patterns

Strategy: validation

Validate before calling

const FORBIDDEN: &[i32] = &[9 /*SIGKILL*/, 19 /*SIGSTOP*/];
fn can_register(sig: i32) -> bool {
    sig > 0 && !FORBIDDEN.contains(&sig)
}
if !can_register(num) { return Err(io::Error::new(io::ErrorKind::Other, "refused")); }
signal(SignalKind::from_raw(num))?

Type guard

fn is_catchable_signal(sig: i32) -> bool {
    sig > 0 && sig != 9 && sig != 19
}

Try / catch

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

Prevention

When it happens

Trigger: Calling unix::signal(SignalKind::from_raw(n)) with n <= 0 or a forbidden value such as SIGKILL or SIGSTOP.

Common situations: Passing SignalKind::from_raw(0); trying to listen for SIGKILL/SIGSTOP; computing a signal number incorrectly and producing a non-positive value.

Related errors


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