rayon-rs/rayon · error

FIFO is empty

Error message

FIFO is empty

What it means

Rayon's work-stealing queue panicked because `steal()` repeatedly returned `Steal::Empty` while trying to execute a job as FIFO. This is an internal invariant: a job that reached this path must exist in a queue, so an empty steal indicates a lifecycle bug or misuse of unsafe APIs around job references.

Solutions

  1. Upgrade rayon/rayon-core to the latest patched version
  2. Reduce concurrent use of low-level rayon-core internals (job refs, registries)
  3. Reproduce with a minimal test and file an issue on the rayon repo
  4. As a workaround, switch to a stable thread-pool crate (e.g. std::thread scoped threads)

Example fix

// before: relying on internal job API
let job_ref = unsafe { ... };
job_ref.execute();
// after: use public rayon API
rayon::scope(|s| { s.spawn(|_| work()); });
Defensive patterns

Strategy: try-catch

Try / catch

// Rust: panics are not catchable at normal call sites; isolate risky work
catch_unwind(AssertUnwindSafe(|| rayon::join(work_a, work_b)))

Prevention

When it happens

Trigger: A job reference is executed via `JobRef::execute` (internal rayon-core path) while all worker queues report empty; typically reached only through internal scheduler bugs or incorrect custom unsafe integration with rayon's job API.

Common situations: Rare; seen in rayon-core internal scheduler races or when downstream code misuses unsafe job/registry APIs. End users hitting this are usually on a buggy rayon version.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at rayon-core/src/job.rs:272

    pub(super) unsafe fn push(&self, job_ref: JobRef) -> JobRef {
        // A little indirection ensures that spawns are always prioritized in FIFO order.  The
        // jobs in a thread's deque may be popped from the back (LIFO) or stolen from the front
        // (FIFO), but either way they will end up popping from the front of this queue.
        self.inner.push(job_ref);
        unsafe { JobRef::new(self) }
    }
}

impl Job for JobFifo {
    unsafe fn execute(this: *const ()) {
        unsafe {
            // We "execute" a queue by executing its first job, FIFO.
            let this = &*(this as *const Self);
            loop {
                match this.inner.steal() {
                    Steal::Success(job_ref) => break job_ref.execute(),
                    Steal::Empty => panic!("FIFO is empty"),
                    Steal::Retry => std::hint::spin_loop(),
                }
            }
        }
    }
}

View on GitHub (pinned to ee0a00bdb1)