rayon-rs/rayon · critical

The global thread pool has not been initialized.

Error message

The global thread pool has not been initialized.

What it means

Rayon operations that need the global thread pool (e.g. `current_num_threads`, `in_worker` contexts) panic when no global pool has been initialized and registry construction failed or was never triggered. The registry is created lazily via `call_once`; if it's absent/unavailable, this expect fires.

Solutions

  1. Check the Result of `ThreadPoolBuilder::new().num_threads(n).build_global()` at startup and log the error
  2. Pass a positive thread count (>= 1) to the builder
  3. Raise the container's process/thread limits (ulimit -u, cgroup pids.max)
  4. Fall back to sequential iterators (`for` loops) on platforms without thread support

Example fix

// before
rayon::spawn(task); // may panic if registry init failed earlier
// after
rayon::ThreadPoolBuilder::new().num_threads(4).build_global()
    .expect("failed to init rayon global pool");
rayon::spawn(task);
Defensive patterns

Strategy: validation

Validate before calling

fn init_rayon(threads: usize) -> Result<(), rayon::ThreadPoolBuildError> {
    rayon::ThreadPoolBuilder::new().num_threads(threads.max(1)).build_global()
}

Try / catch

match rayon::ThreadPoolBuilder::new().build_global() {
    Ok(()) => {},
    Err(e) => log::error!("rayon init failed: {e}"),
}

Prevention

When it happens

Trigger: Calling parallel code from a context where the global registry can't be built — notably inside a process where thread spawning fails, or when a previous `ThreadPoolBuilder::build_global()` error left THE_REGISTRY as None (e.g. unusable thread count = 0).

Common situations: Configuring `num_threads(0)` or hitting OS thread-creation limits in containers with low pid limits; running rayon in restricted environments (some WASM/embedded targets) where threads can't spawn.

Related errors


AI-assisted analysis of rayon-rs/rayon@ee0a00bdb1 (2026-09-07). Data as JSON: /api/errors/afcdbfa300676130. Report an issue: GitHub.

Appendix: source

Thrown at rayon-core/src/registry.rs:169

// ////////////////////////////////////////////////////////////////////////
// Initialization

static mut THE_REGISTRY: Option<Arc<Registry>> = None;
static THE_REGISTRY_SET: Once = Once::new();

/// Starts the worker threads (if that has not already happened). If
/// initialization has not already occurred, use the default
/// configuration.
pub(super) fn global_registry() -> &'static Arc<Registry> {
    set_global_registry(default_global_registry)
        .or_else(|err| {
            // SAFETY: we only create a shared reference to `THE_REGISTRY` after the `call_once`
            // that initializes it, and there will be no more mutable accesses at all.
            debug_assert!(THE_REGISTRY_SET.is_completed());
            let the_registry = unsafe { &*ptr::addr_of!(THE_REGISTRY) };
            the_registry.as_ref().ok_or(err)
        })
        .expect("The global thread pool has not been initialized.")
}

/// Starts the worker threads (if that has not already happened) with
/// the given builder.
pub(super) fn init_global_registry<S>(
    builder: ThreadPoolBuilder<S>,
) -> Result<&'static Arc<Registry>, ThreadPoolBuildError>
where
    S: ThreadSpawn,
{
    set_global_registry(|| Registry::new(builder))
}

/// Starts the worker threads (if that has not already happened)
/// by creating a registry with the given callback.
fn set_global_registry<F>(registry: F) -> Result<&'static Arc<Registry>, ThreadPoolBuildError>
where
    F: FnOnce() -> Result<Arc<Registry>, ThreadPoolBuildError>,

View on GitHub (pinned to ee0a00bdb1)