3.9 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||
|---|---|---|---|---|---|---|---|---|---|---|---|
| pg-fix-forwarder-hardening | Forwarder TOCTOU restructure + hang-shaped arms (review 004 items 4+6) | pending | broad | high | component | implementation |
|
Description
Forwarder surgery that should ride its own session, per
docs/reviews/004-pre-release-general-review.md §"Findings recorded,
not fixed" items 4 and 6 (the review bundles them as the
forwarder-hardening session). Line refs are as of the review; the
inline fixes may have shifted them slightly — re-locate on pickup.
(a) Register/unregister TOCTOU (item 4). The registry refcount and
the LISTEN/UNLISTEN command emission are not one critical section
(forwarder.rs:131-176 vs 438-482): interleaved register/unregister
across tasks can leave a live refcount with the server-side LISTEN
cleared (both queued commands share a generation, so no stale check
saves it) until the next reconnect re-issue. Fix shape: a
decide-and-queue restructure inside the forwarder — the
register/unregister decision and the derived command enqueue must be
atomic with respect to each other (decide under the registry guard;
queue the command with the refcount state it was derived from).
(b) Generation-verify/LISTEN failure parks forever (item 6, arm 1).
A failed generation-verify/LISTEN batch is treated as connection death
and parks on poll_task.await forever even though the session may be
alive (forwarder.rs:698-702/803-808).
(c) Shutdown can leak the listener session (item 6, arm 2).
close()/Drop can leak the listener session past shutdown when the
serve loop is awaiting a command on a black-holed connection (the
run_command await sits outside the shutdown select, :781; no
timeout on this connection).
Treatment family for (b)+(c): timeouts/bounded waits on the listener path.
Preserve while restructuring (the gate will check these): the two
POC-pinned deadlock pitfalls stay structurally excluded; the reconnect
invariants from pg-fix-forwarder-reconnect (the retry core, the
fanout release guard, receivers-terminal-iff-loop-gone) stay green;
the register-failure unregister fix from review 004 (inline fix 3)
stays; the poison-recovery lock idiom from review 004 (inline fix 4)
stays.
Use the existing test-seam vocabulary (the reconnect-config slot, the connector injection, the generation tests) for the new pins.
Acceptance Criteria
- The TOCTOU is closed: register/unregister interleavings across tasks cannot leave a live refcount with the server-side LISTEN cleared — pinned by a test that interleaves the two and asserts the server-side LISTEN state matches the refcount
- A failed generation-verify/LISTEN batch no longer parks forever: bounded wait with an honest error or reconnect — pinned by test
close()/Dropreclaims the listener session even when the serve loop is stuck awaiting on a dead connection — pinned by test (black-holed connection seam)- The preserved postures listed above re-verified green (existing forwarder pins: reconnect survival, no-replay, shutdown close, stale-UNLISTEN generation tests, the register-failure unregister pin)
cargo test -p alkstore-postgres(harness server), clippy-D warnings, fmt clean; workspace gates green server-less
References
- docs/reviews/004-pre-release-general-review.md §"Findings recorded, not fixed" items 4 and 6 (the findings, the fix shapes, the own-session scoping)
- docs/architecture/decisions/004-postgres-driver.md (the forwarder's owned failure modes, the two POC-pinned deadlock pitfalls)
- tasks/pg-fix-forwarder-reconnect.md (the retry core, the fanout guard, the test-seam vocabulary)
- tasks/pg-fix-stale-unlisten.md (the generation/staleness machinery the TOCTOU restructure must compose with)
Notes
To be filled by implementation agent
Summary
To be filled on completion