Skip to content

fix(fpss): evict in-flight subscribe correlation on unsubscribe so a superseded reject cannot drop a live sub#883

Merged
userFRM merged 1 commit into
mainfrom
fix/round9-fpss
Jun 17, 2026
Merged

fix(fpss): evict in-flight subscribe correlation on unsubscribe so a superseded reject cannot drop a live sub#883
userFRM merged 1 commit into
mainfrom
fix/round9-fpss

Conversation

@userFRM

@userFRM userFRM commented Jun 17, 2026

Copy link
Copy Markdown
Owner

What

A per-contract or full-stream subscribe records a pending correlation keyed by its req_id so a rejecting REQ_RESPONSE can untrack exactly that subscription. That correlation is only an authority to untrack while the tracked entry it created is still live.

An unsubscribe removed the tracked entry from active_subs / active_full_subs but left the correlation resident in the pending registry. So a subscribe X -> unsubscribe X -> re-subscribe X of the same identity, all before the first subscribe's REQ_RESPONSE landed, left the first request's correlation pointing at a slot now occupied by a new live entry. A late rejection of that obsolete req_id then untracked the re-subscribed entry by value and dropped it from the reconnect-replay set: the on-wire stream stayed live until the next reconnect, where the contract was no longer replayed.

Fix

Evict the in-flight correlation for an identity at the unsubscribe boundary, alongside removing the tracked entry, for both the per-contract and full-stream paths. This restores the invariant that at most one correlation per (kind, contract) or (kind, sec_type) identity is resident and it always names the current live entry. apply_req_response can then untrack purely by req_id lookup, and a rejection of a superseded request is a no-op rather than a value match against a newer entry.

This keeps the existing duplicate-subscribe guard (a duplicate shares the one live entry and registers no second correlation), the TTL / cap eviction, and the reconnect clear unchanged.

Tests

Two regression tests added: the per-contract and full-stream unsubscribe -> re-subscribe -> reject-superseded sequences each assert that exactly one correlation is resident after the re-subscribe and that the live entry stays tracked (and therefore replayed on reconnect) after the obsolete request is rejected.

cargo test -p thetadatadx --lib fpss: 270 passed, 0 failed.

🤖 Generated with Claude Code

…superseded reject cannot drop a live sub

A per-contract or full-stream subscribe records a pending correlation keyed by its req_id so a rejecting REQ_RESPONSE can untrack exactly that subscription. The correlation is only an authority to untrack while the tracked entry it created is still live. An unsubscribe removed the tracked entry but left the correlation resident, so a subscribe -> unsubscribe -> re-subscribe of the same identity, all before the first subscribe's REQ_RESPONSE landed, left the first request's correlation pointing at a slot now occupied by a new live entry. A late rejection of that obsolete req_id then untracked the re-subscribed entry by value and dropped it from the reconnect-replay set.

Evict the in-flight correlation for an identity at the unsubscribe boundary, alongside removing the tracked entry. This restores the invariant that at most one correlation per (kind, contract) or (kind, sec_type) identity is resident and it always names the current live entry, so apply_req_response can untrack purely by req_id lookup and a rejection of a superseded request is a no-op rather than a value match against a newer entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@userFRM userFRM merged commit 6645bf0 into main Jun 17, 2026
22 checks passed
@userFRM userFRM deleted the fix/round9-fpss branch June 17, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants