Bug 1214408 - Telemetry on SessionStore:update OOM;r=ttaubert
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Fri, 16 Oct 2015 21:44:54 +0200
changeset 270748 8fea00e5b7784abc955e5426e9253e5ed094317c
parent 270747 7270d5b3283a501d4e1be6f2be1ad0e1ae2cb784
child 270749 dec44b4525eb56e6ef4d423f57a6193ddf099206
push id29622
push userkwierso@gmail.com
push dateMon, 02 Nov 2015 22:26:41 +0000
treeherdermozilla-central@6275cd9c71b7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersttaubert
bugs1214408
milestone45.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 1214408 - Telemetry on SessionStore:update OOM;r=ttaubert
browser/components/sessionstore/SessionStore.jsm
browser/components/sessionstore/content/content-sessionStore.js
browser/components/sessionstore/test/browser.ini
browser/components/sessionstore/test/browser_send_async_message_oom.js
toolkit/components/telemetry/Histograms.json
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -67,50 +67,62 @@ const MESSAGES = [
   // consider restoring another tab in the queue. The document has
   // been restored, and forms have been filled. We trigger
   // SSTabRestored at this time.
   "SessionStore:restoreTabContentComplete",
 
   // A crashed tab was revived by navigating to a different page. Remove its
   // browser from the list of crashed browsers to stop ignoring its messages.
   "SessionStore:crashedTabRevived",
+
+  // The content script encountered an error.
+  "SessionStore:error",
 ];
 
 // The list of messages we accept from <xul:browser>s that have no tab
 // assigned. Those are for example the ones that preload about:newtab pages.
 const NOTAB_MESSAGES = new Set([
   // For a description see above.
   "SessionStore:setupSyncHandler",
 
   // For a description see above.
   "SessionStore:crashedTabRevived",
 
   // For a description see above.
   "SessionStore:update",
+
+  // For a description see above.
+  "SessionStore:error",
 ]);
 
 // The list of messages we accept without an "epoch" parameter.
 // See getCurrentEpoch() and friends to find out what an "epoch" is.
 const NOEPOCH_MESSAGES = new Set([
   // For a description see above.
   "SessionStore:setupSyncHandler",
 
   // For a description see above.
   "SessionStore:crashedTabRevived",
+
+  // For a description see above.
+  "SessionStore:error",
 ]);
 
 // The list of messages we want to receive even during the short period after a
 // frame has been removed from the DOM and before its frame script has finished
 // unloading.
 const CLOSED_MESSAGES = new Set([
   // For a description see above.
   "SessionStore:crashedTabRevived",
 
   // For a description see above.
   "SessionStore:update",
+
+  // For a description see above.
+  "SessionStore:error",
 ]);
 
 // These are tab events that we listen to.
 const TAB_EVENTS = [
   "TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
   "TabUnpinned"
 ];
 
