Bug 1214408 - Telemetry on SessionStore:update OOM;r=ttaubert
--- 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)"
},