Bug 1018320 - RequestSync API - patch 4 - sendAsyncMessage used to schedule tasks, r=ehsan
☠☠ backed out by 636498d041b5 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Sun, 04 Jan 2015 10:37:03 +0100
changeset 247782 46353577ef7a88f1b3032e6ea04ac94d14b58f44
parent 247781 edf5737d6e0e7ead604d0b0cc7b15a33f3828150
child 247783 8c01c134e40f0c73b8841e2210b17333944e4804
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [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) {