Skip to content

0.8.1906

Choose a tag to compare

@TimoKramer TimoKramer released this 16 Oct 10:26
51cf69d
gc-storage: refuse to sweep when the mark is incomplete (#1117)

A live server's periodic `d/gc-storage` deleted every key of a store except
`:branches` (dev.simm.is system DB, 2026-09-29; datahike 0.8.1744). The mark's
go block in `reachable-in-branch` died on a java.lang.AssertionError
(core.async's 1024-pending-takes limit, reached through an unrelated leak of
parked `<? S` takes in the same JVM). `go-try`/`go-try-` catch Exception, not
Error, so the walk's channel closed EMPTY; `async/merge` + `<<?` simply
yielded one result fewer, and the sweep ran against a whitelist of
#{:branches}.

The hole is still on main: any Error inside a walk (AssertionError,
StackOverflowError, OutOfMemoryError), a `:branches` read that comes back
nil, or a branch-head record that reads back nil, each turns into "nothing
to keep" and the sweep deletes the database.

Fail closed instead:
- `:branches` must read back non-empty (`:gc-branches-unreadable`);
- every seed must produce exactly one mark (`:gc-incomplete-mark`), at all
  three merge sites in `gc-storage!` and in `reachable-store-refs`;
- a BRANCH head record that cannot be read aborts the mark
  (`:gc-branch-head-unreadable`); absent ancestor/root records keep the
  existing "lineage ends here" tolerance.

Regression test injects each failure and asserts that no key is deleted
and the database still opens; all three fail (whole store swept) without
the fix.