Bug 914581 - [Session Restore] Plug into AsyncShutdown. r=ttaubert, a=lsblakk
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Tue, 17 Sep 2013 10:26:56 +0200
changeset 160422 fa50f923db953d4ac52a79c1ce08c071feac2df3
parent 160421 9c9acece841f8b31475e4dad524608fec53086bb
child 160423 59d9eff1d3ed3a8f4168f277e6a46f2a261773c9
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersttaubert, lsblakk
bugs914581
milestone26.0a2
Bug 914581 - [Session Restore] Plug into AsyncShutdown. r=ttaubert, a=lsblakk
browser/components/sessionstore/src/_SessionFile.jsm
--- a/browser/components/sessionstore/src/_SessionFile.jsm
+++ b/browser/components/sessionstore/src/_SessionFile.jsm
@@ -29,16 +29,17 @@ const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/AsyncShutdown.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
   "resource://gre/modules/TelemetryStopwatch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
@@ -144,16 +145,28 @@ let SessionFileInternal = {
   path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
 
   /**
    * The path to sessionstore.bak
    */
   backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
 
   /**
+   * The promise returned by the latest call to |write|.
+   * We use it to ensure that AsyncShutdown.profileBeforeChange cannot
+   * interrupt a call to |write|.
+   */
+  _latestWrite: null,
+
+  /**
+   * |true| once we have decided to stop receiving write instructiosn
+   */
+  _isClosed: false,
+
+  /**
    * Utility function to safely read a file synchronously.
    * @param aPath
    *        A path to read the file from.
    * @returns string if successful, undefined otherwise.
    */
   readAuxSync: function (aPath) {
     let text;
     try {
@@ -205,33 +218,45 @@ let SessionFileInternal = {
   read: function () {
     return SessionWorker.post("read").then(msg => {
       this._recordTelemetry(msg.telemetry);
       return msg.ok;
     });
   },
 
   write: function (aData) {
+    if (this._isClosed) {
+      return Promise.reject(new Error("_SessionFile is closed"));
+    }
     let refObj = {};
-    return TaskUtils.spawn(function task() {
+    return this._latestWrite = TaskUtils.spawn(function task() {
       TelemetryStopwatch.start("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
 
       try {
         let promise = SessionWorker.post("write", [aData]);
         // At this point, we measure how long we stop the main thread
         TelemetryStopwatch.finish("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
 
         // Now wait for the result and record how long the write took
         let msg = yield promise;
         this._recordTelemetry(msg.telemetry);
       } catch (ex) {
         TelemetryStopwatch.cancel("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
         Cu.reportError("Could not write session state file " + this.path
                        + ": " + ex);
       }
+      // At this stage, we are done writing. If shutdown has started,
+      // we will want to stop receiving write instructions.
+      if (Services.startup.shuttingDown) {
+        this._isClosed = true;
+      }
+      // In rare cases, we may already have other writes pending,
+      // which we need to flush before shutdown proceeds. AsyncShutdown
+      // uses _latestWrite to determine what needs to be flushed during
+      // shutdown.
     }.bind(this));
   },
 
   writeLoadStateOnceAfterStartup: function (aLoadState) {
     SessionWorker.post("writeLoadStateOnceAfterStartup", [aLoadState]).then(msg => {
       this._recordTelemetry(msg.telemetry);
       return msg;
     });
@@ -272,8 +297,16 @@ let SessionWorker = (function () {
           } else {
             throw error;
           }
         }
       );
     }
   };
 })();
+
+// Ensure that we can write sessionstore.js cleanly before the profile
+// becomes unaccessible.
+AsyncShutdown.profileBeforeChange.addBlocker(
+  "SessionFile: Finish writing the latest sessionstore.js",
+  function() {
+    return _SessionFile._latestWrite;
+  });