fix(sync): hold replies to requesting peers until storage has been checked - #763
Open
Tanyayayya wants to merge 2 commits into
Open
Tanyayayya wants to merge 2 commits into
Tanyayayya wants to merge 2 commits into
Conversation
Tanyayayya
marked this pull request as ready for review
September 14, 2026 17:43
…ecked Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tanyayayya
force-pushed
the
fix/defer-requesting-peers-until-storage
branch
from
September 14, 2026 20:05
e34f11a to
0718e66
Compare
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.
When a peer requests a document the repo does not have in memory yet,
#evaluateanswers it straight away from the empty doc while the storage source is still loading. The storage-deferral guard only covers peers in theunknownstate; a requesting peer is alreadywants, so it is skipped.Some clients treat that first empty-heads reply as "unavailable". We hit this with a sync server that creates documents through a separate REST service and loads them from S3 on first request: the Python automerge client got an empty reply, marked the document unavailable, and stalled until its 120 s timeout.
This is the same failure class as #343, reported in 2024 from the Swift client: a premature "unavailable" for a document that then syncs fine. The envelope has changed since (empty-heads sync now, explicit
doc-unavailablethen) but the cause is the same, answering a requester before storage has been checked. The test here is the one asked for in that thread. Fixes #343.This applies the existing guard to
wantspeers too, so the reply waits until storage has settled. Once storage resolves, the deferred peer is still dirty and gets its message on the next evaluate pass.The deferral is bounded:
shouldDeferAvailabilityonly waits on a strictly higher-priority source, so a requester waits for the storage lookup and nothing else. Other peers that are connected but silent do not hold it up. I tried keying on the query being inloadinginstead; it passes the suite, but a single silent peer would then stall a requester with no bound.When storage settles empty, the wire order in one evaluate pass is an empty-heads sync followed by
doc-unavailable. So a requester can still see an empty sync for a document that does not exist, just not before storage has been checked, and never without thedoc-unavailablebehind it.Note on blast radius: a JS peer is unaffected by the empty reply either way. It only promotes a sender to
hason non-empty heads, so the empty sync leaves the senderunknown, the sync source stays pending, and the query stays inloadinguntil real data arrives. The change matters for clients that read an initial empty-heads sync as "unavailable", but the guard's own comment already states the principle: don't publish something other peers may read as evidence we don't have the document. An empty-heads sync to a requesting peer is that same evidence in a different envelope.Full test suite passes (891 tests). Includes a regression test in
DocSynchronizer.test.ts: a requesting peer gets no reply while the storage source is pending, and does get one once storage settles. It fails onmainand passes with this change.