Skip to content

Commit e6dc8a6

Browse files
kriszypclaude
andauthored
fix(jobs): unref parentPort after job completes to prevent Bun NAPI crash (#1227)
* fix(jobs): keep Bun event loop alive during job worker NAPI async work Bun does not count NAPI-held JS callbacks (e.g. the resolve/reject passed into RocksDB's native commit) as ref'd event-loop work. Job workers have no other ref'd work (no HTTP server, all sibling ports are unref'd), so Bun can drain and exit the event loop before the commit callback fires. The symptom: csv_data_load jobs that trigger getNewId() (upsert with no primary key column) hang indefinitely on Bun — the job stays IN_PROGRESS because the worker exits silently before updateJob() is reached. HTTP workers are unaffected because their HTTP server always holds a ref. Fix: hold a ref'd setInterval for the duration of the job (including updateJob) and clear it just before the unref'd exit setTimeout. Node is unaffected (the interval is only created when globalThis.Bun is present). Fixes #1222 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 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> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ca69f10 commit e6dc8a6

1 file changed

Lines changed: 6 additions & 0 deletions

File tree

server/jobs/jobProcess.ts

Lines changed: 6 additions & 0 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;
@@ -77,6 +78,11 @@ const JOB_ID = JOB_NAME.substring(4);
7778
jobObj.end_datetime = moment().valueOf();
7879
} finally {
7980
await jobs.updateJob(jobObj);
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();
8086
setTimeout(() => {
8187
realExit(exitCode);
8288
}, 3000).unref();

0 commit comments

Comments
 (0)