Repository navigation
fix(db): prevent Windows native-driver hang from stalling all requests (#10627) - #10709
Merged
diegosouzapw merged 1 commit intoAug 20, 2026
Conversation
…iegosouzapw#10627) On Windows a mismatched-ABI better-sqlite3 addon can hang inside DllMain (loader lock) instead of throwing, so the sync driver cascade's try/catch never fires and the fallback to node:sqlite / sql.js never runs — the first DB touch in the proxy runtime stalls indefinitely at ~0% CPU (the diegosouzapw#10627 symptom: every request hangs, 0 bytes, no logs). Add a Windows-only child-process probe (bounded 5s timeout, cached per process) that gates the better-sqlite3 branch: a hang degrades to a timed-out probe -> "bad" verdict -> failover to node:sqlite instead of a deadlock. POSIX behavior is unchanged (no subprocess, probe returns true). Also warm the proxy runtime's settings cache at boot so the native driver load happens at startup, never on the first request path; a driver failure now surfaces as a logged startup error instead of a request hang. Tests: 7 new probe/cascade unit tests; validated on Windows Node 24 (healthy path prefers better-sqlite3 unchanged; broken-addon path falls through to node:sqlite in 1ms).
Contributor
Author
Confirmed: this is still live on the
|
| Check | Result |
|---|---|
Commits since v3.8.49 tag |
#10691, #10692/#10695, #10698, #10697, #10699, #10700 — none touch the driver factory |
createBetterSqliteProbe / probe gating in shipped driverFactory.ts |
0 matches |
| Any hang-guard machinery (child_process / spawnSync / AbortSignal.timeout) in the shipped load path | 0 matches — still the unguarded in-process _require("better-sqlite3") in a plain try/catch |
Shipped proxy.ts |
26 lines, no warm — first request still triggers the cold native load on the request path |
The vulnerable shape on the 3.8.50 tip is exactly what #10627 describes:
driverFactory.ts(~line 136):const BetterSqlite = _require("better-sqlite3")— in-process, synchronous, no timeout, no child-process boundary- The
catchfires only on throws; a WindowsDllMainloader-lock hang never throws → no fallthrough → first DB touch stalls forever
Two precision notes:
- Tagging: the release branch carries version
3.8.50but there is nov3.8.50tag yet — tags stop atv3.8.49. The reporter's v3.8.48/49 are the last tagged builds; the 3.8.50 line is the current untagged tip, and the bug code is byte-identical on it. - Not a regression: the bug is structural (the throw-only cascade assumption) and predates 3.8.48/49 — the unguarded load exists since at least the May
v3.8.5tag. It's Windows-only and machine-dependent (broken addon ABI), which is why Linux CI never caught it and it shipped.
This PR's two changes are the first to touch that path on this release line.
This was referenced Aug 20, 2026
Merged
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…iegosouzapw#10627) (diegosouzapw#10709) Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #10627 — a Windows-only total request hang where every request stalls indefinitely (0 bytes returned, no logs, ~0% CPU) while the port still accepts connections. Reported on v3.8.48/49; the reporter's
omniroute doctorflagged better-sqlite3 as broken on their global install.The fix makes a broken native driver fail fast and fail over instead of deadlocking the process, and moves the native driver load off the first-request path in the proxy runtime.
The problem
Symptom (from #10627)
/,/api/*,/v1/*— 0 bytes, no request logsomniroute doctorreports better-sqlite3 broken/mismatched on the global installRoot cause
OmniRoute's SQLite driver cascade (
src/lib/db/adapters/driverFactory.ts) is atry/catchchain:bun:sqlite → better-sqlite3 → node:sqlite → sql.js. Each branch assumes a broken native addon throws on load (e.g.ERR_DLOPEN_FAILED,Module did not self-register), so the fallback runs.That assumption holds on POSIX but not on Windows: a mismatched-ABI addon can hang inside
DllMain(the OS loader lock) instead of throwing. A hang never reaches thecatch, so:getCachedSettings()→ dynamicimport("@/lib/db/settings")→ native addon load — stalls foreverTwo compounding factors:
instrumentation-node.ts's startup warm-ups, so its first DB touch happened on the request path, cold, with no timeout.runtimeRequire("better-sqlite3")(webpack external) resolves the addon in-process, so a hang is in-process and unstoppable — no timeout can interrupt a synchronousrequireonce DllMain is entered.The fix (2 changes, 3 files)
1. Windows driver-hang probe —
src/lib/db/adapters/driverFactory.tsNew
createBetterSqliteProbe()gates the better-sqlite3 branch:DllMainhang → child killed by timeout (status === null) → verdict "bad"truewithout spawning — broken addons throw there (already handled by the cascade), and we don't pay a subprocess on Linux/CI bootcreateSyncDriverFactory(load, probe?)seam — tests can simulate both worlds without a real childResult: same machine, same broken binary — previously a deadlock, now an instant failover to
node:sqlite(built into Node 22.5+, no addon) → sql.js.2. Proxy boot warm —
src/proxy.tsWarm the settings cache at proxy-runtime boot (fire-and-forget, mirrors the
void warmModelCatalogCache()pattern):Files changed
src/lib/db/adapters/driverFactory.tscreateBetterSqliteProbe(), cascade gating, production wiringsrc/proxy.tstests/unit/db-adapters/driverFactory.test.tsTests
7 new unit tests:
status === null) rejects better-sqlite3 — the fix(startup): v3.8.49 (and v3.8.48) HTTP requests hang indefinitely on Windows — not the #6800/#8654 warmup window #10627 hang caseVerification performed
UV_HANDLE_CLOSINGteardown crash, reproduced identically on the unmodified baseEPERMon%TEMP%(files don't import the changed modules)tsconfig.typecheck-core)omniglyphnode_modules artifact)spawnSync)E2E matrix (real Windows machine):
Platform behavior
Affected users
node:sqlite(built into Node 22.5+)npm rebuild better-sqlite3)Related