browser/components/sessionstore/src/SessionFile.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 this.EXPORTED_SYMBOLS = ["SessionFile"];
michael@0 8
michael@0 9 /**
michael@0 10 * Implementation of all the disk I/O required by the session store.
michael@0 11 * This is a private API, meant to be used only by the session store.
michael@0 12 * It will change. Do not use it for any other purpose.
michael@0 13 *
michael@0 14 * Note that this module implicitly depends on one of two things:
michael@0 15 * 1. either the asynchronous file I/O system enqueues its requests
michael@0 16 * and never attempts to simultaneously execute two I/O requests on
michael@0 17 * the files used by this module from two distinct threads; or
michael@0 18 * 2. the clients of this API are well-behaved and do not place
michael@0 19 * concurrent requests to the files used by this module.
michael@0 20 *
michael@0 21 * Otherwise, we could encounter bugs, especially under Windows,
michael@0 22 * e.g. if a request attempts to write sessionstore.js while
michael@0 23 * another attempts to copy that file.
michael@0 24 *
michael@0 25 * This implementation uses OS.File, which guarantees property 1.
michael@0 26 */
michael@0 27
michael@0 28 const Cu = Components.utils;
michael@0 29 const Cc = Components.classes;
michael@0 30 const Ci = Components.interfaces;
michael@0 31 const Cr = Components.results;
michael@0 32
michael@0 33 Cu.import("resource://gre/modules/Services.jsm");
michael@0 34 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 35 Cu.import("resource://gre/modules/osfile.jsm");
michael@0 36 Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
michael@0 37 Cu.import("resource://gre/modules/Promise.jsm");
michael@0 38 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
michael@0 39
michael@0 40 XPCOMUtils.defineLazyModuleGetter(this, "console",
michael@0 41 "resource://gre/modules/devtools/Console.jsm");
michael@0 42 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
michael@0 43 "resource://gre/modules/TelemetryStopwatch.jsm");
michael@0 44 XPCOMUtils.defineLazyModuleGetter(this, "Task",
michael@0 45 "resource://gre/modules/Task.jsm");
michael@0 46 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
michael@0 47 "@mozilla.org/base/telemetry;1", "nsITelemetry");
michael@0 48
michael@0 49 this.SessionFile = {
michael@0 50 /**
michael@0 51 * Read the contents of the session file, asynchronously.
michael@0 52 */
michael@0 53 read: function () {
michael@0 54 return SessionFileInternal.read();
michael@0 55 },
michael@0 56 /**
michael@0 57 * Write the contents of the session file, asynchronously.
michael@0 58 */
michael@0 59 write: function (aData) {
michael@0 60 return SessionFileInternal.write(aData);
michael@0 61 },
michael@0 62 /**
michael@0 63 * Gather telemetry statistics.
michael@0 64 *
michael@0 65 *
michael@0 66 * Most of the work is done off the main thread but there is a main
michael@0 67 * thread cost involved to send data to the worker thread. This method
michael@0 68 * should therefore be called only when we know that it will not disrupt
michael@0 69 * the user's experience, e.g. on idle-daily.
michael@0 70 *
michael@0 71 * @return {Promise}
michael@0 72 * @promise {object} An object holding all the information to be submitted
michael@0 73 * to Telemetry.
michael@0 74 */
michael@0 75 gatherTelemetry: function(aData) {
michael@0 76 return SessionFileInternal.gatherTelemetry(aData);
michael@0 77 },
michael@0 78 /**
michael@0 79 * Create a backup copy, asynchronously.
michael@0 80 * This is designed to perform backup on upgrade.
michael@0 81 */
michael@0 82 createBackupCopy: function (ext) {
michael@0 83 return SessionFileInternal.createBackupCopy(ext);
michael@0 84 },
michael@0 85 /**
michael@0 86 * Remove a backup copy, asynchronously.
michael@0 87 * This is designed to clean up a backup on upgrade.
michael@0 88 */
michael@0 89 removeBackupCopy: function (ext) {
michael@0 90 return SessionFileInternal.removeBackupCopy(ext);
michael@0 91 },
michael@0 92 /**
michael@0 93 * Wipe the contents of the session file, asynchronously.
michael@0 94 */
michael@0 95 wipe: function () {
michael@0 96 SessionFileInternal.wipe();
michael@0 97 }
michael@0 98 };
michael@0 99
michael@0 100 Object.freeze(SessionFile);
michael@0 101
michael@0 102 /**
michael@0 103 * Utilities for dealing with promises and Task.jsm
michael@0 104 */
michael@0 105 let SessionFileInternal = {
michael@0 106 /**
michael@0 107 * The path to sessionstore.js
michael@0 108 */
michael@0 109 path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
michael@0 110
michael@0 111 /**
michael@0 112 * The path to sessionstore.bak
michael@0 113 */
michael@0 114 backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
michael@0 115
michael@0 116 /**
michael@0 117 * The promise returned by the latest call to |write|.
michael@0 118 * We use it to ensure that AsyncShutdown.profileBeforeChange cannot
michael@0 119 * interrupt a call to |write|.
michael@0 120 */
michael@0 121 _latestWrite: null,
michael@0 122
michael@0 123 /**
michael@0 124 * |true| once we have decided to stop receiving write instructiosn
michael@0 125 */
michael@0 126 _isClosed: false,
michael@0 127
michael@0 128 read: function () {
michael@0 129 // We must initialize the worker during startup so it will be ready to
michael@0 130 // perform the final write. If shutdown happens soon after startup and
michael@0 131 // the worker has not started yet we may not write.
michael@0 132 // See Bug 964531.
michael@0 133 SessionWorker.post("init");
michael@0 134
michael@0 135 return Task.spawn(function*() {
michael@0 136 for (let filename of [this.path, this.backupPath]) {
michael@0 137 try {
michael@0 138 let startMs = Date.now();
michael@0 139
michael@0 140 let data = yield OS.File.read(filename, { encoding: "utf-8" });
michael@0 141
michael@0 142 Telemetry.getHistogramById("FX_SESSION_RESTORE_READ_FILE_MS")
michael@0 143 .add(Date.now() - startMs);
michael@0 144
michael@0 145 return data;
michael@0 146 } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
michael@0 147 // Ignore exceptions about non-existent files.
michael@0 148 }
michael@0 149 }
michael@0 150
michael@0 151 return "";
michael@0 152 }.bind(this));
michael@0 153 },
michael@0 154
michael@0 155 gatherTelemetry: function(aStateString) {
michael@0 156 return Task.spawn(function() {
michael@0 157 let msg = yield SessionWorker.post("gatherTelemetry", [aStateString]);
michael@0 158 this._recordTelemetry(msg.telemetry);
michael@0 159 throw new Task.Result(msg.telemetry);
michael@0 160 }.bind(this));
michael@0 161 },
michael@0 162
michael@0 163 write: function (aData) {
michael@0 164 if (this._isClosed) {
michael@0 165 return Promise.reject(new Error("SessionFile is closed"));
michael@0 166 }
michael@0 167 let refObj = {};
michael@0 168
michael@0 169 let isFinalWrite = false;
michael@0 170 if (Services.startup.shuttingDown) {
michael@0 171 // If shutdown has started, we will want to stop receiving
michael@0 172 // write instructions.
michael@0 173 isFinalWrite = this._isClosed = true;
michael@0 174 }
michael@0 175
michael@0 176 return this._latestWrite = Task.spawn(function task() {
michael@0 177 TelemetryStopwatch.start("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
michael@0 178
michael@0 179 try {
michael@0 180 let promise = SessionWorker.post("write", [aData]);
michael@0 181 // At this point, we measure how long we stop the main thread
michael@0 182 TelemetryStopwatch.finish("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
michael@0 183
michael@0 184 // Now wait for the result and record how long the write took
michael@0 185 let msg = yield promise;
michael@0 186 this._recordTelemetry(msg.telemetry);
michael@0 187 } catch (ex) {
michael@0 188 TelemetryStopwatch.cancel("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
michael@0 189 console.error("Could not write session state file ", this.path, ex);
michael@0 190 }
michael@0 191
michael@0 192 if (isFinalWrite) {
michael@0 193 Services.obs.notifyObservers(null, "sessionstore-final-state-write-complete", "");
michael@0 194 }
michael@0 195 }.bind(this));
michael@0 196 },
michael@0 197
michael@0 198 createBackupCopy: function (ext) {
michael@0 199 return SessionWorker.post("createBackupCopy", [ext]);
michael@0 200 },
michael@0 201
michael@0 202 removeBackupCopy: function (ext) {
michael@0 203 return SessionWorker.post("removeBackupCopy", [ext]);
michael@0 204 },
michael@0 205
michael@0 206 wipe: function () {
michael@0 207 SessionWorker.post("wipe");
michael@0 208 },
michael@0 209
michael@0 210 _recordTelemetry: function(telemetry) {
michael@0 211 for (let id of Object.keys(telemetry)){
michael@0 212 let value = telemetry[id];
michael@0 213 let samples = [];
michael@0 214 if (Array.isArray(value)) {
michael@0 215 samples.push(...value);
michael@0 216 } else {
michael@0 217 samples.push(value);
michael@0 218 }
michael@0 219 let histogram = Telemetry.getHistogramById(id);
michael@0 220 for (let sample of samples) {
michael@0 221 histogram.add(sample);
michael@0 222 }
michael@0 223 }
michael@0 224 }
michael@0 225 };
michael@0 226
michael@0 227 // Interface to a dedicated thread handling I/O
michael@0 228 let SessionWorker = (function () {
michael@0 229 let worker = new PromiseWorker("resource:///modules/sessionstore/SessionWorker.js",
michael@0 230 OS.Shared.LOG.bind("SessionWorker"));
michael@0 231 return {
michael@0 232 post: function post(...args) {
michael@0 233 let promise = worker.post.apply(worker, args);
michael@0 234 return promise.then(
michael@0 235 null,
michael@0 236 function onError(error) {
michael@0 237 // Decode any serialized error
michael@0 238 if (error instanceof PromiseWorker.WorkerError) {
michael@0 239 throw OS.File.Error.fromMsg(error.data);
michael@0 240 }
michael@0 241 // Extract something meaningful from ErrorEvent
michael@0 242 if (error instanceof ErrorEvent) {
michael@0 243 throw new Error(error.message, error.filename, error.lineno);
michael@0 244 }
michael@0 245 throw error;
michael@0 246 }
michael@0 247 );
michael@0 248 }
michael@0 249 };
michael@0 250 })();
michael@0 251
michael@0 252 // Ensure that we can write sessionstore.js cleanly before the profile
michael@0 253 // becomes unaccessible.
michael@0 254 AsyncShutdown.profileBeforeChange.addBlocker(
michael@0 255 "SessionFile: Finish writing the latest sessionstore.js",
michael@0 256 function() {
michael@0 257 return SessionFileInternal._latestWrite;
michael@0 258 });

mercurial