Bug 1253204 - Don't abort shutdown on plugins sanitization;r?mak draft
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Thu, 03 Mar 2016 19:46:51 +0100
changeset 336594 ea86ef45572801add1a3107c4599fa97490fbd4b
parent 335982 eb25b90a05c194bfd4f498ff3ffee7440f85f1cd
child 515463 05d509752d48c5c35c33793f98c2500bd4a07ecb
push id12136
push userdteller@mozilla.com
push dateThu, 03 Mar 2016 21:15:59 +0000
reviewersmak
bugs1253204, 1249333
milestone47.0a1
Bug 1253204 - Don't abort shutdown on plugins sanitization;r?mak While investigating shutdown hangs/crashes, we determined that one of the sources was plugin cookie sanitization, which is sometimes insanely long. As a workaround, and until we have solved this in bug 1249333, this patch inroduces soft timeouts that will not hang/crash Firefox until sanitization of plugin cookies is complete. MozReview-Commit-ID: Bn6Jsq18NpF
browser/base/content/sanitize.js
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -224,77 +224,124 @@ Sanitizer.prototype = {
         TelemetryStopwatch.finish("FX_SANITIZE_CACHE", refObj);
         if (seenException) {
           throw seenException;
         }
       })
     },
 
     cookies: {
-      clear: Task.async(function* (range) {
+      clear: function(range) {
         let seenException;
-        let yieldCounter = 0;
         let refObj = {};
         TelemetryStopwatch.start("FX_SANITIZE_COOKIES", refObj);
 
-        // Clear cookies.
-        TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
-        try {
-          let cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
-                                    .getService(Ci.nsICookieManager);
-          if (range) {
-            // Iterate through the cookies and delete any created after our cutoff.
-            let cookiesEnum = cookieMgr.enumerator;
-            while (cookiesEnum.hasMoreElements()) {
-              let cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
+        // We have encountered issues with operations that take waaaay too long
+        // and cause shutdowns hangs/crashes. We have decided to introduce
+        // "softened" timeouts: if an operation takes too long, we report
+        // it as finished and proceed with shutdown.
+        //
+        // This mechanism gives some breathing space to operations that need
+        // several seconds and that have a chance of actually completing further
+        // down during shutdown.
+        //
+        // We still keep track of real durations, for Telemetry purposes.
+        //
+        // TODO: Once bug 1249333 has landed, we should be able to get rid of
+        // shoftened timeouts.
+
+        // Promises resolved once the operation is really complete.
+        let allReal = [];
+        // Promises resolved once the operation is either complete or has timed
+        // out.
+        let allSoftened = [];
 
-              if (cookie.creationTime > range[0]) {
-                // This cookie was created after our cutoff, clear it
-                cookieMgr.remove(cookie.host, cookie.name, cookie.path,
-                                 cookie.originAttributes, false);
+        const operations = [
+          // Clear cookies. Returns once complete.
+          {
+            histogram: "FX_SANITIZE_COOKIES_2",
+            closure: () => this.promiseClearWebCookies(range)
+          },
+          // Clear deviceIds. Returns *before* complete.
+          {
+            histogram: null,
+            closure: () => this.promiseClearDeviceIds(range)
+          },
+          // Clear plugin cookies. Returns once complete.
+          {
+            histogram: "FX_SANITIZE_PLUGINS",
+            closure: () => this.promiseClearPluginCookies(range)
+          }
+        ];
+        for (let {histogram: h, closure: c} of operations) {
+          // Avoid capture
+          let histogram = h;
+          let closure = c;
+          if (histogram) {
+            TelemetryStopwatch.start(histogram, refObj);
+          }
+          let real = Promise.resolve(closure()); // Be sure to promote to promise
+          real = real.catch(ex => {
+            seenException = ex;
+          }).then(() => {
+            if (histogram) {
+              TelemetryStopwatch.finish(histogram, refObj);
+            }
+          });
+          allReal.push(real);
+
+          let softened = Promise.race([real, new Promise(resolve => setTimeout(resolve, 10000 /* 10 seconds */))]);
+          allSoftened.push(softened);
+        }
 
-                if (++yieldCounter % YIELD_PERIOD == 0) {
-                  yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long
-                }
+        // Use the real durations to mark the total duration of FX_SANITIZE_COOKIES.
+        Promise.all(allReal).then(() => {
+          TelemetryStopwatch.finish("FX_SANITIZE_COOKIES", refObj);
+        });
+
+        // But resolve as soon as all softened promises are resolved.
+        return Promise.all(allSoftened).then(() => {
+          if (seenException) {
+            throw seenException;
+          }
+        });
+      },
+
+      promiseClearDeviceIds: function(range) {
+        // This operation returns immediately.
+        let mediaMgr = Components.classes["@mozilla.org/mediaManagerService;1"]
+                                 .getService(Ci.nsIMediaManagerService);
+        mediaMgr.sanitizeDeviceIds(range && range[0]);
+      },
+
+      promiseClearWebCookies: Task.async(function* (range) {
+        let yieldCounter = 0;
+        let cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
+                                  .getService(Ci.nsICookieManager);
+        if (range) {
+          // Iterate through the cookies and delete any created after our cutoff.
+          let cookiesEnum = cookieMgr.enumerator;
+          while (cookiesEnum.hasMoreElements()) {
+            let cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
+
+            if (cookie.creationTime > range[0]) {
+              // This cookie was created after our cutoff, clear it
+              cookieMgr.remove(cookie.host, cookie.name, cookie.path,
+                               cookie.originAttributes, false);
+
+              if (++yieldCounter % YIELD_PERIOD == 0) {
+                yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long
               }
             }
           }
-          else {
-            // Remove everything
-            cookieMgr.removeAll();
-            yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long
-          }
-        } catch (ex) {
-          seenException = ex;
-        } finally {
-          TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2", refObj);
         }
-
-        // Clear deviceIds. Done asynchronously (returns before complete).
-        try {
-          let mediaMgr = Components.classes["@mozilla.org/mediaManagerService;1"]
-                                   .getService(Ci.nsIMediaManagerService);
-          mediaMgr.sanitizeDeviceIds(range && range[0]);
-        } catch (ex) {
-          seenException = ex;
-        }
-
-        // Clear plugin data.
-        TelemetryStopwatch.start("FX_SANITIZE_PLUGINS", refObj);
-        try {
-          yield this.promiseClearPluginCookies(range);
-        } catch (ex) {
-          seenException = ex;
-        } finally {
-          TelemetryStopwatch.finish("FX_SANITIZE_PLUGINS", refObj);
-        }
-
-        TelemetryStopwatch.finish("FX_SANITIZE_COOKIES", refObj);
-        if (seenException) {
-          throw seenException;
+        else {
+          // Remove everything
+          cookieMgr.removeAll();
+          yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long
         }
       }),
 
       promiseClearPluginCookies: Task.async(function* (range) {
         const FLAG_CLEAR_ALL = Ci.nsIPluginHost.FLAG_CLEAR_ALL;
         let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
 
         // Determine age range in seconds. (-1 means clear all.) We don't know
@@ -327,17 +374,17 @@ Sanitizer.prototype = {
             } catch (ex) {
               // Ignore errors from plug-ins
               if (probe) {
                 TelemetryStopwatch.cancel(probe, refObj);
               }
             }
           }
         }
-      })
+      }),
     },
 
     offlineApps: {
       clear: Task.async(function* (range) {
         let refObj = {};
         TelemetryStopwatch.start("FX_SANITIZE_OFFLINEAPPS", refObj);
         try {
           Components.utils.import("resource:///modules/offlineAppCache.jsm");