nodejs / nodejs/node

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

Open
#65,100 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

confirmed-bug node-api worker
Dominant language
JavaScript
Stars
122k
Forks
37.3k
Avg merge
4d 2h
Merged PRs (30d)
283

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the minimal reproduction's main.mjs and worker.mjs: run npm install and node main.mjs 50, then compare v24.11.1 with v24.15.0 or later. Trace the worker teardown path shown in the stack, especially napi_release_threadsafe_function and Environment::RunCleanup. Done means all 50 workers exit with code 0 without SIGABRT.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
backend, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.