Repository navigation
fix(sync): reject waitForSync when all peers disconnect before sync completes - #1352
Open
gmaclennan wants to merge 2 commits into
Open
gmaclennan wants to merge 2 commits into
gmaclennan wants to merge 2 commits into
Conversation
This branch has not been deployed
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 caller awaits
$sync.waitForSync('full')and the only connected peer drops part way through, the promise resolves and the caller believes sync completed. The mobile Exchange screen derives its stage from the peer count so it does not show this, but anything that callswaitForSyncdirectly (cloud tests, scripts, server-side code) is misled.The cause is in
src/sync/sync-api.js:isSynced()checksdataToSyncand then each connected peer sync controller, butwant/wantedare only counted against connected peers' bitfields anddisconnectPeerremoves the peer's remote state, so once the last peer is gone there is nothing left to check and the predicate is vacuously true.waitForSyncnow remembers whether it has seen a connected peer and, if the count later drops to zero, rejects with the existingPeerDisconnectedError(the same error the invitor-sidekWaitForInitialSyncWithPeerwait raises when its peer goes away).isSynced()and#updateStateare untouched, so the full-stop and autostop table keeps its behaviour. Calling with no peers at all still resolves at once, as before, witherrorOnNoPeersas the existing opt-in for that case. The test keeps data sync off on one side so the wanted block can never arrive, which is deterministic where racing a blob transfer would not be.A few design questions for reviewers:
Rejecting rather than waiting: waiting until
timeoutMswould hang every caller that passes no timeout (mobile tests, cloud tests, the repro scripts) and would make offlineleaveProjectwait 45 s, so rejecting seemed the right default. The line is drawn at zero peers rather than any peer leaving because the existing test "Sync state with disconnected peer (want data from peer)" expects full sync to complete with the remaining peer.The
'initial'type is covered too, soaddProjectnow fails if the invitor disconnects mid-initial-sync (and withleaveOnFail, used for invite links, clears the project and throwsInitialSyncFailedError) instead of silently keeping a half-synced project. The invitor side already treats that disconnect as a failure, so this looks like the intended behaviour, but it is a change.The check runs on the throttled
statesnapshot, so a peer that disconnects within about 200 ms of sync genuinely completing is reported as a disconnect.kWaitForInitialSyncWithPeeravoids that window with the unthrottledupdateevent; I left that out here to keep the diff small, and the reviewer could not trigger the false negative in 14 attempts.Also considered and not done: a dedicated error class instead of reusing
PeerDisconnectedError, and rejecting when there are no peers at call time as well, which would break theconnectServers()thenwaitForSync('full')pattern the cloud tests use.