Concurrent PGlite WebAssembly initialization intermittently SIGSEGVs Node.js
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 122k
- Forks
- 37.3k
- Avg merge
- 4d 2h
- Merged PRs (30d)
- 283
Description
Version
v26.5.0 (also reproduced on Node 22, 24, 25)
Platform
The primary Docker reproduction ran on this host:
Linux dragon-pro18 6.18.35-1-cachyos-lts #1 SMP PREEMPT_DYNAMIC Sat, 13 Jun 2026 12:39:40 +0000 x86_64 GNU/Linux
Intel Core Ultra 9 285HX, 24 cores online
Docker replaces the userspace and Node installation but shares the host kernel and CPU. The same failure occurs with the host Node binary.
Subsystem
V8 / WebAssembly (observed through child_process)
What steps will reproduce the bug?
The minimal repository is:
https://github.com/gadicc/node-pglite-wasm-sigsegv-repro
It has one runtime dependency, @electric-sql/pglite@0.5.4. PGlite ships its PostgreSQL engine as WebAssembly; there is no native addon.
On a machine with at least 20 GiB available:
git clone https://github.com/gadicc/node-pglite-wasm-sigsegv-repro.git
cd node-pglite-wasm-sigsegv-repro
docker build -t pglite-node-sigsegv-repro .
docker run --rm pglite-node-sigsegv-repro
Equivalently, with a local Node installation:
npm ci
npm run repro
Each of 16 child processes independently creates an in-memory PGlite client, runs SELECT 1, awaits client.close() in finally, and exits. The parent repeats that wave up to 50 times and stops on the first failure. There is no Vitest, Vite, test runner, application code, native addon, IPC dependency, or database persistence.
The memory warning matters: the observed cgroup peak was approximately 19 GiB. The recorded failing run had oom=0 and oom_kill=0.
How often does it reproduce? Is there a required condition?
It is intermittent and requires concurrent processes in the measured setup.
Two clean Node 26 official-image runs failed at waves 13 and 8. Other results
without V8 CLI overrides were:
| Node | Result |
|---|---|
| 25 | 1/20 waves failed |
| 24 | 4/45 waves failed |
| 22 | 1/10 waves failed |
| 20 | 0/40 waves failed |
The Node 20 result is a non-reproduction in an intermittent test, not evidence that it is unaffected. Official Node 20, 22, 24, and 26 images expose the same relevant defaults: Liftoff, lazy compilation, dynamic tiering, and WASM tier-up are enabled, with up to 128 compilation tasks. Node 20 did reproduce with --no-liftoff, which forces the optimizing compiler.
The Node 27 V8 canary failed at wave 17 with child_process.fork() and failed 2/50 waves with ordinary child_process.spawn(). A sequential one-child control passed 50/50 waves.
What is the expected behavior? Why is that the expected behavior?
Every child should complete the query, close the PGlite client, and exit with code 0.
What do you see instead?
An intermittent child is terminated by SIGSEGV:
node=v26.5.0 v8=14.6.202.34-node.24 platform=linux arch=x64 children=16 waves=50
wave=1 passed=16/16
...
wave=8 passed=15/16
child=10 code=null signal=SIGSEGV elapsedMs=4315
failedWaves=1 completedWaves=8 requestedWaves=50
Additional information
Native diagnostics from a sanitized-environment reproduction:
stracerecorded an initialSIGSEGVwithSEGV_MAPERRat a large guarded WASM address.- Node's
node::TrapWebAssemblyOrContinuecalledraise(SIGSEGV)after V8 declined to handle the address as a valid WASM trap. - The retained core records the resulting
SI_TKILLsignal. - GDB placed another crash in the lazy WASM compilation path through
LiftoffAssembler::PrepareCall,ExecuteLiftoffCompilation,CompileLazy, andRuntime_WasmCompileLazy, with background Turboshaft compilation active. A separate retained crash was inv8::internal::MarkingBarrier::MarkValueLocal.
Relevant flag controls on Node 25:
- default: reproduced;
--wasm-enforce-bounds-checks: reproduced;--wasm-num-compilation-tasks=1: reproduced;--no-wasm-tier-up: passed 100/100 waves in that version;--no-liftoff: reproduced.
The Node 27 canary nevertheless reproduced with both --no-wasm-tier-up and --liftoff-only, so no single WASM tier is universally required.
I also built the exact PGlite 0.5.4 tag using its official pnpm build:all:debug workflow. That PostgreSQL WASM used -g,
-gsource-map, and --no-wasm-opt; LLVM verified 1,322 DWARF compilation units without errors. With Node 25.2.1, a sequential control passed 5/5 waves and eight concurrent children passed 5/5 waves, while 16 concurrent children
reproduced SIGSEGV in wave 2. The cgroup peak was 67,306,729,472 bytes (62.7 GiB), with oom=0 and oom_kill=0.
Runtime controls using the same PGlite version and concurrency:
- Bun 1.3.11 / JavaScriptCore: 50/50 waves passed;
- Deno 2.8.0 / V8 14.9.207.2, default: 50/50 passed;
- Deno 2.8.0 / V8 14.9.207.2,
--no-liftoff: 50/50 passed.
The evidence therefore establishes a Node-runtime-specific interaction in the tested configurations, but does not yet distinguish Node's WASM trap/embedding layer or build configuration from a V8 defect exposed by Node. d8 is V8's standalone JavaScript shell; reproducing there without Node or its APIs would prove that the bug exists independently in V8. I would appreciate guidance on whether this should be routed or cross-filed to V8.
On 2026-07-14 I searched open Node and V8 reports for PGlite, TrapWebAssemblyOrContinue, WasmCompileLazy, LiftoffAssembler, and concurrent WASM SIGSEGVs, and found no exact duplicate. The reproduction README records the related but materially different reports.
Lastly,
- Co-authored with Codex gpt-5.6-sol, reasoning: high, with significant human input.
- This issue was opened by me personally by hand and without any automation.
Edit 1: Added PGLite debug build info in "Additional Information" above.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Clone and run the linked node-pglite-wasm-sigsegv-repro, starting with the documented Docker or npm reproduction and its sequential control. Compare the failing Node behavior with d8, then inspect the mentioned node::TrapWebAssemblyOrContinue and V8 lazy WASM compilation paths. Done means identifying whether the defect belongs to Node or V8 and providing a confirmed fix or routing recommendation with a reproducible regression case.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, javascript, nodejs, wasm
- Domain
- backend, compilers, operating-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100