napi-rs/napi-rs · error

async runtime generation task id space exhausted

Error message

async runtime generation task id space exhausted

What it means

The runtime allocates per-task ids from a monotonically increasing `next_task_id` within the current generation. When spawning a task would overflow the id counter, the runtime panics (after dropping the state guard) rather than reusing ids, since abort-handle bookkeeping depends on unique ids. Effectively unreachable defensive code.

Solutions

  1. Restart the process or recreate the runtime to start a new generation.
  2. In tests, reduce spawn counts or reset the generation counter.
  3. Report upstream if hit — a real hit indicates a generation-rotation bug.
Defensive patterns

Strategy: retry

Validate before calling

// Not guardable; requires ~2^64 spawns in one runtime generation.
// Recreate the runtime long before id exhaustion.

Try / catch

std::panic::catch_unwind(|| runtime.spawn(task)).unwrap_or_else(|_| recreate_runtime());

Prevention

When it happens

Trigger: Spawning a task (via the runtime's task-spawn path that inserts into `abort_handles`) when `next_task_id` has reached `u64::MAX` within one runtime generation — i.e. ~2^64 spawns without resetting the runtime generation.

Common situations: Never in normal apps; only fuzzing, synthetic tests saturating the counter, or extremely long-lived runtimes with astronomically many spawns.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of napi-rs/napi-rs@39bd1205e4 (2026-09-13). Data as JSON: /api/errors/ef58de96c36cddf2. Report an issue: GitHub.

Appendix: source

Thrown at crates/async-runtime/src/async_runtime.rs:2658

        abort_handles: FxHashMap::default(),
      }),
      idle: Condvar::new(),
    })
  }

  fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
    let (abort_handle, abort_registration) = AbortHandle::new_pair();
    let mut state = self
      .state
      .lock()
      .unwrap_or_else(std::sync::PoisonError::into_inner);
    if state.closed {
      return None;
    }
    let task_id = state.next_task_id;
    let Some(next_task_id) = state.next_task_id.checked_add(1) else {
      drop(state);
      panic!("async runtime generation task id space exhausted");
    };
    state.next_task_id = next_task_id;
    state.active += 1;
    state.abort_handles.insert(task_id, abort_handle);
    Some((
      abort_registration,
      GenerationWorkGuard {
        work: Arc::clone(self),
        task_id: Some(task_id),
      },
    ))
  }

  fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
    let mut state = self
      .state
      .lock()
      .unwrap_or_else(std::sync::PoisonError::into_inner);

View on GitHub (pinned to 39bd1205e4)