tests: one extra DuckDB handle in the test worker can segfault the suite at teardown (exit 139) #54
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
From the aside in the approving review on #46, plus the flake noted in the approving review on #44.
A probe file that opened a plain
Db(no app, no host) in the same run segfaulted the suite at teardown — exit 139, every test green first — and the failure went away when the file was deleted. So the hazardwithStack's comment documents is broader than "two app stacks": one more DuckDB handle in that worker is enough. Any future test that opens a database directly is a coin flip.Likely related: the #44 review measured the full suite clean on 20 of 21 runs with one unreproducible failure — same rate across two commits, reads as a pre-existing suite flake, and CI has shown the same exit-139-in-teardown shape before.
Worth either serializing DB-opening tests into their own worker, documenting the constraint where a test author will see it, or finding the teardown ordering bug in the DuckDB binding.
Sources: #46 (comment) (aside), #44 (comment) (determinism note).
Root-caused from a core dump. It is not the handle count, and the fix is the Deno pin —
229af54bumpsdenoland/deno2.9.5 → 2.9.6 (also closing #58 and #59, which had been sitting there with the fix since 2026-08-31).Every 139 is the same frame:
DuckDB data chunks carry no
dispose/closeSync— in 1.5.4-r.1 and in the current 1.5.5-r.4 alike, they are freed only by their napi finalizer. Deno 2.9.5 runs the pending ones at env teardown through a hint that is already gone. It happens at process exit: the reduced case prints its last line and then segfaults.Same script, same machine, same package:
closeSync)The pin moved 2.5.6 → 2.9.5 on 2026-08-10 in
0eabe16. This issue was filed 2026-08-21; run #640 failed 08-21 and #713 failed 09-06. All after.The handle-count diagnosis does not hold. Measured on the full suite, pinned image, adding bare
Dbopen/close cycles that have no writer at all:No dose-response — 100 extra handles crashed less than 20. Roughly 5 crashes in 32 runs regardless of arm. The variable is whether GC left a chunk finalizer pending at exit, not how many handles exist; a probe file "fixing" it by being deleted moved allocation timing, which reads as causation across a handful of runs. Twelve full-suite runs on 2.9.6 are clean, though the reduced case above is the actual proof.
Worth keeping from the
withStackcomment: one stack per test is still the right shape for other reasons, but it is not load-bearing for this crash.Also, so it is not rediscovered:
f0057db(Db refusing statements after close) was pushed with this flake in mind and is not a fix for it — the finalizer never goes throughDb, and the suite still crashed with it in. It stands on its own —TaskServicequeues a sweep by microtask thatstop()cannot cancel, andtasks_test's rig drains it by hand for exactly that reason — but it is a different bug.