From 1cb607249e50344829dcbc9a4ed49335842e5058 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 24 Nov 2023 13:02:09 +0000 Subject: [PATCH] Fix syncer race condition (#7843) * Initial commit * Log task choosing * A tiny bit more logging * Typo * Restructure syncer to use a single state machine --- core/modules/syncer.js | 286 +++++++++++++++++++++++------------------ 1 file changed, 164 insertions(+), 122 deletions(-) diff --git a/core/modules/syncer.js b/core/modules/syncer.js index c06fcb143..58b087b8d 100644 --- a/core/modules/syncer.js +++ b/core/modules/syncer.js @@ -24,7 +24,7 @@ Syncer.prototype.titleSyncPollingInterval = "$:/config/SyncPollingInterval"; Syncer.prototype.titleSyncDisableLazyLoading = "$:/config/SyncDisableLazyLoading"; Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done"; Syncer.prototype.titleSyncThrottleInterval = "$:/config/SyncThrottleInterval"; -Syncer.prototype.taskTimerInterval = 1 * 1000; // Interval for sync timer +Syncer.prototype.taskTimerInterval = 0.25 * 1000; // Interval for sync timer Syncer.prototype.throttleInterval = 1 * 1000; // Defer saving tiddlers if they've changed in the last 1s... Syncer.prototype.errorRetryInterval = 5 * 1000; // Interval to retry after an error Syncer.prototype.fallbackInterval = 10 * 1000; // Unless the task is older than 10s @@ -74,9 +74,11 @@ function Syncer(options) { this.titlesHaveBeenLazyLoaded = {}; // Hashmap of titles of tiddlers that have already been lazily loaded from the server // Timers this.taskTimerId = null; // Timer for task dispatch - this.pollTimerId = null; // Timer for polling server // Number of outstanding requests this.numTasksInProgress = 0; + // True when we want to force an immediate sync from the server + this.forceSyncFromServer = false; + this.timestampLastSyncFromServer = new Date(); // Listen out for changes to tiddlers this.wiki.addEventListener("change",function(changes) { // Filter the changes to just include ones that are being synced @@ -187,6 +189,7 @@ Syncer.prototype.readTiddlerInfo = function() { // Record information for known tiddlers var self = this, tiddlers = this.getSyncedTiddlers(); + this.logger.log("Initialising tiddlerInfo for " + tiddlers.length + " tiddlers"); $tw.utils.each(tiddlers,function(title) { var tiddler = self.wiki.getTiddler(title); if(tiddler) { @@ -203,33 +206,38 @@ Syncer.prototype.readTiddlerInfo = function() { Checks whether the wiki is dirty (ie the window shouldn't be closed) */ Syncer.prototype.isDirty = function() { - this.logger.log("Checking dirty status"); - // Check tiddlers that are in the store and included in the filter function - var titles = this.getSyncedTiddlers(); - for(var index=0; index tiddlerInfo.changeCount) { + var self = this; + function checkIsDirty() { + // Check tiddlers that are in the store and included in the filter function + var titles = self.getSyncedTiddlers(); + for(var index=0; index tiddlerInfo.changeCount) { + return true; + } + } else { + // If the tiddler isn't known on the server then it needs to be saved to the server return true; } - } else { - // If the tiddler isn't known on the server then it needs to be saved to the server + } + } + // Check tiddlers that are known from the server but not currently in the store + titles = Object.keys(self.tiddlerInfo); + for(index=0; index 0 || updates.deletions.length > 0) { - self.processTaskQueue(); - } - } - }); - } else if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) { - this.logger.log("Retrieving skinny tiddler list"); - cancelNextSync(); - this.syncadaptor.getSkinnyTiddlers(function(err,tiddlers) { - triggerNextSync(); - // Check for errors - if(err) { - self.displayError($tw.language.getString("Error/RetrievingSkinny"),err); - return; - } - // Keep track of which tiddlers we already know about have been reported this time - var previousTitles = Object.keys(self.tiddlerInfo); - // Process each incoming tiddler - for(var t=0; t= (this.timestampLastSyncFromServer.valueOf() + this.pollTimerInterval)))) { + return new SyncFromServerTask(this); + } + // Third, we check tiddlers that are known from the server but not currently in the store, and so need deleting on the server titles = Object.keys(this.tiddlerInfo); for(index=0; index