Skip to content

worker_threads Worker exit aborts (SIGABRT) when a native addon (fsevents) has an in-flight napi_threadsafe_function — napi_release_threadsafe_function / uv_mutex_lock #65100

Description

@mastoj

Version

v24.15.0 through v24.19.0 (tested v24.15.0, v24.16.0, v24.19.0 — all crash).
Does NOT reproduce on v24.11.0 or v24.11.1.

Platform

macOS 15.6.1 (24G90), Apple Silicon (arm64)

Subsystem

worker_threads, N-API (napi_threadsafe_function)

What steps will reproduce the bug?

Minimal repro repo: https://github.com/mastoj/node24-fsevents-worker-threads-abort

  1. npm install
  2. node main.mjs 50

main.mjs spawns 50 worker_threads.Workers sequentially. Each worker
(worker.mjs) does:

import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const fsevents = require("fsevents");

fsevents.watch(process.argv[2], () => {});

setTimeout(() => process.exit(0), 5);

i.e. it starts a native fsevents watch (which registers a
napi_threadsafe_function under the hood) and exits the worker shortly
after, without explicitly stopping the watcher first.

How often does it reproduce? Is there a required condition?

100% reproducible on Node.js v24.15.0+ on macOS. Crashes on the very first
worker. 0% reproducible on Node.js v24.11.0/v24.11.1 (ran 50 workers cleanly).

What is the expected behavior?

All workers start, watch, and exit cleanly with code 0:

worker 0 exited with code 0
...
worker 49 exited with code 0
done, no crash across 50 workers

(This is what actually happens on v24.11.0.)

What do you see instead?

The whole process aborts with SIGABRT (exit code 134) on the very first
worker — before any of the console.log lines are even printed, since the
abort takes down the entire process (all worker threads share one OS
process).

$ node main.mjs 50
Abort trap: 6
$ echo $?
134

macOS crash reporter consistently shows the same stack trace on the
"WorkerThread":

__pthread_kill
pthread_kill
abort
uv_mutex_lock
napi_release_threadsafe_function
fse_instance_destroy
napi_env__::CallIntoModule<...>
node_napi_env__::CallFinalizer(void (*)(napi_env__*, void*, void*), void*, void*)
v8impl::Reference::Finalize()
node_napi_env__::DeleteMe()
node::Environment::CloseHandle<...>
uv_run
node::Environment::RunCleanup()
node::FreeEnvironment(node::Environment*)
node::worker::Worker::Run()
node::worker::Worker::StartThread(...)::$_0::__invoke(void*)
_pthread_start
thread_start

i.e. during worker-thread Environment teardown, napi_release_threadsafe_function
attempts uv_mutex_lock while finalizing fsevents' native threadsafe
function, and this ends in abort() instead of a clean/graceful teardown.

Additional context

This was originally found as a crash inside a real-world Next.js 16.3.0
monorepo build (next build --turbopack), where Next's build worker threads
transitively load fsevents (via chokidar/watchpack) and exit shortly
after doing their work. The exact same crash signature (same stack trace)
was observed across 15+ independent crash reports collected via macOS's
crash reporter during real builds, before being reduced to this minimal,
Next.js-free, fsevents-only reproduction.

It doesn't reproduce in a fresh create-next-app project (which doesn't
happen to have an active fsevents watcher inside its build workers) —
only projects where a native addon with an in-flight
napi_threadsafe_function is active in a worker thread at worker-exit
time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions