Bug 1018320 - RequestSync API - patch 4 - sendAsyncMessage used to schedule tasks, r=ehsan
☠☠ backed out by c3569fd75d4e ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 05 Jan 2015 13:42:03 +0100
changeset 221997 2df23c44ef44ba54888d4fe966a2c73d81d5d7d0
parent 221996 daf456b0a23a470cc2739ab4a198fb4e95956ed5
child 221998 6063a2463d6c241e6325f7faf03f558757bb2010
push id53487
push useramarchesini@mozilla.com
push dateMon, 05 Jan 2015 12:42:21 +0000
treeherdermozilla-inbound@f60d4ad64070 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs1018320
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1018320 - RequestSync API - patch 4 - sendAsyncMessage used to schedule tasks, r=ehsan
dom/messages/SystemMessageInternal.js
dom/requestsync/RequestSyncService.jsm
--- a/dom/messages/SystemMessageInternal.js
+++ b/dom/messages/SystemMessageInternal.js
@@ -174,17 +174,17 @@ SystemMessageInternal.prototype = {
         page = aPage;
       }
       return page !== null;
     }, this);
     return page;
   },
 
   sendMessage: function(aType, aMessage, aPageURI, aManifestURI, aExtra) {
-    return new Promise((resolve, reject) => {
+    return new Promise((aResolve, aReject) => {
       this.sendMessageInternal(aType, aMessage, aPageURI, aManifestURI, aExtra,
                                aResolve, aReject);
     });
   },
 
   sendMessageInternal: function(aType, aMessage, aPageURI, aManifestURI,
                                 aExtra, aResolvePromiseCb, aRejectPromiseCb) {
     // Buffer system messages until the webapps' registration is ready,
--- a/dom/requestsync/RequestSyncService.jsm
+++ b/dom/requestsync/RequestSyncService.jsm
@@ -9,16 +9,18 @@ const {classes: Cc, interfaces: Ci, util
 function debug(s) {
   //dump('DEBUG RequestSyncService: ' + s + '\n');
 }
 
 const RSYNCDB_VERSION = 1;
 const RSYNCDB_NAME = "requestSync";
 const RSYNC_MIN_INTERVAL = 100;
 
+const RSYNC_OPERATION_TIMEOUT = 120000 // 2 minutes
+
 Cu.import('resource://gre/modules/IndexedDBHelper.jsm');
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.importGlobalProperties(["indexedDB"]);
 
 
 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
                                    "@mozilla.org/AppsService;1",
@@ -51,16 +53,19 @@ this.RequestSyncService = {
 
   _pendingOperation: false,
   _pendingMessages: [],
 
   _registrations: {},
 
   _wifi: false,
 
+  _activeTask: null,
+  _queuedTasks: [],
+
   // Initialization of the RequestSyncService.
   init: function() {
     debug("init");
 
     this._messages.forEach((function(msgName) {
       ppmm.addMessageListener(msgName, this);
     }).bind(this));
 
@@ -227,29 +232,44 @@ this.RequestSyncService = {
 
     this.removeRegistrationInternal(aTaskName, aKey);
     return true;
   },
 
   removeRegistrationInternal: function(aTaskName, aKey) {
     debug('removeRegistrationInternal');
 
-    if (this._registrations[aKey][aTaskName].timer) {
-      this._registrations[aKey][aTaskName].timer.cancel();
+    let obj = this._registrations[aKey][aTaskName];
+    if (obj.timer) {
+      obj.timer.cancel();
     }
 
+    // It can be that this task has been already schedulated.
+    this.removeTaskFromQueue(obj);
+
+    // It can be that this object is already in scheduled, or in the queue of a
+    // iDB transacation. In order to avoid rescheduling it, we must disable it.
+    obj.active = false;
+
     delete this._registrations[aKey][aTaskName];
 
     // Lets remove the key in case there are not tasks registered.
     for (var key in this._registrations[aKey]) {
       return;
     }
     delete this._registrations[aKey];
   },
 
+  removeTaskFromQueue: function(aObj) {
+    let pos = this._queuedTasks.indexOf(aObj);
+    if (pos != -1) {
+      this._queuedTasks.splice(pos, 1);
+    }
+  },
+
   // The communication from the exposed objects and the service is done using
   // messages. This function receives and processes them.
   receiveMessage: function(aMessage) {
     debug("receiveMessage");
 
     // We cannot process this request now.
     if (this._pendingOperation) {
       this._pendingMessages.push(aMessage);
@@ -505,78 +525,140 @@ this.RequestSyncService = {
     aObj.timer.initWithCallback(function() { self.timeout(aObj); },
                                 aObj.data.minInterval * 1000,
                                 Ci.nsITimer.TYPE_ONE_SHOT);
   },
 
   timeout: function(aObj) {
     debug("timeout");
 
+    if (this._activeTask) {
+      debug("queueing tasks");
+      // We have an active task, let's queue this as next task.
+      if (this._queuedTasks.indexOf(aObj) == -1) {
+        this._queuedTasks.push(aObj);
+      }
+      return;
+    }
+
     let app = appsService.getAppByLocalId(aObj.principal.appId);
     if (!app) {
       dump("ERROR!! RequestSyncService - Failed to retrieve app data from a principal.\n");
       aObj.active = false;
       this.updateObjectInDB(aObj);
       return;
     }
 
     let manifestURL = Services.io.newURI(app.manifestURL, null, null);
     let pageURL = Services.io.newURI(aObj.data.wakeUpPage, null, aObj.principal.URI);
 
     // Maybe need to be rescheduled?
-    if (this.needRescheduling('request-sync', manifestURL, pageURL)) {
+    if (this.hasPendingMessages('request-sync', manifestURL, pageURL)) {
       this.scheduleTimer(aObj);
       return;
     }
 
     aObj.timer = null;
+    this._activeTask = aObj;
 
     if (!manifestURL || !pageURL) {
       dump("ERROR!! RequestSyncService - Failed to create URI for the page or the manifest\n");
       aObj.active = false;
       this.updateObjectInDB(aObj);
       return;
     }
 
-    // Sending the message.
-    systemMessenger.sendMessage('request-sync',
-                                this.createPartialTaskObject(aObj.data),
-                                pageURL, manifestURL);
+    // We don't want to run more than 1 task at the same time. We do this using
+    // the promise created by sendMessage(). But if the task takes more than
+    // RSYNC_OPERATION_TIMEOUT millisecs, we have to ignore the promise and
+    // continue processing other tasks.
+
+    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
-    // One shot? Then this is not active.
-    aObj.active = !aObj.data.oneShot;
-    aObj.data.lastSync = new Date();
+    let done = false;
+    let self = this;
+    function taskCompleted() {
+      debug("promise or timeout for task calls taskCompleted");
+
+      if (!done) {
+        done = true;
+        self.operationCompleted();
+      }
 
-    let self = this;
-    this.updateObjectInDB(aObj, function() {
-      // SchedulerTimer creates a timer and a nsITimer cannot be cloned. This
-      // is the reason why this operation has to be done after storing the aObj
-      // into IDB.
-      if (!aObj.data.oneShot) {
-        self.scheduleTimer(aObj);
-      }
+      timer.cancel();
+      timer = null;
+    }
+
+    timer.initWithCallback(function() {
+      debug("Task is taking too much, let's ignore the promise.");
+      taskCompleted();
+    }, RSYNC_OPERATION_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
+
+    // Sending the message.
+    let promise =
+      systemMessenger.sendMessage('request-sync',
+                                  this.createPartialTaskObject(aObj.data),
+                                  pageURL, manifestURL);
+
+    promise.then(function() {
+      debug("promise resolved");
+      taskCompleted();
+    }, function() {
+      debug("promise rejected");
+      taskCompleted();
     });
   },
 
-  needRescheduling: function(aMessageName, aManifestURL, aPageURL) {
+  operationCompleted: function() {
+    debug("operationCompleted");
+
+    if (!this._activeTask) {
+      dump("ERROR!! RequestSyncService - OperationCompleted called without an active task\n");
+      return;
+    }
+
+    // One shot? Then this is not active.
+    this._activeTask.active = !this._activeTask.data.oneShot;
+    this._activeTask.data.lastSync = new Date();
+
+    let self = this;
+    this.updateObjectInDB(this._activeTask, function() {
+      // SchedulerTimer creates a timer and a nsITimer cannot be cloned. This
+      // is the reason why this operation has to be done after storing the task
+      // into IDB.
+      if (!self._activeTask.data.oneShot) {
+        self.scheduleTimer(self._activeTask);
+      }
+
+      self.processNextTask();
+    });
+  },
+
+  processNextTask: function() {
+    debug("processNextTask");
+
+    this._activeTask = null;
+
+    if (this._queuedTasks.length == 0) {
+      return;
+    }
+
+    let task = this._queuedTasks.shift();
+    this.timeout(task);
+  },
+
+  hasPendingMessages: function(aMessageName, aManifestURL, aPageURL) {
     let hasPendingMessages =
       cpmm.sendSyncMessage("SystemMessageManager:HasPendingMessages",
                            { type: aMessageName,
                              pageURL: aPageURL.spec,
                              manifestURL: aManifestURL.spec })[0];
 
     debug("Pending messages: " + hasPendingMessages);
-
-    if (hasPendingMessages) {
-      return true;
-    }
-
-    // FIXME: other reasons?
-
-    return false;
+    return hasPendingMessages;
   },
 
   // Update the object into the database.
   updateObjectInDB: function(aObj, aCb) {
     debug("updateObjectInDB");
 
     this.dbTxn('readwrite', function(aStore) {
       aStore.put(aObj, aObj.dbKey);
@@ -644,20 +726,24 @@ this.RequestSyncService = {
   },
 
   wifiStateChanged: function(aEnabled) {
     debug("onWifiStateChanged");
     this._wifi = aEnabled;
 
     if (!this._wifi) {
       // Disable all the wifiOnly tasks.
+      let self = this;
       this.forEachRegistration(function(aObj) {
         if (aObj.data.wifiOnly && aObj.timer) {
           aObj.timer.cancel();
           aObj.timer = null;
+
+          // It can be that this task has been already schedulated.
+          self.removeTaskFromQueue(aObj);
         }
       });
       return;
     }
 
     // Enable all the tasks.
     let self = this;
     this.forEachRegistration(function(aObj) {