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
- 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.
- Pin the worker count below the limit in Rocket.toml with [default] workers = 4 instead of the num_cpus default.
- Check file-descriptor headroom (ulimit -n, systemd LimitNOFILE) — the tokio I/O driver needs spare fds for epoll/eventfd.
- If a sandbox (gVisor, seccomp profile) blocks clone/epoll_create/eventfd, allow those syscalls or run the service outside the filter.
- 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
- Set container --pids-limit and ulimit -u to at least ~2x Rocket's configured workers plus blocking-pool threads
- Pin `workers` in Rocket.toml instead of relying on the num_cpus default on many-core hosts
- Smoke-test the exact production image with the same limits before deploy
- Monitor thread count and open-fd count of the service in production
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.