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

A Tokio 1.x context was found, but it is being shutdown.

Error message

A Tokio 1.x context was found, but it is being shutdown.

What it means

RegistrationSet::allocate returns this error when synced.is_shutdown is true. Allocation of a new ScheduledIo (the slot tracking readiness/interests for a freshly registered fd) is refused because the driver is already shut down — the resource cannot be tracked.

Source

Thrown at tokio/src/runtime/io/registration_set.rs:58

            registrations: LinkedList::new(),
            pending_release: Vec::with_capacity(NOTIFY_AFTER),
        };

        (set, synced)
    }

    pub(super) fn is_shutdown(&self, synced: &Synced) -> bool {
        synced.is_shutdown
    }

    /// Returns `true` if there are registrations that need to be released
    pub(super) fn needs_release(&self) -> bool {
        self.num_pending_release.load(Acquire) != 0
    }

    pub(super) fn allocate(&self, synced: &mut Synced) -> io::Result<Arc<ScheduledIo>> {
        if synced.is_shutdown {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                crate::util::error::RUNTIME_SHUTTING_DOWN_ERROR,
            ));
        }

        let ret = Arc::new(ScheduledIo::default());

        // Push a ref into the list of all resources.
        synced.registrations.push_front(ret.clone());

        Ok(ret)
    }

    // Returns `true` if the caller should unblock the I/O driver to purge
    // registrations pending release.
    pub(super) fn deregister(&self, synced: &mut Synced, registration: &Arc<ScheduledIo>) -> bool {
        synced.pending_release.push(registration.clone());

View on GitHub (pinned to 625954f365)

Solutions

  1. Construct all needed I/O resources before initiating runtime shutdown.
  2. Guard resource construction behind a check that the runtime/driver is still alive.
  3. Propagate the error rather than retrying — shutdown is terminal.
  4. Restructure so teardown waits for outstanding I/O resources to close.

Example fix

// before
runtime.enter();
let listener = TcpListener::bind(addr).await?; // may hit allocate() during shutdown
// after
if runtime.is_shutting_down() { return Err(shutdown()); }
let listener = TcpListener::bind(addr).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Construct all I/O resources up front, before initiating shutdown.
// If exposing a builder, refuse new resources once shutdown started:
if shutdown_started.load(Ordering::SeqCst) {
    return Err(io::Error::new(io::ErrorKind::Other, "shutting down"));
}
TcpListener::bind(addr).await

Try / catch

match TcpListener::bind(addr).await {
    Ok(l) => l,
    Err(e) if e.to_string().contains("shutting down") => return Err(e),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Registering a new source with the I/O driver (tokio::net::* construction, AsyncFd registration) after driver shutdown was initiated.

Common situations: Creating a new TcpStream/UnixListener during runtime teardown; registering AsyncFd from a task running after shutdown started; nested runtimes where the inner driver is dropped first.

Related errors


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