tokio-rs/tokio · critical

there is no signal driver running, must be called from the c

Error message

there is no signal driver running, must be called from the context of Tokio runtime

What it means

Driver::signal() calls .expect(...) when self.signal is None — the runtime has no signal driver. Signal APIs (ctrl_c, unix::signal) require the signal driver, which is only enabled with enable_all or on platforms with cfg_signal_internal_and_unix.

Source

Thrown at tokio/src/runtime/driver.rs:103

        self.io.unpark();
    }

    cfg_io_driver! {
        #[track_caller]
        pub(crate) fn io(&self) -> &crate::runtime::io::Handle {
            self.io
                .as_ref()
                .expect("A Tokio 1.x context was found, but IO is disabled. Call `enable_io` on the runtime builder to enable IO.")
        }
    }

    cfg_signal_internal_and_unix! {
        #[track_caller]
        pub(crate) fn signal(&self) -> &crate::runtime::signal::Handle {
            self.signal
                .as_ref()
                .expect("there is no signal driver running, must be called from the context of Tokio runtime")
        }
    }

    cfg_time! {
        /// Returns a reference to the time driver handle.
        ///
        /// Panics if no time driver is present.
        #[track_caller]
        pub(crate) fn time(&self) -> &crate::runtime::time::Handle {
            self.time
                .as_ref()
                .expect("A Tokio 1.x context was found, but timers are disabled. Call `enable_time` on the runtime builder to enable timers.")
        }

        #[cfg(tokio_unstable)]
        pub(crate) fn with_time<F, R>(&self, f: F) -> R
        where
            F: FnOnce(Option<&crate::runtime::time::Handle>) -> R,

View on GitHub (pinned to 625954f365)

Solutions

  1. Use .enable_all() on the Builder so the signal driver is created.
  2. On non-Unix/non-supported targets, avoid signal APIs and handle shutdown differently.
  3. Ensure signal calls originate from within the runtime context (enter()/block_on).
  4. Verify cfg_signal_internal_and_unix is active for your target.

Example fix

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

Strategy: validation

Validate before calling

// Ensure the signal driver is present by enabling all drivers:
let rt = tokio::runtime::Builder::new_current_thread()
    .enable_all() // includes signal driver on Unix
    .build()?;

Try / catch

// Panic in macro path; manual build lets you handle:
match Builder::new_current_thread().enable_all().build() {
    Ok(rt) => rt.block_on(async { signal::ctrl_c().await }),
    Err(e) => { eprintln!("no signal driver: {e}"); std::process::exit(1); }
}

Prevention

When it happens

Trigger: Calling signal APIs on a runtime built without enable_io (the signal driver depends on the I/O driver) or on a platform/configuration where cfg_signal_internal_and_unix is off.

Common situations: Builder with only enable_time(); calling ctrl_c from a manually-built current-thread runtime that skipped enable_all; target without Unix signal support.

Related errors


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