3.7 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||
|---|---|---|---|---|---|---|---|---|---|---|---|
| sqlite-fix-seam-panic-safety | Panic-safety armor for the sqlite auto-commit seams (review 004 item 1) | pending | moderate | medium | component | implementation |
|
Description
Defense-in-depth panic-safety for the SQLite auto-commit seams, per
docs/reviews/004-pre-release-general-review.md §"Findings recorded,
not fixed" item 1. with_writer/with_reader/blocking_read
(alkstore-sqlite/src/seam.rs, substrate/schema.rs) release their
slot/pool connection only after the closure returns normally. A panic
inside the closure skips the release: the single writer slot wedges
forever (Writer::acquire waits on a condvar with no timeout), and an
abandoned reader connection permanently shrinks the pool
(Readers.outstanding never decrements on abandonment). The tx seam
already owns this class (ConnLease); the auto-commit seams don't.
No reachable panic path today (the library is panic-free by the
standing gates, and these closures take no consumer code), so this is
armor, not exposure — but the fix needs abandonment plumbing on both
Writer (drop + path-based replenish) and Readers (an outstanding
decrement), which is a deliberate small design task, not an inline
patch.
Design first: decide the abandonment mechanism of record before
touching code, and record it in Notes. The established drop-guard
idioms are the precedents: ConnLease (the tx seam's connection
lease), the tick's poisoned-disposition replenish arm (review 004
inline fix 1), and cross-engine FanoutRelease (pg's forwarder guard).
The natural shape is a guard whose Drop marks abandonment (poison /
replenish / decrement) — Drop runs on unwind as well as return — but
the design must pin which dispositions each seam's abandonment takes
(writer: drop + replenish per the tick's established arm; reader:
outstanding decrement, connection dropped not released).
Testability: the closures take no consumer code, so the panic must
be injected — a cfg(test) panic seam inside a test closure (or a
test-only closure that panics), with the unwind caught, asserting the
slot/pool state after: the writer slot is grantable again and a full
store operation succeeds; the reader pool's outstanding count is
restored and a later reader acquires.
Acceptance Criteria
- The abandonment design of record is decided and written into Notes (mechanism per seam, which dispositions, which precedents it follows) before implementation lands
- A panic inside the
with_writerclosure leaves the writer slot grantable after the unwind — pinned by a test with an injected panic (slot re-acquired, full store operation succeeds after) - A panic inside the
with_reader/blocking_readclosures leaves the reader pool's outstanding count restored and a later reader acquires — pinned by test - Normal (non-panic) paths are behaviorally unchanged: the existing seam/slot/pool tests green unchanged
cargo test -p alkstore-sqlite, clippy-D warnings, fmt clean; workspace gates green server-less
References
- docs/reviews/004-pre-release-general-review.md §"Findings recorded, not fixed" item 1 (the finding, the fix shape, the "deliberate small design task" scoping)
- docs/architecture/engine-sqlite.md (connection architecture: writer slot, reader pool)
- alkstore-sqlite/src/seam.rs, substrate/schema.rs (the three seams)
- The tx seam's
ConnLeaseand the tick's poisoned-disposition replenish arm (review 004 inline fix 1) — the established idioms
Notes
To be filled by implementation agent
Summary
To be filled on completion