Files
alkstore/tasks/sqlite-fix-seam-panic-safety.md
T

84 lines
3.7 KiB
Markdown

---
id: sqlite-fix-seam-panic-safety
name: Panic-safety armor for the sqlite auto-commit seams (review 004 item 1)
status: pending
depends_on: []
scope: moderate
risk: medium
impact: component
level: implementation
tags: [review-004-fixes, sqlite-engine, seam]
---
## 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_writer` closure 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_read` closures 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 `ConnLease` and 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