Bug 1416320 - Do a quick sync before going to sleep. r?markh draft
authorEdouard Oger <eoger@fastmail.com>
Fri, 10 Nov 2017 14:09:44 -0500
changeset 701042 910c0e3a0e2aac190a909fd640d900962d8edce3
parent 700905 081c06e175b2b4431b7af5ea594ff0373e97b70a
child 741068 08b32668b164f4e7b8cfb3bac503d66199f7161d
push id90044
push userbmo:eoger@fastmail.com
push dateTue, 21 Nov 2017 05:38:22 +0000
reviewersmarkh
bugs1416320
milestone59.0a1
Bug 1416320 - Do a quick sync before going to sleep. r?markh MozReview-Commit-ID: I36uvEFlEz5
services/sync/modules/policies.js
services/sync/modules/service.js
services/sync/modules/stages/enginesync.js
--- a/services/sync/modules/policies.js
+++ b/services/sync/modules/policies.js
@@ -172,16 +172,17 @@ SyncScheduler.prototype = {
     Svc.Obs.add("weave:engine:sync:applied", this);
     Svc.Obs.add("weave:service:setup-complete", this);
     Svc.Obs.add("weave:service:start-over", this);
     Svc.Obs.add("FxA:hawk:backoff:interval", this);
 
     if (Status.checkSetup() == STATUS_OK) {
       Svc.Obs.add("wake_notification", this);
       Svc.Obs.add("captive-portal-login-success", this);
+      Svc.Obs.add("sleep_notification", this);
       IdleService.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
     }
   },
 
   // eslint-disable-next-line complexity
   observe: function observe(subject, topic, data) {
     this._log.trace("Handling " + topic);
     switch (topic) {
@@ -323,16 +324,17 @@ SyncScheduler.prototype = {
           this._log.error(`Engine ${data} found ${subject.newFailed} new records that failed to apply`);
         }
         break;
       case "weave:service:setup-complete":
          Services.prefs.savePrefFile(null);
          IdleService.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
          Svc.Obs.add("wake_notification", this);
          Svc.Obs.add("captive-portal-login-success", this);
+         Svc.Obs.add("sleep_notification", this);
          break;
       case "weave:service:start-over":
          this.setDefaults();
          try {
            IdleService.removeIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
          } catch (ex) {
            if (ex.result != Cr.NS_ERROR_FAILURE) {
              throw ex;
@@ -384,16 +386,19 @@ SyncScheduler.prototype = {
         });
         break;
       case "captive-portal-login-success":
         this.shouldSyncWhenLinkComesUp = false;
         this._log.debug("Captive portal login success. Scheduling a sync.");
         CommonUtils.nextTick(() => {
           this.scheduleNextSync(3000);
         });
+      case "sleep_notification":
+        this._log.debug("Going to sleep, doing a quick sync.");
+        this.scheduleNextSync(0, ["tabs"], "sleep");
         break;
     }
   },
 
   adjustSyncInterval: function adjustSyncInterval() {
     if (Status.eol) {
       this._log.debug("Server status is EOL; using eolInterval.");
       this.syncInterval = this.eolInterval;
@@ -485,39 +490,41 @@ SyncScheduler.prototype = {
     this.scheduleNextSync(wait);
   },
 
   /**
    * Call sync() if Master Password is not locked.
    *
    * Otherwise, reschedule a sync for later.
    */
-  syncIfMPUnlocked: function syncIfMPUnlocked() {
+  syncIfMPUnlocked(engines, why) {
     // No point if we got kicked out by the master password dialog.
     if (Status.login == MASTER_PASSWORD_LOCKED &&
         Utils.mpLocked()) {
       this._log.debug("Not initiating sync: Login status is " + Status.login);
 
       // If we're not syncing now, we need to schedule the next one.
       this._log.trace("Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL");
       this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
       return;
     }
 
     if (!Async.isAppReady()) {
       this._log.debug("Not initiating sync: app is shutting down");
       return;
     }
-    CommonUtils.nextTick(this.service.sync, this.service);
+    Services.tm.dispatchToMainThread(() => {
+      this.service.sync({engines, why});
+    });
   },
 
   /**
    * Set a timer for the next sync
    */
-  scheduleNextSync: function scheduleNextSync(interval) {
+  scheduleNextSync(interval, engines = null, why = null) {
     // If no interval was specified, use the current sync interval.
     if (interval == null) {
       interval = this.syncInterval;
     }
 
     // Ensure the interval is set to no less than the backoff.
     if (Status.backoffInterval && interval < Status.backoffInterval) {
       this._log.trace("Requested interval " + interval +
@@ -538,22 +545,23 @@ SyncScheduler.prototype = {
                         interval + " ms.");
         return;
       }
     }
 
     // Start the sync right away if we're already late.
     if (interval <= 0) {
       this._log.trace("Requested sync should happen right away.");
-      this.syncIfMPUnlocked();
+      this.syncIfMPUnlocked(engines, why);
       return;
     }
 
     this._log.debug("Next sync in " + interval + " ms.");
-    CommonUtils.namedTimer(this.syncIfMPUnlocked, interval, this, "syncTimer");
+    CommonUtils.namedTimer(() => { this.syncIfMPUnlocked(engines, why); },
+                           interval, this, "syncTimer");
 
     // Save the next sync time in-case sync is disabled (logout/offline/etc.)
     this.nextSync = Date.now() + interval;
   },
 
 
   /**
    * Incorporates the backoff/retry logic used in error handling and elective
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -903,18 +903,18 @@ Sync11Service.prototype = {
       this.serverConfiguration = configResponse.obj;
     }
     this._log.trace("info/configuration for this server", this.serverConfiguration);
     return true;
   },
 
   // Stuff we need to do after login, before we can really do
   // anything (e.g. key setup).
-  async _remoteSetup(infoResponse) {
-    if (!(await this._fetchServerConfiguration())) {
+  async _remoteSetup(infoResponse, fetchConfig = true) {
+    if (fetchConfig && !(await this._fetchServerConfiguration())) {
       return false;
     }
 
     this._log.debug("Fetching global metadata record");
     let meta = await this.recordManager.get(this.metaURL);
 
     // Checking modified time of the meta record.
     if (infoResponse &&
@@ -1107,17 +1107,17 @@ Sync11Service.prototype = {
   async _lockedSync(engineNamesToSync, why) {
     return this._lock("service.js: sync",
                       this._notify("sync", JSON.stringify({why}), async function onNotify() {
 
       let histogram = Services.telemetry.getHistogramById("WEAVE_START_COUNT");
       histogram.add(1);
 
       let synchronizer = new EngineSynchronizer(this);
-      await synchronizer.sync(engineNamesToSync); // Might throw!
+      await synchronizer.sync(engineNamesToSync, why); // Might throw!
 
       histogram = Services.telemetry.getHistogramById("WEAVE_COMPLETE_SUCCESS_COUNT");
       histogram.add(1);
 
       // We successfully synchronized.
       // Check if the identity wants to pre-fetch a migration sentinel from
       // the server.
       // If we have no clusterURL, we are probably doing a node reassignment
--- a/services/sync/modules/stages/enginesync.js
+++ b/services/sync/modules/stages/enginesync.js
@@ -26,17 +26,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 this.EngineSynchronizer = function EngineSynchronizer(service) {
   this._log = Log.repository.getLogger("Sync.Synchronizer");
   this._log.level = Log.Level[Svc.Prefs.get("log.logger.synchronizer")];
 
   this.service = service;
 };
 
 EngineSynchronizer.prototype = {
-  async sync(engineNamesToSync) {
+  async sync(engineNamesToSync, why) {
+    let fastSync = why && why == "sleep";
     let startTime = Date.now();
 
     this.service.status.resetSync();
 
     // Make sure we should sync or record why we shouldn't.
     let reason = this.service._checkSync();
     if (reason) {
       if (reason == kSyncNetworkOffline) {
@@ -70,27 +71,29 @@ EngineSynchronizer.prototype = {
     // Figure out what the last modified time is for each collection
     let info = await this.service._fetchInfo(infoURL);
 
     // Convert the response to an object and read out the modified times
     for (let engine of [this.service.clientsEngine].concat(engineManager.getAll())) {
       engine.lastModified = info.obj[engine.name] || 0;
     }
 
-    if (!(await this.service._remoteSetup(info))) {
+    if (!(await this.service._remoteSetup(info, !fastSync))) {
       throw new Error("Aborting sync, remote setup failed");
     }
 
-    // Make sure we have an up-to-date list of clients before sending commands
-    this._log.debug("Refreshing client list.");
-    if (!(await this._syncEngine(this.service.clientsEngine))) {
-      // Clients is an engine like any other; it can fail with a 401,
-      // and we can elect to abort the sync.
-      this._log.warn("Client engine sync failed. Aborting.");
-      return;
+    if (!fastSync) {
+      // Make sure we have an up-to-date list of clients before sending commands
+      this._log.debug("Refreshing client list.");
+      if (!(await this._syncEngine(this.service.clientsEngine))) {
+        // Clients is an engine like any other; it can fail with a 401,
+        // and we can elect to abort the sync.
+        this._log.warn("Client engine sync failed. Aborting.");
+        return;
+      }
     }
 
     // We only honor the "hint" of what engines to Sync if this isn't
     // a first sync.
     let allowEnginesHint = false;
     // Wipe data in the desired direction if necessary
     switch (Svc.Prefs.get("firstSync")) {
       case "resetClient":
@@ -102,17 +105,17 @@ EngineSynchronizer.prototype = {
       case "wipeRemote":
         await this.service.wipeRemote(engineManager.enabledEngineNames);
         break;
       default:
         allowEnginesHint = true;
         break;
     }
 
-    if (this.service.clientsEngine.localCommands) {
+    if (!fastSync && this.service.clientsEngine.localCommands) {
       try {
         if (!(await this.service.clientsEngine.processIncomingCommands())) {
           this.service.status.sync = ABORT_SYNC_COMMAND;
           throw new Error("Processed command aborted sync.");
         }
 
         // Repeat remoteSetup in-case the commands forced us to reset
         if (!(await this.service._remoteSetup(info))) {
@@ -174,17 +177,19 @@ EngineSynchronizer.prototype = {
           await this.service.uploadMetaGlobal(meta);
           delete meta.isNew;
           delete meta.changed;
         } catch (error) {
           this._log.error("Unable to upload meta/global. Leaving marked as new.");
         }
       }
 
-      await Doctor.consult(enginesToValidate);
+      if (!fastSync) {
+        await Doctor.consult(enginesToValidate);
+      }
 
       // If there were no sync engine failures
       if (this.service.status.service != SYNC_FAILED_PARTIAL) {
         Svc.Prefs.set("lastSync", new Date().toString());
         this.service.status.sync = SYNC_SUCCEEDED;
       }
     } finally {
       Svc.Prefs.reset("firstSync");