fix(mls/api): XIP-83 Subscribe — flush send-error, history_only overlap, reusable send timer#563
Open
tylerhawkes wants to merge 1 commit into
Open
fix(mls/api): XIP-83 Subscribe — flush send-error, history_only overlap, reusable send timer#563tylerhawkes wants to merge 1 commit into
tylerhawkes wants to merge 1 commit into
Conversation
ApprovabilityVerdict: Needs human review This PR introduces a new validation rule that rejects requests targeting topics with in-flight history_only catch-ups - changing API behavior by failing requests that previously succeeded. Combined with the author not owning these files (owned by @xmtp/backend), human review is warranted. You can customize Macroscope's approvability policy. Learn more. |
ced549f to
b6d8022
Compare
…ap guard, reusable send timer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
b6d8022 to
4c511c7
Compare
neekolas
approved these changes
Jun 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
XIP-83
Subscribe— three correctness/resource fixesFollow-up to #562. While building the decentralized (d14n) binding of this same handler on xmtpd (xmtp/xmtpd#2020) and putting it through an independent multi-agent review, three issues surfaced that also exist in this v3 handler. (The d14n review caught them precisely because it was a fresh look at a sibling of code #562's own review had already passed — e.g. #562 fixed the flush timeout false-OK but not the send-error one below.)
1.
flush()reported a false OK after a sender send-errorflush()returnednilonsenderDonewithout checkingsendErrCh. If the sender goroutine had already stopped on astream.Senderror, the graceful-completion path returned a clean OK while the wave's terminal frames (history tail +CatchupComplete) were never written — silent truncation with no reconnect signal. Now drainssendErrChaftersenderDoneand propagates the error (mirrors the existing timeout case, which already fails rather than false-OKs).2. A second add overlapping an in-flight
history_onlywaveA
history_onlyadd never registers insubscribed/catchingUp, and there was no in-flight guard, so a second add for the same topic while itshistory_onlycatch-up was still running started a competing wave and reset the high-water floor — re-delivering history the first wave already sent (and orphaning it). Now rejected withInvalidArgument. (Covers the common no-remove overlap; a remove+re-add of such a topic is a narrower sequencedropTopicpartially settles — noted in a comment.)3. Per-send
time.Afterallocationsend()allocated atime.Aftertimer on every call (live for the whole pong deadline), accumulating under high throughput. Replaced with a reused per-session timer (send runs only on the writer goroutine).Not changed (deliberately)
A cursor above
MaxInt64is left as-is — #562 intentionally clamps it to "no history" with a clean close (TestSubscribe_CursorAboveInt64ReturnsNoHistory), and that decision is respected here. (The d14n binding rejects it instead, because its per-originator cursor path silently drops the entry and replays from 0 rather than clamping — a different, genuinely-buggy code path. Flagging the cross-binding inconsistency in case you want to align them.)Tests
TestSubscribe_AddOverInflightHistoryOnlyRejected(new; gates the wave mid-fetch to hold it in flight).TestSubscribe*suite green under-race;go vet+ gofmt + golangci-lint clean.The flush send-error and the timer reuse are exercised functionally by the existing suite (closures, not unit-testable in isolation); both are structurally identical to the equivalent fixes already tested on the d14n side.
🤖 Generated with Claude Code
Note
Fix flush send-error propagation, history_only overlap rejection, and reusable send timer in
Subscribeflush()in subscribe.go, reads fromsendErrChaftersenderDonesignals so the actualstream.Senderror is returned instead of a false success.time.Afterinsend()with a reusablesendTimer(reset each call, drained via a newstopTimerhelper) to avoid stale timer events on subsequent resets.addrequest withInvalidArgumentwhen the topic already has an in-flighthistory_onlycatch-up wave and is neither subscribed nor catching up.TestSubscribe_AddOverInflightHistoryOnlyRejectedto verify the overlap rejection under a gated read store.Macroscope summarized 4c511c7.