rwf2/Rocket · critical

create tokio runtime

Error message

create tokio runtime

What it means

Rocket launches on a dedicated tokio multi-thread runtime built by the internal async_run helper; the .expect("create tokio runtime") turns any io::Error from Builder::build() into a panic that aborts launch. build() fails when the OS cannot supply what a runtime needs: worker/blocking threads (pthread_create), thread-stack memory, or the epoll/eventfd file descriptors for the I/O and time drivers enabled by .enable_all(). It signals a process or environment resource limit, not a mistake in your Rocket application code.

Source

Thrown at core/lib/src/lib.rs:237

/// Creates a [`Rocket`] instance with a custom config provider: aliases
/// [`Rocket::custom()`].
pub fn custom<T: figment::Provider>(provider: T) -> Rocket<Build> {
    Rocket::custom(provider)
}

/// WARNING: This is unstable! Do not use this method outside of Rocket!
#[doc(hidden)]
pub fn async_run<F, R>(fut: F, workers: usize, sync: usize, force_end: bool, name: &str) -> R
    where F: std::future::Future<Output = R>
{
    let runtime = tokio::runtime::Builder::new_multi_thread()
        .thread_name(name)
        .worker_threads(workers)
        .max_blocking_threads(sync)
        .enable_all()
        .build()
        .expect("create tokio runtime");

    let result = runtime.block_on(fut);
    if force_end {
        runtime.shutdown_timeout(std::time::Duration::from_millis(500));
    }

    result
}

/// WARNING: This is unstable! Do not use this method outside of Rocket!
#[doc(hidden)]
pub fn async_test<R>(fut: impl std::future::Future<Output = R>) -> R {
    async_run(fut, 1, 32, true, &format!("{WORKER_PREFIX}-test-thread"))
}

/// WARNING: This is unstable! Do not use this method outside of Rocket!
#[doc(hidden)]
pub fn async_main<R>(fut: impl std::future::Future<Output = R> + Send) -> R {

View on GitHub (pinned to 3a54d079ae)

Solutions

  1. Raise the thread budget where the process runs: increase Docker --pids-limit / cgroup pids.max or ulimit -u so Rocket's worker threads plus blocking threads fit with headroom.
  2. Pin the worker count below the limit in Rocket.toml with [default] workers = 4 instead of the num_cpus default.
  3. Check file-descriptor headroom (ulimit -n, systemd LimitNOFILE) — the tokio I/O driver needs spare fds for epoll/eventfd.
  4. If a sandbox (gVisor, seccomp profile) blocks clone/epoll_create/eventfd, allow those syscalls or run the service outside the filter.
  5. Relieve memory pressure and avoid launching multiple Rocket instances simultaneously, since each launch builds its own runtime via async_run.

Example fix

# before — 64-core host, container capped at 32 pids; Rocket defaults to
# workers = num_cpus (64 threads) and panics at launch: 'create tokio runtime'
docker run --pids-limit 32 my-rocket-app

# after — give the runtime headroom, and/or pin workers explicitly
docker run --pids-limit 512 my-rocket-app

# Rocket.toml
[default]
workers = 4
Defensive patterns

Strategy: validation

Validate before calling

fn assert_runtime_resources() -> std::io::Result<()> {
    // proxy for tokio's worker spawn: can we still create a thread?
    std::thread::Builder::new()
        .stack_size(2 * 1024 * 1024)
        .spawn(|| ())?
        .join()
        .map_err(|_| std::io::Error::other("thread join failed"))?;
    // proxy for the I/O driver: can we still open a file descriptor?
    std::fs::File::open("/dev/null")?;
    Ok(())
}

// call before rocket.launch(); fail with a clear diagnostic, not a panic
assert_runtime_resources()
    .expect("not enough threads/fds to build the tokio runtime");

Try / catch

// supervisor pattern: keep a launch failure from tearing down the process
// without diagnostics
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|info| eprintln!("launch panicked: {info}")));
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    rocket.launch()
}));
std::panic::set_hook(prev);
if outcome.is_err() {
    // surface environment diagnostics: /proc/self/limits, thread and fd counts
}

Prevention

When it happens

Trigger: Calling rocket.launch() or #[rocket::main] when the process has hit its thread budget (ulimit -u, cgroup pids.max, Docker --pids-limit); running with so few spare file descriptors that epoll/eventfd creation fails with EMFILE; running under a seccomp/gVisor sandbox that blocks clone/epoll_create/eventfd syscalls; heavy memory pressure failing thread-stack allocation; launching multiple Rocket instances concurrently so each async_run build competes for the remaining thread/fd quota. Rocket's default worker count is num_cpus, so many-core hosts inside small pids-limited containers are the classic trigger.

Common situations: Containerized deploys (Docker/Kubernetes) with low default pids limits on many-core hosts where workers = num_cpus exceeds the cap; CI runners with syscall filters; migrating from Rocket 0.4 to 0.5, where launch now builds a full tokio runtime on hosts that previously restricted threading; hosts under memory exhaustion where thread stacks cannot be allocated.

Related errors


AI-assisted analysis of rwf2/Rocket@3a54d079ae (2026-08-16). Data as JSON: /api/errors/97eb355760734c2a. Report an issue: GitHub.