Bug 1190627 - Part 5 - Do regular session data backups. r=margaret draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Sun, 29 May 2016 21:17:15 +0200
changeset 376095 c09d94fa75cb2cb35a42f4f54ee316f9cf2b94b3
parent 376094 8de568dd9e25fb3cae4a0773978f7074cbf4584d
child 376096 3e26dae632dec4b9288fa12925a9fec8eff5d5f9
push id20502
push usermozilla@buttercookie.de
push dateTue, 07 Jun 2016 11:58:37 +0000
reviewersmargaret
bugs1190627
milestone50.0a1
Bug 1190627 - Part 5 - Do regular session data backups. r=margaret We now do a backup copy of the session store data at regular intervals to guard against interrupted write operations damaging the main session data file. We don't use writeAtomic()'s backupTo option, because that one works by first moving the old data to the backup file before attempting to write the new data, which might still leave us vulnerable against data loss if Firefox crashes or is otherwise forcibly terminated at precisely that moment. MozReview-Commit-ID: Cv52rmlfmfh
mobile/android/app/mobile.js
mobile/android/components/SessionStore.js
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -136,16 +136,17 @@ pref("browser.sessionhistory.max_total_v
 pref("browser.sessionhistory.max_entries", 50);
 pref("browser.sessionhistory.contentViewerTimeout", 360);
 pref("browser.sessionhistory.bfcacheIgnoreMemoryPressure", false);
 
 /* session store */
 pref("browser.sessionstore.resume_session_once", false);
 pref("browser.sessionstore.resume_from_crash", true);
 pref("browser.sessionstore.interval", 10000); // milliseconds
+pref("browser.sessionstore.backupInterval", 120000); // milliseconds -> 2 minutes
 pref("browser.sessionstore.max_tabs_undo", 5);
 pref("browser.sessionstore.max_resumed_crashes", 1);
 pref("browser.sessionstore.privacy_level", 0); // saving data: 0 = all, 1 = unencrypted sites, 2 = never
 pref("browser.sessionstore.debug_logging", false);
 
 /* these should help performance */
 pref("mozilla.widget.force-24bpp", true);
 pref("mozilla.widget.use-buffer-pixmap", true);
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -56,24 +56,30 @@ SessionStore.prototype = {
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISessionStore,
                                          Ci.nsIDOMEventListener,
                                          Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
   _windows: {},
   _lastSaveTime: 0,
+  _lastBackupTime: 0,
   _interval: 10000,
+  _backupInterval: 120000, // 2 minutes
   _minSaveDelay: MINIMUM_SAVE_DELAY,
   _maxTabsUndo: 5,
   _scrollSavePending: null,
   _pendingWrite: 0,
   _writeInProgress: false,
   _deferredWrite: {},
 
+  // We only want to start doing backups if we've successfully
+  // written the session data at least once.
+  _sessionDataIsGood: false,
+
   // The index where the most recently closed tab was in the tabs array
   // when it was closed.
   _lastClosedTabIndex: -1,
 
   // Whether or not to send notifications for changes to the closed tabs.
   _notifyClosedTabs: false,
 
   init: function ss_init() {
@@ -87,31 +93,34 @@ SessionStore.prototype = {
     this._sessionFile.append("sessionstore.js"); // The main session store save file.
     this._sessionFileBackup.append("sessionstore.bak"); // A backup copy to guard against interrupted writes.
     this._sessionFilePrevious.append("sessionstore.old"); // The previous session's file, used for what used to be the "Tabs from last time".
     this._sessionFileTemp.append(this._sessionFile.leafName + ".tmp"); // Temporary file for writing changes to disk.
 
     this._loadState = STATE_STOPPED;
 
     this._interval = Services.prefs.getIntPref("browser.sessionstore.interval");
+    this._backupInterval = Services.prefs.getIntPref("browser.sessionstore.backupInterval");
     this._maxTabsUndo = Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo");
 
     // Copy changes in Gecko settings to their Java counterparts,
     // so the startup code can access them
     Services.prefs.addObserver(PREFS_RESTORE_FROM_CRASH, function() {
       SharedPreferences.forApp().setBoolPref(PREFS_RESTORE_FROM_CRASH,
         Services.prefs.getBoolPref(PREFS_RESTORE_FROM_CRASH));
     }, false);
     Services.prefs.addObserver(PREFS_MAX_CRASH_RESUMES, function() {
       SharedPreferences.forApp().setIntPref(PREFS_MAX_CRASH_RESUMES,
         Services.prefs.getIntPref(PREFS_MAX_CRASH_RESUMES));
     }, false);
   },
 
   _clearDisk: function ss_clearDisk() {
+    this._sessionDataIsGood = false;
+
     OS.File.remove(this._sessionFile.path);
     OS.File.remove(this._sessionFileBackup.path);
     OS.File.remove(this._sessionFilePrevious.path);
     OS.File.remove(this._sessionFileTemp.path);
   },
 
   observe: function ss_observe(aSubject, aTopic, aData) {
     let self = this;
@@ -814,16 +823,28 @@ SessionStore.prototype = {
       // All _saveState() calls with an async write in progress will be coalesced
       // into one deferred operation, so sync writes take precedence.
       this._deferredWrite.syncWrite = this._deferredWrite.syncWrite || !aAsync;
       log("_saveState() deferring write, asyncWrite = " + !this._deferredWrite.syncWrite);
       return;
     }
     this._writeInProgress = true;
 
+    // Periodically save a "known good" copy of the session store data.
+    if (Date.now() - this._lastBackupTime > this._backupInterval && this._sessionDataIsGood &&
+        this._sessionFile.exists()) {
+      if (this._sessionFileBackup.exists()) {
+        this._sessionFileBackup.remove(false);
+      }
+
+      log("_saveState() backing up session data");
+      this._sessionFile.copyTo(null, this._sessionFileBackup.leafName);
+      this._lastBackupTime = Date.now();
+    }
+
     let data = this._getCurrentState();
     let normalData = { windows: [] };
     let privateData = { windows: [] };
     log("_saveState() current state collected");
 
     for (let winIndex = 0; winIndex < data.windows.length; ++winIndex) {
       let win = data.windows[winIndex];
       let normalWin = {};
@@ -973,16 +994,17 @@ SessionStore.prototype = {
       log("_writeFile() _write() returned, _pendingWrite = " + this._pendingWrite);
 
       // We don't use a stopwatch here since the calls are async and stopwatches can only manage
       // a single timer per histogram.
       Services.telemetry.getHistogramById("FX_SESSION_RESTORE_WRITE_FILE_MS").add(Math.round(stopWriteMs - startWriteMs));
       Services.obs.notifyObservers(null, "sessionstore-state-write-complete", "");
 
       this._writeInProgress = false;
+      this._sessionDataIsGood = true;
 
       // A call to _saveState() was deferred while an async write
       // was in progress, so we execute it now.
       if (this._deferredWrite.pending) {
         asyncWrite = !this._deferredWrite.syncWrite;
         delete this._deferredWrite.syncWrite;
         delete this._deferredWrite.pending;
         log("_writeFile() executing deferred write");