Skip to content

Commit 5402f85

Browse files
kriszypclaude
andcommitted
fix(jobs): unref parentPort after job completes to prevent Bun NAPI crash
The prior commit's premise was wrong. The actual root cause is a Bun 1.3.13 bug: calling process.exit() in a worker_threads.Worker that has lmdb-js loaded, while sibling workers are running, triggers a NAPI fatal error (panic: NAPI FATAL ERROR: Error::New napi_create_error) crashing the whole process. The crash chain for the CSV upsert integration test: 1. northnwd.suppliers has schemaDefined=false (create_table without attributes) 2. CSV upsert with a new column triggers addAttributes → signalSchemaChange → broadcastWithAcknowledgement, which calls port.ref() on ALL ports including parentPort 3. After ACKs received, parentPort is never unref'd (by design for regular workers, but wrong for job workers) 4. parentPort ref keeps the event loop alive until the 3s unref'd timer fires 5. realExit() → process.exit() in the lmdb-bearing job worker → Bun crash Fix: unref parentPort after updateJob so the event loop drains naturally, avoiding process.exit() entirely. Remove the setInterval keepalive which was based on the incorrect premise. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f9f36ca commit 5402f85

1 file changed

Lines changed: 6 additions & 7 deletions

File tree

server/jobs/jobProcess.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { cloneDeep } from 'lodash';
1515

1616
import { pathToFileURL } from 'node:url';
1717
import { join } from 'node:path';
18+
import { parentPort } from 'node:worker_threads';
1819
import { getEnvBuiltInComponents } from './../../components/Application.ts';
1920
import { PACKAGE_ROOT } from '../../utility/packageUtils.js';
2021
const JOB_NAME = process.env[(hdbTerms as any).PROCESS_NAME_ENV_PROP] as string;
@@ -26,12 +27,6 @@ const JOB_ID = JOB_NAME.substring(4);
2627
* @returns {Promise<void>}
2728
*/
2829
(async function job() {
29-
// Bun's event loop does not keep the loop alive for pending NAPI async callbacks
30-
// (e.g. RocksDB transaction commit passing resolve/reject into native code). Job
31-
// workers have no other ref'd work (HTTP server, ref'd ports), so Bun exits the
32-
// loop before the callback fires. A ref'd interval prevents that.
33-
const bunEventLoopKeepAlive =
34-
typeof (globalThis as any).Bun !== 'undefined' ? setInterval(() => {}, 1000) : undefined;
3530
// The request value could potentially be quite large so it's set to undefined to clear it out after being processed.
3631
let jobObj: any = { id: JOB_ID, request: undefined };
3732
let exitCode = 0;
@@ -83,7 +78,11 @@ const JOB_ID = JOB_NAME.substring(4);
8378
jobObj.end_datetime = moment().valueOf();
8479
} finally {
8580
await jobs.updateJob(jobObj);
86-
if (bunEventLoopKeepAlive) clearInterval(bunEventLoopKeepAlive);
81+
// On Bun 1.3.13, calling process.exit() in a worker thread with lmdb-js loaded
82+
// while sibling workers are running causes a NAPI fatal error crash. Unref
83+
// parentPort (which broadcastWithAcknowledgement may have ref'd during schema
84+
// changes) so the event loop drains naturally without calling process.exit().
85+
parentPort?.unref();
8786
setTimeout(() => {
8887
realExit(exitCode);
8988
}, 3000).unref();

0 commit comments

Comments
 (0)