π€ This issue was generated by Claude on behalf of @keddie.
Found by the exactly-once/deferred-leak audit during the ProposalForwarder extraction (PR #1237). Pre-existing (unchanged by that PR; carried verbatim from before) β same parked-forever shape as the Β§6.4 read-machine hang (#1235).
Severity: π liveness (caller hangs; the requestId dedup makes caller-driven retry exactly-once-safe, so this may be intended recovery β but nothing spontaneous completes the deferred, and the map entry leaks for the engine's lifetime).
Bug. Once a Forward has been sent (entry in forwardedProposals, id no longer in waitingForLeader), its deferred is completed only by (a) a matching ForwardResponse, or (b) actor teardown failAll. If the leader crashes (vs. stepping down, where onForward's watcher replies NotLeader), no response ever arrives, and leader-change/re-election touches only waitingForLeader (via flush) β never already-sent entries. The follower's readIndex()-style d.await() in proposeWithRequestId suspends indefinitely; the only escape is caller-side cancellation/timeout.
Fix options. On an observed leader change (or after a bounded no-response interval), either fail outstanding sent forwards with LeadershipLostException, or move their ids back into waitingForLeader for re-send to the new leader (safe under the dedupKey β the leader dedups a re-sent proposal). The former is simpler and matches the transfer/read machines' failAll-on-relinquish behavior.
Consider grouping with #1235 (both are "deferred parked forever on a leadership event" bugs) if a spec-fix sweep addresses them together.
Found by the exactly-once/deferred-leak audit during the
ProposalForwarderextraction (PR #1237). Pre-existing (unchanged by that PR; carried verbatim from before) β same parked-forever shape as the Β§6.4 read-machine hang (#1235).Severity: π liveness (caller hangs; the
requestIddedup makes caller-driven retry exactly-once-safe, so this may be intended recovery β but nothing spontaneous completes the deferred, and the map entry leaks for the engine's lifetime).Bug. Once a
Forwardhas been sent (entry inforwardedProposals, id no longer inwaitingForLeader), its deferred is completed only by (a) a matchingForwardResponse, or (b) actor teardownfailAll. If the leader crashes (vs. stepping down, whereonForward's watcher repliesNotLeader), no response ever arrives, and leader-change/re-election touches onlywaitingForLeader(viaflush) β never already-sent entries. The follower'sreadIndex()-styled.await()inproposeWithRequestIdsuspends indefinitely; the only escape is caller-side cancellation/timeout.Fix options. On an observed leader change (or after a bounded no-response interval), either fail outstanding sent forwards with
LeadershipLostException, or move their ids back intowaitingForLeaderfor re-send to the new leader (safe under thededupKeyβ the leader dedups a re-sent proposal). The former is simpler and matches the transfer/read machines'failAll-on-relinquish behavior.Consider grouping with #1235 (both are "deferred parked forever on a leadership event" bugs) if a spec-fix sweep addresses them together.