Skip to content

fix: bound the PGlite open so a stalled init can't wedge a turn - #6

Merged
TheArchitectit merged 1 commit into
TheArchitectit:masterfrom
davidj4tech:fix/pglite-open-timeout
Jul 29, 2026
Merged

fix: bound the PGlite open so a stalled init can't wedge a turn#6
TheArchitectit merged 1 commit into
TheArchitectit:masterfrom
davidj4tech:fix/pglite-open-timeout

Conversation

@davidj4tech

Copy link
Copy Markdown
Contributor

Follow-up to #5, at the opposite end of the same lifecycle. #5 guards the close (headless exit hang). This guards the open.

The failure

Both index modules cache their in-flight open in a module-level initPromise. That cache turns one stalled open into a permanent hang: PGlite is a single-writer WASM Postgres over a shared dataDir (~/.pi/mega-compact-vector), so a second pi process on the same dir can block indefinitely. The await never settles, the dead promise is cached, and every later caller awaits it. With no timers and no sockets left, node reports "Promise resolution is still pending but the event loop has already resolved" — and the pi turn awaiting it never ends.

Observed in the wild: a session sat on one turn for 75+ minutes, all threads parked in epoll_wait/futex_wait, zero open sockets, PGlite files held open. Not slow — deadlocked. #5's fix cannot help here: the session never reaches session_shutdown.

The change

withOpenTimeout() puts a ceiling on the open — 30s default, configurable via MEGACOMPACT_PGLITE_OPEN_TIMEOUT_MS, 0 restores the previous unbounded behavior. On timeout the caller gets undefined and both modules degrade to the synchronous scan they already fall back to. The cached initPromise is cleared rather than left dead, and the abandoned open is disowned — if it settles later the instance is closed, so an orphan can't hold the dataDir lock.

Rejections deliberately propagate unchanged, so the existing corrupt-dir detection (Aborted/RuntimeError → wipe + one retry) still runs. Only the timeout resolves to undefined.

Tests

src/store/pgOpenGuard.test.ts — 6 tests: never-settling open returns promptly, success passes through, rejection still reaches the corrupt-retry path, late-settling open is closed, 0 disables the guard, env parsing rejects junk.

Full sweep: 70 of 76 test files pass. The 6 that don't are pre-existing and unrelated — 5 hang on a harness() teardown leak (a zero-test run exits in 148ms), 1 fails because the @mongodb-js/zstd native addon isn't built. I confirmed src/memoryOps.test.js hangs identically with these changes stashed.

Both index modules cache their in-flight open in a module-level initPromise.
That cache turns a single stalled open into a permanent hang: PGlite is a
single-writer WASM Postgres over a shared dataDir (~/.pi/mega-compact-vector),
so a second pi process on the same dir can block indefinitely. The await never
settles, the dead promise is cached, and every later caller awaits it. With no
timers and no sockets left, node reports "Promise resolution is still pending
but the event loop has already resolved" and the pi turn awaiting it never ends.

Observed in the wild: a pi session sat on one turn for 75 minutes with all
threads parked in epoll/futex and zero open sockets — not slow, deadlocked.

withOpenTimeout() puts a ceiling on the open (30s default, configurable via
MEGACOMPACT_PGLITE_OPEN_TIMEOUT_MS; 0 restores the old unbounded behavior).
On timeout the caller gets undefined and both modules degrade to the
synchronous scan they already fall back to. The cached initPromise is cleared
rather than left dead, and the abandoned open is disowned — if it settles
later the instance is closed so an orphan can't hold the dataDir lock.

Rejections deliberately propagate unchanged, so the existing corrupt-dir
detection (Aborted/RuntimeError -> wipe + one retry) still runs. Only the
timeout resolves to undefined.
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@TheArchitectit
TheArchitectit merged commit 0aa0412 into TheArchitectit:master Jul 29, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants