Repository navigation
Description
Summary
The Unit tests: lmdb step (test:unit:lmdb, in its first stage, HARPER_STORAGE_ENGINE=lmdb npm run test:unit:resources) intermittently dies with Segmentation fault (core dumped), exit 139, inside unitTests/resources/subscriptionReplay.test.js › Subscription replay › edge cases. It always lands in the same three-test window, on all three Node lines, across seven different PR branches (dependency bumps, a restart test, fulltext, auth), which points at something on main rather than any one branch. Six runs in the last two days hit it (seven jobs), plus one on 2026-10-14. The re-run of the most recent one passed.
| Date | Run / job | Node | PR (head) | Last test that passed | Last ✔ to SIGSEGV |
|---|---|---|---|---|---|
| 2026-10-14 | run 38645062397, job 116129758444 | v26 | #3201 (d20cf8a14) |
startTime: startTime = 0 with no records |
9.8 s |
| 2026-10-19 | run 39262381337, job 118093112483 | v26 | #3162 (098efac98) |
startTime: startTime = 0 with no records |
9.8 s |
| 2026-10-19 | run 39265280289, job 118103121781 | v24 | #2901 (7c253b96a) |
!omitCurrent: empty primaryStore delivers nothing initially |
11.1 s |
| 2026-10-19 | run 39280957232, job 118194344436 (attempt 3) | v26 | #3121 (70bc7fbb1) |
startTime: startTime = 0 with no records |
10.2 s |
| 2026-10-19 | run 39331415523, job 118330261285 | v24 | #3301 (a7359c48e) |
!omitCurrent: deleted keys are not delivered as current state |
11.5 s |
| 2026-10-20 | run 39367103563, job 118446326036 | v24 | #3313 (88874034b) |
!omitCurrent: empty primaryStore delivers nothing initially |
11.9 s |
| 2026-10-20 | run 39367103563, job 118446326002 | v22 | #3313 (88874034b) |
!omitCurrent: deleted keys are not delivered as current state |
8.8 s |
| 2026-10-20 | run 39462572144, job 118765545982 (attempt 1) | v22 | #3336 (89150ccea) |
!omitCurrent: empty primaryStore delivers nothing initially |
9.0 s; attempt 2 passed |
From the last one (timestamps trimmed):
17:19:47.647 ✔ startTime: startTime = 0 with no records (empty audit log) (144ms)
17:19:47.682 The one-to-many/many-to-many relationship property "badRelationship" in table "RelatedTable" must have an array type referencing a table as the elements
17:19:47.797 ✔ !omitCurrent: empty primaryStore delivers nothing initially (149ms)
17:19:47.834 The one-to-many/many-to-many relationship property "badRelationship" in table "RelatedTable" must have an array type referencing a table as the elements
17:19:56.752 Segmentation fault (core dumped)
##[error]Process completed with exit code 139.
What the timing says
The badRelationship warning is printed each time a test in this file calls table() in the test database, which also holds query.test.js's RelatedTable. In all eight sightings it appears 35–91 ms after the last ✔, so the next test had started and created its table. Then nothing for 9–12 s, then the crash. So the fault is inside one of three consecutive tests: !omitCurrent: empty primaryStore delivers nothing initially, !omitCurrent: deleted keys are not delivered as current state, or non-collection: subscribe to non-existent record delivers nothing initially.
Each of those creates a fresh audited table (the second also puts two records and deletes one), subscribes, collects for 100 ms (collect()'s deadline is 5 s), and calls subscription.return(), which is synchronous. Each normally passes in 145–200 ms, and .mocharc.json has timeout: 0. Nothing in the JS flow accounts for 9–12 s of silence, so the main thread was either blocked in native code or idle while another thread (the lmdb-js writer, a finalizer, a worker) faulted. The gap is a fairly steady ~10 s across Node lines. No native stack is captured: the step runs plain mocha without --report-on-fatalerror, and the runner keeps no core.
Every sighting is in the LMDB step. I didn't check whether the RocksDB run of the same file (Unit tests: resources (core)) ever crashes here.
Not reproduced locally
macOS arm64, Node v24.19.0, lmdb 3.5.7, 89150ccea: 5 runs of subscriptionReplay.test.js alone with HARPER_STORAGE_ENGINE=lmdb (43 passing each), and one full HARPER_STORAGE_ENGINE=lmdb npm run test:unit:resources (3324 passing, 738 pending). No crash.
How I found the sightings
The latest attempt of every failed Unit Test workflow run from 2026-10-07 to 2026-10-20 (253 runs; 118 jobs failed an lmdb step), checked for Segmentation fault. Earlier attempts weren't scanned, except the two named above. The only other lmdb-step segfaults in that window are two at #2669's copy-db point, both on v5.3.
Suggested next steps
- Capture a native stack:
--report-on-fatalerror, or a SIGSEGV handler, on theUnit tests: lmdbstep. Flaky: Unit Test (Node.js v24) SIGSEGV (exit 139) in schema-change / index-rebuild test group — v24-only #1380 and Flaky: 'Unit tests: lmdb' SIGSEGV (exit 139) right after copy-db integrity 'refuses a copy target that already exists' — Node 22 and 24 #2669 ask for the same, so one change covers all three. - Loop
subscriptionReplay.test.jsunder LMDB on a Linux runner, alone and after the resources files that precede it, to see whether it needs the earlier suites' state. - If the stack matches Flaky: 'Unit tests: lmdb' SIGSEGV (exit 139) right after copy-db integrity 'refuses a copy target that already exists' — Node 22 and 24 #2669's, fold one into the other.
Related: #2669 (lmdb-step SIGSEGV at copy-db), #1380 (Node 24 SIGSEGV in the schema-change / index-rebuild group).
sent with Claude Opus 5.5
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
kylebernhardy commentedon Oct 31, 2026
Observed another Node 26 occurrence in Adopt fulltext 0.8 with host query policy and fuzzy controls, at 8bf60a4: failed LMDB job.
non-collection: record with single version (no version chain) delivers just current.The location and timing resemble the existing reports. The warning adds evidence of an invalid cursor/transaction lifetime, but neither it nor matching timing establishes that every occurrence has the same native fault.
A separate fixture ownership defect is verified in the cleanup patch: 35 calls used
subscription.return?.(), but the subscription queue exposesend()/close();return()belongs to its iterator.collect()attaches data listeners, so it does not implicitly close an iterator. A real-table check asserting closed/unregistered state failed before and passed after correcting cleanup. The fixture now tracks subscriptions from all 43 creation sites and ends them after each test, including assertion-failure paths. The actual iterator-return case remains, with stronger lifecycle/no-late-delivery assertions.Cleanup verification: full LMDB fixture passed 43 cases each on Node 24/26; RocksDB passed 34 with 9 existing skips. Final focused iterator/idle checks also passed. These checks prove fixture cleanup, not the native SIGSEGV cause or resolution. This issue remains open.
Next diagnostic: capture an all-thread native backtrace at SIGSEGV while preserving the existing LMDB workload and assertions, for example by running the affected Mocha process under a native debugger. A Node fatal-error report alone is insufficient evidence that an operating-system SIGSEGV will be captured. Keep raw cores private; do not add speculative cursor fixes or treat a passing rerun as resolution. No CI job was rerun.
Comment generated by kAIle (GPT-6)
kriszyp commentedon Nov 3, 2026
Root cause found. The defect is in lmdb-js 3.5.9, the newest release; a fix is ready but not yet pushed.
Native stack. Print a native backtrace when a unit-test step segfaults in CI keeps cores in the
unit-testjob. Its ownlmdbleg then crashed inoutOfOrderWriteExpiry.test.jsand printed the stack (log):The faulting load is the
me_flagsread through a freedMDB_txn. When the freed memory happens to be zero, the same call printscursor_close: mc->mc_txn or mc->mc_txn->mt_env is NULLinstead, the warning seen on 10-31.The 9–14 s silence before each crash came after the fault, not before it. It is the time spent writing the core: about 1 GB, piped to apport on the runner. With cores written to disk, the gap is 4 s.
Violated invariant.
finishCursor(lmdb-jsread.js) parks a finished read cursor indb.availableCursor, bound to the current read txn. A later iterator renews it only when it first positions. Two paths free that txn while the cursor is still parked:resetReadTxnorphans it, andTxn.doneorreleaseToRenewingCursorsthen aborts it._commitReadTxn, which runs on everyopenDB(every Harper table creation), commits it.An iterator that takes the parked cursor and is closed before it positions then calls
mdb_cursor_closeon freed memory.releaseToRenewingCursorsdoes exactly that to asnapshot: falseiterator suspended across a tick.Deterministic reproduction with plain lmdb 3.5.9, under valgrind:
Fix (lmdb-js, local branch, not pushed): keep a parked cursor only while its txn is current. Close the parked cursors before the read txn stops being current (both
resetReadTxnorphan branches and_commitReadTxn) and beforeclose()aborts it.test/index.test.jsHARPER_STORAGE_ENGINE=lmdbfullunitTests/resourcesNext: an lmdb-js PR and release, then a Harper bump of
lmdb.Comment by KrAIs (Claude Opus 5.5)
Keep native crash cores in unit-test CI and print their backtraces on…
Close a parked cursor while its read transaction is still alive (#386)
Print a native backtrace when a unit-test step segfaults in CI (#3973)