Repository navigation
fix(sync): finish syncing with a server before disconnectServers() closes it - #1360
gmaclennan wants to merge 6 commits into
Conversation
…oses it disconnectServers() closed every server websocket at once, so leaving the Exchange screen mid-upload left the archive with a partial original, and the auth and config namespaces could be cut short too. Each server connection now closes once sync with it has finished in both directions, or when its want and wanted have not changed for the same idle timeout that bounds finishing after stop(). connectServers() cancels a pending disconnect. A connection with no peer yet closes straight away, since nothing has started.
Adds a pausable websocket relay for the manager makeWebsocket option so a server connection can be held mid-transfer.
…loses If connectServers() reopens a url while the previous socket is still closing, the old close handler removed the new entry, leaving a connection that disconnectServers() could no longer see. Only delete the entry that still belongs to the closing socket. Not pinned by a test: it would need to race the close handshake against the server url lookup.
A namespace the server has not opened yet read as stopped, which counted as finished, so a disconnect that landed before the namespaces had all opened closed the socket at the first intermediate synced state: after presync with data sync wanted, or after auth with data sync off, and the rest never uploaded. The check now takes the level #updateState has just set for the peer, so it does not depend on the controller having reacted to the same state event, and requires every non-blocked namespace at that level to be open with nothing to transfer either way. The idle timer still bounds a server that never opens one.
…bracket the idle bound Adds the two reproductions from the review (disconnect during presync with data sync wanted; disconnect while auth is still syncing with data sync off), start() while a disconnect is pending, autostop while pending and a full stop while pending. The stall test now settles in real time after each advance and waits only briefly for the close, so a 5 s or a 25 s timeout fails it; before, both passed.
|
Two more commits after an independent review of the first version. The review showed the finished check was wrong in a window I had taken to be a few milliseconds: a namespace the server has not opened yet reads The review also showed the stall test did not pin the 15 s bound: it passed with the timeout set to 5 s and to 25 s. dffd5c3 adds the two reproductions and three more cases ( |
…ver-disconnect * fix/sync-stop-keeps-serving: test(sync): make the stall test bracket the idle timeout
Stacked on #1353, which it builds on; the diff here is the last five commits.
Leaving the Exchange screen while an upload to the archive is still running cuts the websocket straight away, and the archive is left holding a partial original until the phone comes back and the upload resumes. The screen calls
disconnectServers()on blur, and that closed every server websocket at once whatever was in flight, including the auth and config namespaces. digidem/comapeo-mobile#2213 first tried to make the screen decide when to disconnect, but the screen only has derived UI state and the decision belongs with the thing that knows whether sync has finished.With this change
disconnectServers()means "disconnect from servers once sync with them has finished". What counts as finished depends on the level the peer is at: while data sync is wanted, every non-blocked namespace must be open with nothing to transfer either way; otherwise the presync namespaces (auth, config, blob index) must be. A namespace the server has not opened yet is not finished, because the peer has been set to a level that includes it. The bound is the same as finishing afterstop(): if the peer's want and wanted have not changed for 15 s it is closed anyway, and the timer restarts on any change, so a slow server that is still making progress is never cut. A connection that has no peer yet, because the handshake has not completed, is closed straight away since nothing has started.connectServers()cancels a pending disconnect and keeps a connection that is still open, so blurring and refocusing the screen keeps the socket. A server that we are finishing with afterstop()is held for the data namespaces by #1353 and then closed here, which is the Exchange screen flow: the user presses stop and leaves, and the upload completes before the socket goes.While data sync is still enabled, "finished with the server" can keep extending if the archive receives new data from other devices. That is real transfer rather than a stall, but it means a blur during a busy period holds the socket for longer. A full stop (app backgrounded) does not touch server websockets, as before.
The implementation keeps the protomux alongside each websocket so the connection can be matched to its
PeerSyncController, and sweeps pending disconnects at the end of every#updateState(). The finished check takes the sync level that#updateState()has just set for the peer, exposed by a new getter on the controller, rather than what the controller has already opened, because the sweep runs before the controllers react to the same state event. The first version decided from remote status alone and treated a namespace that readstoppedas finished; an independent review showed that closes the socket at the first intermediate synced state when the disconnect lands before the server's namespaces have all opened, after presync with data sync wanted or after auth with data sync off, and the rest never uploads. The window is from connect until the next namespaces open, a second or more. Apendingstatus that records "enabled on our side, not yet open" would make this a property of the state rather than of the level, and is proposed separately in #1362. A third commit guards the socket close handler so a reconnect that replaced the socket for the same url is not dropped from the connection map. Nothing new is added to the sync state; the server's entry inremoteDeviceSyncStatedisappears when its socket closes, anddata.isStoppingcovers the stop case.Tests add a pausable websocket relay for the manager's
makeWebsocketoption so a server connection can be held mid-transfer against the in-process test server. Ten cases: an upload finishes before the socket closes;connectServers()cancels the disconnect; a stalled server is dropped after the idle timeout; stop then disconnect finishes the transfer first; config syncs to the server before disconnecting when data sync is off; a disconnect during presync still syncs the data thatstart()asked for; a disconnect while auth is still syncing finishes the config first;start()while a disconnect is pending syncs the data too; autostop while pending; and a full stop while pending. The three window tests fail on the first version of the check. The stall test uses fake timers but settles in real time after each advance and waits only briefly for the close, so a 5 s or a 25 s timeout fails it; the earlier version passed with both. The existing server tests pass unchanged.Related changes: digidem/comapeo-mobile#2213 now only swaps the screen's direct api calls for the core-react mutation hooks and goes back to an unconditional disconnect on blur, so the behaviour arrives with the next core release. digidem/comapeo-cloud#78 closes a server-side socket that has gone silent, #1359 proposes closing the older connection when a device reconnects, and #1362 proposes the
pendingstatus.