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

signal driver gone

Error message

signal driver gone

What it means

Handle::check_inner for the signal driver returns this io::Error when the inner driver Arc's strong_count is 0, i.e. the signal driver thread/loop has been dropped. Any signal API (signal::ctrl_c, unix::signal) needs the live driver to deliver notifications.

Source

Thrown at tokio/src/runtime/signal/mod.rs:137

                Ok(_) => continue, // Keep reading
                Err(e) if e.kind() == std_io::ErrorKind::WouldBlock => break,
                Err(e) => panic!("Bad read on self-pipe: {e}"),
            }
        }

        // Broadcast any signals which were received
        globals().broadcast();
    }
}

// ===== impl Handle =====

impl Handle {
    pub(crate) fn check_inner(&self) -> std_io::Result<()> {
        if self.inner.strong_count() > 0 {
            Ok(())
        } else {
            Err(std_io::Error::new(
                std_io::ErrorKind::Other,
                "signal driver gone",
            ))
        }
    }
}

View on GitHub (pinned to 625954f365)

Solutions

  1. Build the runtime with enable_all() (or enable_io + enable_signal) so the signal driver runs.
  2. Keep the runtime alive across the entire span of signal::ctrl_c().await.
  3. Call signal APIs only from within tokio runtime context.
  4. Treat 'signal driver gone' as terminal shutdown and exit the process gracefully.

Example fix

// before
let rt = runtime::Builder::new_current_thread().enable_time().build()?;
rt.block_on(async { signal::ctrl_c().await }); // fails: no signal driver
// after
let rt = runtime::Builder::new_current_thread().enable_all().build()?;
rt.block_on(async { signal::ctrl_c().await });
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the runtime was built with enable_all before relying on signals.
// For manual builds: Builder::new_current_thread().enable_all().build()

Try / catch

match signal::ctrl_c().await {
    Ok(()) => {},
    Err(e) if e.to_string().contains("signal driver gone") => {
        // graceful: treat as shutdown
        return;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling signal::ctrl_c() or Signal::new() when the runtime was built without a signal driver or the runtime/driver has been dropped; using a current-thread runtime that exited.

Common situations: Building a runtime with only enable_time() (not enable_all); dropping the runtime then awaiting ctrl_c; calling signal APIs from a non-Tokio thread after the runtime ended.

Related errors


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