@@ -803,16 +815,19 @@ var SessionStoreInternal = {
         SessionStoreInternal._resetLocalTabRestoringState(tab);
         SessionStoreInternal.restoreNextTab();
 
         this._sendTabRestoredNotification(tab);
         break;
       case "SessionStore:crashedTabRevived":
         this._crashedBrowsers.delete(browser.permanentKey);
         break;
+      case "SessionStore:error":
+        this.reportInternalError(data);
+        break;
       default:
         throw new Error(`received unknown message '${aMessage.name}'`);
         break;
     }
   },
 
   /**
    * Record telemetry measurements stored in an object.
@@ -3843,16 +3858,29 @@ var SessionStoreInternal = {
 
   /**
    * Resets the epoch for a given <browser>. We need to this every time we
    * receive a hint that a new docShell has been loaded into the browser as
    * the frame script starts out with epoch=0.
    */
   resetEpoch(browser) {
     this._browserEpochs.delete(browser.permanentKey);
+  },
+
+  /**
+   * Handle an error report from a content process.
+   */
+  reportInternalError(data) {
+    // For the moment, we only report errors through Telemetry.
+    if (data.telemetry) {
+      for (let key of Object.keys(data.telemetry)) {
+        let histogram = Telemetry.getHistogramById(key);
+        histogram.add(data.telemetry[key]);
+      }
+    }
   }
 };
 
 /**
  * Priority queue that keeps track of a list of tabs to restore and returns
  * the tab we should restore next, based on priority rules. We decide between
  * pinned, visible and hidden tabs in that and FIFO order. Hidden tabs are only
  * restored with restore_hidden_tabs=true.
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -24,20 +24,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource:///modules/sessionstore/PageStyle.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
   "resource://gre/modules/ScrollPosition.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
   "resource:///modules/sessionstore/SessionHistory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
-                                   "@mozilla.org/childprocessmessagemanager;1",
-                                   "nsISyncMessageSender");
-
 Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this);
 var gFrameTree = new FrameTree(this);
 
 Cu.import("resource:///modules/sessionstore/ContentRestore.jsm", this);
 XPCOMUtils.defineLazyGetter(this, 'gContentRestore',
                             () => { return new ContentRestore(this) });
 
 // The current epoch.
@@ -687,22 +683,31 @@ var MessageQueue = {
       data[key] = this._data.get(key)();
     }
 
     durationMs = Date.now() - durationMs;
     let telemetry = {
       FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS: durationMs
     }
 
-    // Send all data to the parent process.
-    sendMessage("SessionStore:update", {
-      id: this._id, data, telemetry, flushID,
-      isFinal: options.isFinal || false,
-      epoch: gCurrentEpoch
-    });
+    try {
+      // Send all data to the parent process.
+      sendMessage("SessionStore:update", {
+        id: this._id, data, telemetry, flushID,
+        isFinal: options.isFinal || false,
+        epoch: gCurrentEpoch
+      });
+    } catch (ex if ex && ex.result == Cr.NS_ERROR_OUT_OF_MEMORY) {
+      let telemetry = {
+        FX_SESSION_RESTORE_SEND_UPDATE_CAUSED_OOM: 1
+      };
+      sendMessage("SessionStore:error", {
+        telemetry
+      });
+    }
 
     // Increase our unique message ID.
     this._id++;
   },
 
   /**
    * This function is used to make the message queue flush all queue data that
    * hasn't been sent to the parent process, yet.
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -206,8 +206,9 @@ skip-if = true
 [browser_464620_b.js]
 skip-if = true
 
 # Disabled on OS X:
 [browser_625016.js]
 skip-if = os == "mac"
 
 [browser_911547.js]
+[browser_send_async_message_oom.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_send_async_message_oom.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+const HISTOGRAM_NAME = "FX_SESSION_RESTORE_SEND_UPDATE_CAUSED_OOM";
+
+/**
+ * Test that an OOM in sendAsyncMessage in a framescript will be reported
+ * to Telemetry.
+ */
+
+add_task(function* init() {
+  Services.telemetry.canRecordExtended = true;
+});
+
+function frameScript() {
+  // Make send[A]syncMessage("SessionStore:update", ...) simulate OOM.
+  // Other operations are unaffected.
+  let mm = docShell.sameTypeRootTreeItem.
+    QueryInterface(Ci.nsIDocShell).
+    QueryInterface(Ci.nsIInterfaceRequestor).
+    getInterface(Ci.nsIContentFrameMessageManager);
+
+  let wrap = function(original) {
+    return function(name, ...args) {
+      if (name != "SessionStore:update") {
+        return original(name, ...args);
+      }
+      throw new Components.Exception("Simulated OOM", Cr.NS_ERROR_OUT_OF_MEMORY);
+    }
+  }
+
+  mm.sendAsyncMessage = wrap(mm.sendAsyncMessage);
+  mm.sendSyncMessage = wrap(mm.sendSyncMessage);
+}
+
+add_task(function*() {
+  // Capture original state.
+  let snapshot = Services.telemetry.getHistogramById(HISTOGRAM_NAME).snapshot();
+
+  // Open a browser, configure it to cause OOM.
+  let newTab = gBrowser.addTab("about:robots");
+  let browser = newTab.linkedBrowser;
+  yield ContentTask.spawn(browser, null, frameScript);
+
+
+  let promiseReported = new Promise(resolve => {
+    browser.messageManager.addMessageListener("SessionStore:error", resolve);
+  });
+
+  // Attempt to flush. This should fail.
+  let promiseFlushed = TabStateFlusher.flush(browser);
+  promiseFlushed.then(() => {throw new Error("Flush should have failed")});
+
+  // The frame script should report an error.
+  yield promiseReported;
+
+  // Give us some time to handle that error.
+  yield new Promise(resolve => setTimeout(resolve, 10));
+
+  // By now, Telemetry should have been updated.
+  let snapshot2 = Services.telemetry.getHistogramById(HISTOGRAM_NAME).snapshot();
+  gBrowser.removeTab(newTab);
+
+  Assert.ok(snapshot2.sum > snapshot.sum);
+});
+
+add_task(function* cleanup() {
+  Services.telemetry.canRecordExtended = false;
+});
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4347,16 +4347,22 @@
     "alert_emails": ["session-restore-telemetry-alerts@mozilla.com"],
     "expires_in_version": "default",
     "kind": "exponential",
     "high": "3000",
     "n_buckets": 10,
     "extended_statistics_ok": true,
     "description": "Session restore: Time spent blocking the main thread while restoring a window state (ms)"
   },
+  "FX_SESSION_RESTORE_SEND_UPDATE_CAUSED_OOM": {
+    "alert_emails": ["session-restore-telemetry-alerts@mozilla.com"],
+    "expires_in_version": "default",
+    "kind": "count",
+    "description": "Count of messages sent by SessionRestore from child frames to the parent and that cannot be transmitted as they eat up too much memory."
+  },
   "FX_TABLETMODE_PAGE_LOAD": {
     "expires_in_version": "47",
     "kind": "exponential",
     "high": 100000,
     "n_buckets": 30,
     "keyed": true,
     "description": "Number of toplevel location changes in tablet and desktop mode (only used on win10 where tablet mode is available)"
   },