Bug 1464743: Restore extension child shutdown timeout. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Wed, 06 Jun 2018 12:43:26 -0700
changeset 804915 bb12dfd0f2c2ab6e1a0980e7295726ed224de73a
parent 804910 13251c6d1ee246f50771d4c2c73ad4d7177d1448
child 804927 99dda26cf03857faf3cdc9da716c6ae2cefe799a
child 804933 80d46149bf52e22159c52b3875f65880e55a4959
push id112496
push usermaglione.k@gmail.com
push dateWed, 06 Jun 2018 19:46:43 +0000
reviewersaswan
bugs1464743
milestone62.0a1
Bug 1464743: Restore extension child shutdown timeout. r?aswan MozReview-Commit-ID: 8O6CgKsOwom
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionUtils.jsm
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -92,22 +92,26 @@ var {
   ParentAPIManager,
   StartupCache,
   apiManager: Management,
 } = ExtensionParent;
 
 const {
   EventEmitter,
   getUniqueId,
+  promiseTimeout,
 } = ExtensionUtils;
 
 XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);
 
 XPCOMUtils.defineLazyGetter(this, "LocaleData", () => ExtensionCommon.LocaleData);
 
+// The maximum time to wait for extension child shutdown blockers to complete.
+const CHILD_SHUTDOWN_TIMEOUT_MS = 8000;
+
 /**
  * Classify an individual permission from a webextension manifest
  * as a host/origin permission, an api permission, or a regular permission.
  *
  * @param {string} perm  The permission string to classify
  *
  * @returns {object}
  *          An object with exactly one of the following properties:
@@ -1795,17 +1799,25 @@ class Extension extends ExtensionData {
       obj.close();
     }
 
     ParentAPIManager.shutdownExtension(this.id);
 
     Management.emit("shutdown", this);
     this.emit("shutdown");
 
-    await this.broadcast("Extension:Shutdown", {id: this.id});
+    const TIMED_OUT = Symbol();
+
+    let result = await Promise.race([
+      this.broadcast("Extension:Shutdown", {id: this.id}),
+      promiseTimeout(CHILD_SHUTDOWN_TIMEOUT_MS).then(() => TIMED_OUT),
+    ]);
+    if (result === TIMED_OUT) {
+      Cu.reportError(`Timeout while waiting for extension child to shutdown: ${this.policy.debugName}`);
+    }
 
     MessageChannel.abortResponses({extensionId: this.id});
 
     this.policy.active = false;
 
     return this.cleanupGeneratedFile();
   }
 
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -7,16 +7,18 @@
 
 var EXPORTED_SYMBOLS = ["ExtensionUtils"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "ConsoleAPI",
                                "resource://gre/modules/Console.jsm");
+ChromeUtils.defineModuleGetter(this, "setTimeout",
+                               "resource://gre/modules/Timer.jsm");
 
 function getConsole() {
   return new ConsoleAPI({
     maxLogLevelPref: "extensions.webextensions.log.level",
     prefix: "WebExtensions",
   });
 }
 
@@ -41,16 +43,19 @@ const uniqueProcessID = appinfo.uniquePr
 const processIDMask = (uniqueProcessID & 0xffff) * (2 ** 37);
 
 function getUniqueId() {
   // Note: We can't use bitwise ops here, since they truncate to a 32 bit
   // integer and we need all 53 mantissa bits.
   return processIDMask + nextId++;
 }
 
+function promiseTimeout(delay) {
+  return new Promise(resolve => setTimeout(resolve, delay));
+}
 
 /**
  * An Error subclass for which complete error messages are always passed
  * to extensions, rather than being interpreted as an unknown error.
  */
 class ExtensionError extends Error {}
 
 function filterStack(error) {
@@ -684,16 +689,17 @@ var ExtensionUtils = {
   instanceOf,
   makeWidgetId,
   normalizeTime,
   promiseDocumentIdle,
   promiseDocumentLoaded,
   promiseDocumentReady,
   promiseEvent,
   promiseObserved,
+  promiseTimeout,
   runSafeSyncWithoutClone,
   withHandlingUserInput,
   DefaultMap,
   DefaultWeakMap,
   EventEmitter,
   ExtensionError,
   LimitedSet,
   MessageManagerProxy,