rwf2/Rocket · error

create tokio runtime

Error message

create tokio runtime

What it means

rocket::local::blocking::Client runs requests on its own private multi-thread tokio runtime (one worker thread) built inside Client::_new; the .expect("create tokio runtime") panics if Builder::build() returns an error. Build errors come from the environment: thread spawn failure (pid/thread limit reached, thread-stack memory exhausted) or I/O-driver fd creation failure (fd limit reached, seccomp blocking epoll). Because every Client constructs a fresh runtime, large parallel test suites multiply runtime creation and can exhaust limits even when each individual test passes.

Source

Thrown at core/lib/src/local/blocking/client.rs:39

/// let rocket = rocket::build();
/// let client = Client::tracked(rocket).expect("valid rocket");
/// let response = client.post("/")
///     .body("Hello, world!")
///     .dispatch();
/// ```
pub struct Client {
    pub(crate) inner: Option<asynchronous::Client>,
    runtime: RefCell<tokio::runtime::Runtime>,
}

impl Client {
    fn _new<P: Phase>(rocket: Rocket<P>, tracked: bool, secure: bool) -> Result<Client, Error> {
        let runtime = tokio::runtime::Builder::new_multi_thread()
            .thread_name("rocket-local-client-worker-thread")
            .worker_threads(1)
            .enable_all()
            .build()
            .expect("create tokio runtime");

        // Initialize the Rocket instance
        let inner = Some(runtime.block_on(asynchronous::Client::_new(rocket, tracked, secure))?);
        Ok(Self { inner, runtime: RefCell::new(runtime) })
    }

    // WARNING: This is unstable! Do not use this method outside of Rocket!
    #[doc(hidden)]
    pub fn _test<T, F>(f: F) -> T
        where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send
    {
        let client = Client::debug(crate::build()).unwrap();
        let request = client.get("/");
        let response = request.clone().dispatch();
        f(&client, request, response)
    }

    #[inline(always)]

View on GitHub (pinned to 3a54d079ae)

Solutions

  1. Cap test parallelism: run cargo test -- --test-threads=8 (or lower) so fewer runtimes exist at the same time.
  2. Raise the limits in the test environment: Docker --pids-limit, cgroup pids.max, ulimit -u and ulimit -n.
  3. Serialize runtime-heavy tests with a global Mutex guard so clients/runtimes are created one at a time, and construct one Client per test rather than per request.
  4. Inside async harnesses (#[tokio::test] or #[rocket::test]) use rocket::local::asynchronous::Client instead of the blocking client.
  5. Fix fd leaks (unclosed files/sockets) so descriptors remain available for epoll/eventfd.

Example fix

// before — each parallel test builds its own tokio runtime; under a
// pids-limited CI container the last tests panic: 'create tokio runtime'
#[test]
fn list_users() {
    let client = Client::tracked(rocket()).unwrap();
    assert_eq!(client.get("/users").dispatch().status(), Status::Ok);
}

// after — serialize client construction (and run cargo test with
// `--test-threads=8` plus a higher --pids-limit in CI)
use std::sync::Mutex;
static CLIENT_LOCK: Mutex<()> = Mutex::new(());

#[test]
fn list_users() {
    let _guard = CLIENT_LOCK.lock().unwrap();
    let client = Client::tracked(rocket()).unwrap();
    assert_eq!(client.get("/users").dispatch().status(), Status::Ok);
}
Defensive patterns

Strategy: validation

Validate before calling

fn can_build_runtime() -> bool {
    // probe exactly what Client::_new does: build a throwaway runtime
    tokio::runtime::Builder::new_multi_thread()
        .worker_threads(1)
        .enable_all()
        .build()
        .is_ok()
}

// in the test harness before constructing blocking clients:
if !can_build_runtime() {
    eprintln!("skipping: no thread/fd budget for another tokio runtime");
    return;
}

Try / catch

use std::panic::AssertUnwindSafe;

let client = std::panic::catch_unwind(AssertUnwindSafe(|| {
    Client::tracked(rocket())
}));
match client {
    Ok(c) => { /* proceed with requests */ }
    Err(_) => panic!(
        "environment cannot create a tokio runtime: check pids/fd limits"
    ),
}

Prevention

When it happens

Trigger: Calling Client::tracked(rocket), Client::untracked(rocket), or Client::debug(rocket) — all route through _new — when the test process is out of thread or fd budget; classically a large cargo test suite in a pids-limited container where each parallel test creates one more runtime with its own threads; EMFILE from leaked sockets/files leaving no descriptor for epoll/eventfd; seccomp/gVisor sandboxes in CI blocking the clone or epoll syscalls the runtime needs.

Common situations: Dockerized CI where cargo test defaults to high parallelism and hits --pids-limit; macOS/Linux dev machines or runners with low ulimit -u or ulimit -n during long test runs; leaked file descriptors in the code under test; tests mixing #[tokio::test] with the blocking client, which stresses the same resource budget; sandboxed CI executors.

Related errors


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