electric-sql / electric-sql/pglite

[BUG]: Concurrent Node processes initializing PGlite can SIGSEGV the Node runtime

Open
#1,053 3 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
16k
Forks
442
Avg merge
20h 19m
Merged PRs (30d)
7

Description

**Describe the bug**

When multiple independent Node processes initialize PGlite concurrently, an intermittent process receives a native `SIGSEGV`. This is reproducible without Vitest, Vite, application code, persistence, or a native addon, and still occurs when every PGlite client is explicitly closed in `finally`.

The current evidence suggests a Node/V8 WebAssembly-runtime interaction rather than a JavaScript exception in PGlite. I am reporting it here as well because PGlite's PostgreSQL WASM workload is the reliable trigger and PGlite maintainers may recognize a compilation or generated-WASM pattern that can help reduce or route it.

Node issue: https://github.com/nodejs/node/issues/64500

Minimal repository: https://github.com/gadicc/node-pglite-wasm-sigsegv-repro

**To Reproduce** - include code sample(s)

Use a Linux x64 machine with at least 20 GiB available. Sixteen simultaneous clients reached an observed cgroup peak of approximately 19 GiB.

```sh
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
```

The Dockerfile uses the official `node:26-bookworm` image and the checked-in npm lockfile. Each wave launches 16 independent processes. Each child does only:

```js
import { PGlite } from "@electric-sql/pglite";

const client = new PGlite();

try {
await client.query("SELECT 1");
} finally {
await client.close();
}
```

Two clean official-image runs failed at waves 13 and 8. The sequential control using the same workload passed 50/50 waves.

**Logs**

```text
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
```

The failing cgroup recorded `oom=0` and `oom_kill=0`.

Native inspection found an initial `SEGV_MAPERR` at a guarded WASM address. Node's `node::TrapWebAssemblyOrContinue` then re-raised `SIGSEGV` after V8 did not classify the address as a valid WASM trap. Other retained crashes entered V8 lazy WASM compilation (`LiftoffAssembler::PrepareCall` / `Runtime_WasmCompileLazy`) and `v8::internal::MarkingBarrier::MarkValueLocal`. Raw cores are not public because process memory can contain environment values.

**Details**

- PGlite: `0.5.4`
- Extensions: none
- OS: official Debian Bookworm Node image on Linux x64
- Node: `v26.5.0`, V8 `14.6.202.34-node.24`
- Also reproduced on Node 22, 24, 25, and a Node 27 V8 canary
- Bun 1.3.11 / JavaScriptCore control: 50/50 waves passed
- Deno 2.8.0 / V8 14.9.207.2 control: 50/50 waves passed

Forced optimizer experiments also reproduced with PGlite 0.3.15 and 0.4.1, so the underlying native crash does not appear to have been introduced by 0.5.4. This reproducer pins 0.5.4 because it triggers the failure reliably enough to investigate.

**Additional context**

I built the exact PGlite 0.5.4 tag (source commit `25d0a55e1f1e4c59f26d9e125150dda88a33fd00`) using the documented
`pnpm build:all:debug` workflow. The PostgreSQL WASM build used `-g`, `-gsource-map`, and `--no-wasm-opt`. Its 50,321,851-byte `pglite.wasm` contained 1,322 DWARF compilation units and passed LLVM's DWARF verifier.

Using Node 25.2.1, a sequential control passed 5/5 waves and eight concurrent children passed 5/5 waves. With 16 concurrent children, wave 1 passed and child 9 received `SIGSEGV` in wave 2. After resetting the test scope's cgroup peak counter, that run peaked at 67,306,729,472 bytes (62.7 GiB), with `oom=0` and `oom_kill=0`.

The debug build therefore retains the concurrency-dependent native crash. The remaining practical difficulty is attaching a DWARF-aware debugger to the unpredictable one of 16 independent Node children that fails. Guidance on the preferred capture workflow for that case would be useful.

**Other**

* 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.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by running the Docker reproducer in node-pglite-wasm-sigsegv-repro and compare concurrent with sequential waves. Read the Node issue and the PGlite 0.5.4 build output, including pglite.wasm and its debug symbols. Done means either identifying a PGlite-side mitigation or producing a reproducible, debugger-supported upstream report that separates PGlite from Node/V8.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, postgresql, typescript, wasm
Domain
databases, operating-systems
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.