Bug 1391707: Part 1 - Use idle dispatch in DeferredTask. r?florian draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 01 Sep 2017 16:39:14 -0700
changeset 662051 c95267aff7fba0d920c4585d8fcb93757f3c86d8
parent 662050 af5e8e34381bb52e1136bb3ac5d974931ad7c58d
child 662052 a3dab8cf7030c1b6eedf7abb0466806a8778129c
push id78936
push usermaglione.k@gmail.com
push dateSun, 10 Sep 2017 20:18:54 +0000
reviewersflorian
bugs1391707
milestone57.0a1
Bug 1391707: Part 1 - Use idle dispatch in DeferredTask. r?florian MozReview-Commit-ID: Ktlu71aIcRZ
devtools/server/performance/profiler.js
toolkit/modules/DeferredTask.jsm
toolkit/modules/JSONFile.jsm
toolkit/modules/PromiseUtils.jsm
--- a/devtools/server/performance/profiler.js
+++ b/devtools/server/performance/profiler.js
@@ -382,17 +382,17 @@ const ProfilerManager = (function () {
     /**
      * Will enable or disable "profiler-status" events depending on
      * if there are subscribers and if the profiler is current recording.
      */
     _updateProfilerStatusPolling: function () {
       if (this._profilerStatusSubscribers > 0 && nsIProfilerModule.IsActive()) {
         if (!this._poller) {
           this._poller = new DeferredTask(this._emitProfilerStatus.bind(this),
-                                          this._profilerStatusInterval);
+                                          this._profilerStatusInterval, 0);
         }
         this._poller.arm();
       } else if (this._poller) {
         // No subscribers; turn off if it exists.
         this._poller.disarm();
       }
     },
 
--- a/toolkit/modules/DeferredTask.jsm
+++ b/toolkit/modules/DeferredTask.jsm
@@ -104,20 +104,25 @@ const Timer = Components.Constructor("@m
  *        not considered complete until that promise resolves.  This
  *        task is never re-entered while running.
  * @param aDelayMs
  *        Time between executions, in milliseconds.  Multiple attempts to run
  *        the task before the delay has passed are coalesced.  This time of
  *        inactivity is guaranteed to pass between multiple executions of the
  *        task, except on finalization, when the task may restart immediately
  *        after the previous execution finished.
+ * @param aIdleTimeoutMs
+ *        The maximum time to wait for an idle slot on the main thread after
+ *        aDelayMs have elapsed. If omitted, waits indefinitely for an idle
+ *        callback.
  */
-this.DeferredTask = function(aTaskFn, aDelayMs) {
+this.DeferredTask = function(aTaskFn, aDelayMs, aIdleTimeoutMs) {
   this._taskFn = aTaskFn;
   this._delayMs = aDelayMs;
+  this._timeoutMs = aIdleTimeoutMs;
 
   if (aTaskFn.isGenerator()) {
     Cu.reportError(new Error(`Unexpected generator function passed to DeferredTask`));
   }
 }
 
 this.DeferredTask.prototype = {
   /**
@@ -294,18 +299,24 @@ this.DeferredTask.prototype = {
 
       // Indicate that the execution of the task has finished.  This happens
       // synchronously with the previous state changes in the function.
       this._runningPromise = null;
     })().catch(Cu.reportError));
   },
 
   /**
-   * Executes the associated task and catches exceptions.
+   * Executes the associated task in an idle callback and catches exceptions.
    */
   async _runTask() {
     try {
-      await this._taskFn();
+      // If we're being finalized, execute the task immediately, so we don't
+      // risk blocking async shutdown longer than necessary.
+      if (this._finalized || this._timeoutMs === 0) {
+        await this._taskFn();
+      } else {
+        await PromiseUtils.idleDispatch(this._taskFn, this._timeoutMs);
+      }
     } catch (ex) {
       Cu.reportError(ex);
     }
   },
 };
--- a/toolkit/modules/JSONFile.jsm
+++ b/toolkit/modules/JSONFile.jsm
@@ -32,17 +32,16 @@ this.EXPORTED_SYMBOLS = [
   "JSONFile",
 ];
 
 // Globals
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                   "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                   "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
--- a/toolkit/modules/PromiseUtils.jsm
+++ b/toolkit/modules/PromiseUtils.jsm
@@ -1,28 +1,55 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict"
 
 this.EXPORTED_SYMBOLS = ["PromiseUtils"];
 
+Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/Timer.jsm");
 
 this.PromiseUtils = {
   /*
    * Creates a new pending Promise and provide methods to resolve and reject this Promise.
    *
    * @return {Deferred} an object consisting of a pending Promise "promise"
    * and methods "resolve" and "reject" to change its state.
    */
   defer() {
     return new Deferred();
   },
+
+  /**
+   * Requests idle dispatch to the main thread for the given callback,
+   * and returns a promise which resolves to the callback's return value
+   * when it has been executed.
+   *
+   * @param {function} callback
+   * @param {integer} [timeout]
+   *        An optional timeout, after which the callback will be
+   *        executed immediately if idle dispatch has not yet occurred.
+   *
+   * @returns {Promise}
+   */
+  idleDispatch(callback, timeout = 0) {
+    return new Promise((resolve, reject) => {
+      Services.tm.idleDispatchToMainThread(
+        () => {
+          try {
+            resolve(callback());
+          } catch (e) {
+            reject(e);
+          }
+        },
+        timeout);
+    });
+  },
 }
 
 /**
  * The definition of Deferred object which is returned by PromiseUtils.defer(),
  * It contains a Promise and methods to resolve/reject it.
  */
 function Deferred() {
   /* A method to resolve the associated Promise with the value passed.