neon-bindings/neon · error

u32 overflow ocurred in Lifecycle InstanceId

Error message

u32 overflow ocurred in Lifecycle InstanceId

What it means

Neon assigns every module instance a monotonically increasing `InstanceId` backed by a process-global `AtomicU32`. `next()` uses `checked_add` and panics if the counter would exceed `u32::MAX`. Reaching this requires billions of module-instance creations (e.g. many worker threads or repeated dlopen cycles) within one process, so the panic effectively signals runaway instance churn or counter corruption.

Solutions

  1. Reuse worker threads / a fixed thread pool instead of creating a new Worker (and thus a new module instance) per task.
  2. Load the addon once per process (module-level singleton) and share it across workers where possible.
  3. Profile and fix any code path that triggers instance data initialization on every call; initialization should happen once per instance.
  4. As a last resort, file/patch Neon to use u64 for the counter if your workload legitimately creates billions of instances.

Example fix

// before
for job in jobs {
    let w = Worker::new(); // new module instance per job
    w.run(job);
}

// after
let pool = ThreadPool::new(8); // fixed workers, one module instance
for job in jobs { pool.run(job); }
Defensive patterns

Strategy: fallback

Validate before calling

// keep a process-level counter of worker/module loads in JS
let loads = (global.__addonLoads = global.__addonLoads || 0) + 1;
if (loads > 1e6) throw new Error('excessive addon instance churn');

Prevention

When it happens

Trigger: Creating more than 2^32-1 Neon module instances in one process — typically a hot loop that spawns worker threads each loading the addon, or repeatedly loading/unloading the native module; also possible if instance data is re-initialized per call due to a bug.

Common situations: Thread-pool benchmarks or job systems that spawn a fresh Worker per task instead of reusing workers; test suites that load the addon in thousands of processes... or one process with per-test dynamic loads; a library embedding Neon that re-inits per request.

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 neon-bindings/neon@38960e4381 (2026-09-13). Data as JSON: /api/errors/196781d1f2dd9054. Report an issue: GitHub.

Appendix: source

Thrown at crates/neon/src/lifecycle.rs:43

    types::promise::NodeApiDeferred,
};

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(transparent)]
/// Uniquely identifies an instance of the module
///
/// _Note_: Since `InstanceData` is created lazily, the order of `id` may not
/// reflect the order that instances were created.
pub(crate) struct InstanceId(u32);

impl InstanceId {
    fn next() -> Self {
        static NEXT_ID: AtomicU32 = AtomicU32::new(0);

        let next = NEXT_ID.fetch_add(1, Ordering::SeqCst).checked_add(1);
        match next {
            Some(id) => Self(id),
            None => panic!("u32 overflow ocurred in Lifecycle InstanceId"),
        }
    }
}

/// `InstanceData` holds Neon data associated with a particular instance of a
/// native module. If a module is loaded multiple times (e.g., worker threads), this
/// data will be unique per instance.
pub(crate) struct InstanceData {
    id: InstanceId,

    /// Used to free `Root` in the same JavaScript environment that created it
    ///
    /// _Design Note_: An `Arc` ensures the `ThreadsafeFunction` outlives the unloading
    /// of a module. Since it is unlikely that modules will be re-loaded frequently, this
    /// could be replaced with a leaked `&'static ThreadsafeFunction<NapiRef>`. However,
    /// given the cost of FFI, this optimization is omitted until the cost of an
    /// `Arc` is demonstrated as significant.
    drop_queue: Arc<ThreadsafeFunction<DropData>>,

View on GitHub (pinned to 38960e4381)