Bug 1012466 - Introduce AddonManager shutdown barrier. r=irving,a=lmandel
☠☠ backed out by ada88bfa9f0a ☠ ☠
authorGeorg Fritzsche <georg.fritzsche@googlemail.com>
Tue, 08 Jul 2014 18:56:04 +0200
changeset 209089 7d1746cdf76526ca9aefca4d5c7815f15250f985
parent 209088 3f0b51d12caf86dd92d3d5687ab5d84517434bc4
child 209090 2af1ca98c935bf533f88e6e6d740bfa033b4359c
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersirving, lmandel
bugs1012466
milestone32.0a2
Bug 1012466 - Introduce AddonManager shutdown barrier. r=irving,a=lmandel
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/test/xpcshell/head_addons.js
toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -458,16 +458,17 @@ var gStarted = false;
 var gStartupComplete = false;
 var gCheckCompatibility = true;
 var gStrictCompatibility = true;
 var gCheckUpdateSecurityDefault = true;
 var gCheckUpdateSecurity = gCheckUpdateSecurityDefault;
 var gUpdateEnabled = true;
 var gAutoUpdateDefault = true;
 var gHotfixID = null;
+var gShutdownBarrier = null;
 
 /**
  * This is the real manager, kept here rather than in AddonManager to keep its
  * contents hidden from API users.
  */
 var AddonManagerInternal = {
   managerListeners: [],
   installListeners: [],
@@ -736,18 +737,19 @@ var AddonManagerInternal = {
         catch (e) {
           AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
           logger.error("Exception loading provider " + entry + " from category \"" +
                 url + "\"", e);
         }
       }
 
       // Register our shutdown handler with the AsyncShutdown manager
+      gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for clients to shut down.");
       AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down providers",
-                                                   this.shutdown.bind(this));
+                                                   this.shutdownManager.bind(this));
 
       // Once we start calling providers we must allow all normal methods to work.
       gStarted = true;
 
       this.callProviders("startup", appChanged, oldAppVersion,
                          oldPlatformVersion);
 
       // If this is a new profile just pretend that there were no changes
@@ -925,51 +927,55 @@ var AddonManagerInternal = {
   },
 
   /**
    * Shuts down the addon manager and all registered providers, this must clean
    * up everything in order for automated tests to fake restarts.
    * @return Promise{null} that resolves when all providers and dependent modules
    *                       have finished shutting down
    */
-  shutdown: function AMI_shutdown() {
+  shutdownManager: function() {
     logger.debug("shutdown");
     // Clean up listeners
     Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this);
     Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this);
     Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this);
     Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this);
     Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);
     Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this);
 
     // Only shut down providers if they've been started. Shut down
     // AddonRepository after providers (if any).
     let shuttingDown = null;
     if (gStarted) {
-      shuttingDown = this.callProvidersAsync("shutdown")
+      shuttingDown = gShutdownBarrier.wait()
+        .then(null, err => logger.error("Failure during wait for shutdown barrier", err))
+        .then(() => this.callProvidersAsync("shutdown"))
         .then(null,
               err => logger.error("Failure during async provider shutdown", err))
         .then(() => AddonRepository.shutdown());
     }
     else {
       shuttingDown = AddonRepository.shutdown();
     }
 
-      shuttingDown.then(val => logger.debug("Async provider shutdown done"),
-                        err => logger.error("Failure during AddonRepository shutdown", err))
+    shuttingDown.then(val => logger.debug("Async provider shutdown done"),
+                      err => logger.error("Failure during AddonRepository shutdown", err))
       .then(() => {
         this.managerListeners.splice(0, this.managerListeners.length);
         this.installListeners.splice(0, this.installListeners.length);
         this.addonListeners.splice(0, this.addonListeners.length);
         this.typeListeners.splice(0, this.typeListeners.length);
         for (let type in this.startupChanges)
           delete this.startupChanges[type];
         gStarted = false;
         gStartupComplete = false;
+        gShutdownBarrier = null;
       });
+
     return shuttingDown;
   },
 
   /**
    * Notified when a preference we're interested in has changed.
    *
    * @see nsIObserver
    */
@@ -2794,16 +2800,20 @@ this.AddonManager = {
   },
 
   get hotfixID() {
     return AddonManagerInternal.hotfixID;
   },
 
   escapeAddonURI: function AM_escapeAddonURI(aAddon, aUri, aAppVersion) {
     return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion);
-  }
+  },
+
+  get shutdown() {
+    return gShutdownBarrier.client;
+  },
 };
 
 // load the timestamps module into AddonManagerInternal
 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", AddonManagerInternal);
 Object.freeze(AddonManagerInternal);
 Object.freeze(AddonManagerPrivate);
 Object.freeze(AddonManager);
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -37,18 +37,36 @@ let AddonManagerInternal = AMscope.Addon
 // down AddonManager from the test
 let MockAsyncShutdown = {
   hook: null,
   profileBeforeChange: {
     addBlocker: function(aName, aBlocker) {
       do_print("Mock profileBeforeChange blocker for '" + aName + "'");
       MockAsyncShutdown.hook = aBlocker;
     }
-  }
+  },
+  Barrier: function (name) {
+    this.name = name;
+    this.client.addBlocker = (name, blocker) => {
+      do_print("Mock Barrier blocker for '" + name + "' for barrier '" + this.name + "'");
+      this.blockers.push({name: name, blocker: blocker});
+    };
+  },
 };
+
+MockAsyncShutdown.Barrier.prototype = Object.freeze({
+  blockers: [],
+  client: {},
+  wait: Task.async(function* () {
+    for (let b of this.blockers) {
+      yield b.blocker();
+    }
+  }),
+});
+
 AMscope.AsyncShutdown = MockAsyncShutdown;
 
 var gInternalManager = null;
 var gAppInfo = null;
 var gAddonsList;
 
 var gPort = null;
 var gUrlToFileMap = {};
--- a/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js
@@ -4,32 +4,32 @@
 
 // Verify that API functions fail if the Add-ons Manager isn't initialised.
 
 const IGNORE = ["escapeAddonURI", "shouldAutoUpdate", "getStartupChanges",
                 "addTypeListener", "removeTypeListener",
                 "addAddonListener", "removeAddonListener",
                 "addInstallListener", "removeInstallListener",
                 "addManagerListener", "removeManagerListener",
-                "mapURIToAddonID"];
+                "mapURIToAddonID", "shutdown"];
 
 const IGNORE_PRIVATE = ["AddonAuthor", "AddonCompatibilityOverride",
                         "AddonScreenshot", "AddonType", "startup", "shutdown",
                         "registerProvider", "unregisterProvider",
                         "addStartupChange", "removeStartupChange",
                         "recordTimestamp", "recordSimpleMeasure",
                         "recordException", "getSimpleMeasures", "simpleTimer",
                         "setTelemetryDetails", "getTelemetryDetails",
                         "callNoUpdateListeners"];
 
 function test_functions() {
   for (let prop in AddonManager) {
-    if (typeof AddonManager[prop] != "function")
+    if (IGNORE.indexOf(prop) != -1)
       continue;
-    if (IGNORE.indexOf(prop) != -1)
+    if (typeof AddonManager[prop] != "function")
       continue;
 
     try {
       do_print("AddonManager." + prop);
       AddonManager[prop]();
       do_throw(prop + " did not throw an exception");
     }
     catch (e) {