docs(db2-keys): databasev2 13 story — fresh-log keys-resident seed SEGV, fixed 6310078 + 1b6750d
- symptom: `seed` of docs/examples/residency on a FRESH log, rc 139, in both WO_DATA forms — found smoke-testing databasev2 7, independent of it - root cause, two defects composing: wo_wal_fold_row_at wrote `*msg` unguarded while wo_idx_probe borrows with msg = NULL; and the databasev2 12 schema head was staged lazily AFTER db.c captured the first keys-resident row's offset (`koff`), so that offset pointed at the schema record - fix (already on dev, prefix db2-keys): wo_wal_next_offset stages the pending head before returning an offset; the fold tolerates a NULL msg; both failing-first, and a control build with wal.c reverted reproduces the trace - third finding: residency-accept.sh never checked seed's rc, so 20/0 was green over a crash — `e274f4a`; the gate is 32/0 since - `residency.keys.fit` rc 74 confirmed a SEPARATE defect (compaction/replay of keys-resident offsets), still open under codd.md "Next bugs" - `status: done`, `readiness: ready`; counts test_wal 6629 → 6660, make -C runtime test 8462/0 at the time of the fix Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit b5b1da795917f0446a1e4a0317d9136c33acb262)
This commit is contained in:
parent
9b5498fafe
commit
54b160070f
1 changed files with 127 additions and 0 deletions
127
docs/stories/databasev2/13-fresh-log-keys-seed-segv.md
Normal file
127
docs/stories/databasev2/13-fresh-log-keys-seed-segv.md
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
---
|
||||
track: databasev2
|
||||
iteration: "13"
|
||||
status: done
|
||||
readiness: ready
|
||||
---
|
||||
|
||||
# databasev2 13 — fresh-log keys-resident seed SEGV
|
||||
|
||||
> Part of [Story — databasev2: the database beyond RAM](00-story.md). Found
|
||||
> 2026-09-10 by codd-zack while smoke-testing
|
||||
> [databasev2 7](07-single-file-db.md); independent of that iteration — the
|
||||
> ledger reproduces it against both `WO_DATA` forms.
|
||||
|
||||
## Symptom
|
||||
|
||||
`seed` of [`docs/examples/residency`](../../examples/residency/README.md) (a
|
||||
`resident: keys` table) segfaults, rc 139, on a FRESH log — in both the
|
||||
directory form and the file form of `WO_DATA`, so the crash is independent of
|
||||
databasev2 7. AddressSanitizer reports a SEGV on the zero page inside
|
||||
`wo_wal_fold_row_at` (`database/src/wal.c:1871`), called from
|
||||
`keys_fold_into` (`database/src/table.c:787`), `wo_row_borrow`
|
||||
(`database/src/table.c:849`), `wo_idx_probe` (`database/src/table.c:373`),
|
||||
`wo_builtin_db` (`database/src/db.c:215`).
|
||||
|
||||
## Root cause (confirmed by the fix)
|
||||
|
||||
Two defects compose. First, `wo_wal_fold_row_at` writes through `*msg`
|
||||
unguarded at `database/src/wal.c` lines ~1861, ~1871, ~1884 and ~1890, while
|
||||
`wo_idx_probe` (`database/src/table.c:373`) calls its borrow with `msg =
|
||||
NULL` — the zero-page write IS the SEGV. Second, the arm the fold enters is
|
||||
"record header is malformed", i.e. the row's stored offset points at a
|
||||
record that is not a row record at all: on a fresh log the databasev2 12
|
||||
schema head record is staged LAZILY, inside the first real append
|
||||
(`database/src/wal.c:479-490`), which runs AFTER `database/src/db.c` has
|
||||
already captured `koff = wo_wal_next_offset(w)` (`database/src/db.c:78/99/293`)
|
||||
— so the first keys-resident row's recorded offset points at the schema
|
||||
record, not at itself. Confirmed against the code by codd-zack, with one
|
||||
correction to the original hypothesis: `wo_wal_ensure_schema` was test-only —
|
||||
production never called it, so nothing outside `stage()` staged the head
|
||||
before this fix; the fix deliberately does not commit inside
|
||||
`wo_wal_next_offset` (only stages the head), keeping "one barrier per drain".
|
||||
|
||||
Consistent with: restart then `order` works (replay rebuilds every offset
|
||||
from the real log, so the schema-head race cannot recur there), and with the
|
||||
open `.claude/agents/codd.md` "Next bugs" entry for `residency.keys.fit` rc
|
||||
74 "replay rebuilds the row offsets". The two do **not** share this root
|
||||
cause — `keys.fit` reproduces unchanged on top of this fix (different code
|
||||
path: compaction/replay of keys-resident offsets, not fresh-log first-insert)
|
||||
— and it stays open under codd.md's "Next bugs" as its own defect.
|
||||
|
||||
A third finding, not an engine defect: `scripts/residency-accept.sh:155` ran
|
||||
the example's `seed` without checking its exit code, so the 2026-09-10 gate
|
||||
reported **20 checks, 0 failures** while `seed` was segfaulting under it.
|
||||
Fixed by codd-cyril, `e274f4a` (databasev2 7 task 4).
|
||||
|
||||
## Fix
|
||||
|
||||
Owner: codd-zack, prefix `fix(db2-keys)`. Landed on `dev`:
|
||||
|
||||
- `6310078 fix(db2-keys): stage the schema head before the first offset
|
||||
capture` — root cause: `wo_wal_next_offset` is no longer a pure inline; it
|
||||
stages the pending schema head first (`stage_schema_head`, the one helper
|
||||
`stage()` and `wo_wal_ensure_schema` also use) and only then returns
|
||||
`off + len`, so "the offset the next record lands at" is exact by
|
||||
construction for every caller. `database/src/db.c` is untouched — the head
|
||||
is still staged lazily (a log that never appends still stays schema-less).
|
||||
Test: `test_keys_resident_fresh_log_first_row` (`runtime/test/test_wal.c`).
|
||||
- `1b6750d fix(db2-keys): wo_wal_fold_row_at tolerates msg == NULL` — guard:
|
||||
the fold no longer writes through a NULL `msg` (a throwaway local sink is
|
||||
substituted), independent hardening of the same call path. Test:
|
||||
`test_fold_row_at_tolerates_null_msg` (`runtime/test/test_wal.c`).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Met:
|
||||
|
||||
- **Given** a fresh log (no prior records) and a `resident: keys` table's
|
||||
first insert, **when** that row is read back in the same run, **then** the
|
||||
row is returned correctly and the process does not crash — proven in both
|
||||
the directory form and the file form of `WO_DATA`. ✅ unit:
|
||||
`test_keys_resident_fresh_log_first_row` (`runtime/test/test_wal.c`),
|
||||
failing-first (ASan SEGV at `wal.c:1871`, `koff != 0` FAIL) then green.
|
||||
Gate: `runtime/build/wovm_asan` residency `seed` under both `WO_DATA` forms
|
||||
— dir rc 0 (`seeded: products=2 carts=1 SKU-1 stock=10`; restart `order`
|
||||
rc 0), file rc 0, same lines; no sanitizer output. A control build with
|
||||
only `wal.c`/`wal.h` reverted to pre-fix reproduces the identical SEGV
|
||||
trace, confirming the fix (not a coincidental pass).
|
||||
- **Given** `wo_idx_probe`'s NULL-`msg` borrow, **when** `wo_wal_fold_row_at`
|
||||
runs, **then** it tolerates a NULL `msg` rather than writing through it.
|
||||
✅ unit: `test_fold_row_at_tolerates_null_msg` (`runtime/test/test_wal.c`),
|
||||
failing-first (ASan SEGV at `wal.c:1886` from the test) then green.
|
||||
- **Given** the residency gate (`scripts/residency-accept.sh`), **when**
|
||||
`seed` runs, **then** the gate checks its exit code and fails loudly on a
|
||||
non-zero rc, instead of reporting the run green while `seed` crashed. ✅
|
||||
`e274f4a` (databasev2 7 task 4): `residency-accept.sh:158-162` "example:
|
||||
seed exits 0" — failing-first on the stale binary (`FAIL example seed --
|
||||
rc=139`), green (`ok example: seed exits 0`) once `runtime/wovm` is
|
||||
rebuilt with this fix in the tree.
|
||||
|
||||
Counts: `test_wal` 6629 → 6660 pass, 0 fail (+31: the two new tests plus their
|
||||
helper). `make -C runtime test`: 21 suites, **8462 pass / 0 fail** (was
|
||||
8431/0). `make -C runtime wovm-asan` builds clean (-Werror). FINAL
|
||||
`just residency`: **residency-accept: 32 checks, 0 failures** — up from
|
||||
20 checks (the pre-13 gate did not check `seed`'s rc) and from the 1-failure
|
||||
intermediate reading (`FAIL example seed -- rc=139`) once this fix landed in
|
||||
the tree.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- The `residency.keys.fit` compaction-integrity rc 74 bug
|
||||
(`.claude/agents/codd.md` "Next bugs") — **confirmed a separate defect**:
|
||||
it reproduces unchanged on top of this fix (`db-bench-quick`'s residency
|
||||
legs, still rc 74), a different code path (compaction/replay of
|
||||
keys-resident offsets, not fresh-log first-insert). Stays open under
|
||||
codd.md's "Next bugs", not claimed by this fix.
|
||||
- Any change to databasev2 7's file-form path resolution — this defect
|
||||
reproduces identically in the pre-existing directory form, so it predates
|
||||
and is independent of that iteration.
|
||||
|
||||
## History
|
||||
|
||||
- 2026-09-10: found by codd-zack while smoke-testing databasev2 7 (ledger
|
||||
`.dev/zack/databasev2-7.md`); filed as this story; fix in flight.
|
||||
- 2026-09-10: fixed and closed — `6310078` (root cause) + `1b6750d` (NULL
|
||||
guard); `test_wal` 6660/0, `make -C runtime test` 21 suites 8462/0,
|
||||
`just residency` 32/0.
|
||||
Loading…
Reference in a new issue