You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 27b784b
Browse filesBrowse the repository at this point in the historyBrowse files
fix(collab): serialize fan-out per socket with a flag, not by pre-claiming rev
The race this PR identified is real. With the guard removed, three concurrent
updatePadClients() runs deliver [1,1,2,1,2,3,2,3,3] to one client — every
revision three times (new spec, padFanOutSerialization.ts).
But claiming the range by writing `sessioninfo.rev = headRev` before the awaits
breaks a guarantee that rev carries elsewhere. handleUserChanges asserts
`thisSession.rev === r` right before emitting ACCEPT_COMMIT, with the comment
"make sure we have already sent any previous ACCEPT_COMMIT and NEW_CHANGES
messages". Pre-claiming satisfies that assert while the NEW_CHANGES for those
revisions are still queued behind `await pad.getRevision()`, so ACCEPT_COMMIT can
overtake them — the exact out-of-order delivery the assert exists to prevent.
The rollback path has the same problem in reverse: a mid-batch emit failure
rewinds to startRev and re-sends revisions the client already applied, where the
per-revision commit only retried the one that failed.
So: keep the loop as it was, add `sessioninfo.fanOutInFlight`. At most one fan-out
per socket, rev keeps meaning "last revision actually sent", retry semantics
unchanged, and a skipped run loses nothing because the holder re-reads
getHeadRevisionNumber() each iteration.
Change B (NEW_CHANGES_BATCH) is dropped. It never fired in the dive by the PR's
own account, it changes the wire format behind a flag that needs client
coordination, and it does not belong in a correctness fix. Worth its own PR if
the packet-count argument is ever measured.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0 commit comments