toolkit/components/crashmonitor/CrashMonitor.jsm

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

     1 /* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 /**
     7  * Crash Monitor
     8  *
     9  * Monitors execution of a program to detect possible crashes. After
    10  * program termination, the monitor can be queried during the next run
    11  * to determine whether the last run exited cleanly or not.
    12  *
    13  * The monitoring is done by registering and listening for special
    14  * notifications, or checkpoints, known to be sent by the monitored
    15  * program as different stages in the execution are reached. As they
    16  * are observed, these notifications are written asynchronously to a
    17  * checkpoint file.
    18  *
    19  * During next program startup the crash monitor reads the checkpoint
    20  * file from the last session. If notifications are missing, a crash
    21  * has likely happened. By inspecting the notifications present, it is
    22  * possible to determine what stages were reached in the program
    23  * before the crash.
    24  *
    25  * Note that since the file is written asynchronously it is possible
    26  * that a received notification is lost if the program crashes right
    27  * after a checkpoint, but before crash monitor has been able to write
    28  * it to disk. Thus, while the presence of a notification in the
    29  * checkpoint file tells us that the corresponding stage was reached
    30  * during the last run, the absence of a notification after a crash
    31  * does not necessarily tell us that the checkpoint wasn't reached.
    32  */
    34 this.EXPORTED_SYMBOLS = [ "CrashMonitor" ];
    36 const Cu = Components.utils;
    37 const Cr = Components.results;
    39 Cu.import("resource://gre/modules/Services.jsm");
    40 Cu.import("resource://gre/modules/osfile.jsm");
    41 Cu.import("resource://gre/modules/Promise.jsm");
    42 Cu.import("resource://gre/modules/Task.jsm");
    43 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
    45 const NOTIFICATIONS = [
    46   "final-ui-startup",
    47   "sessionstore-windows-restored",
    48   "quit-application-granted",
    49   "quit-application",
    50   "profile-change-net-teardown",
    51   "profile-change-teardown",
    52   "profile-before-change",
    53   "sessionstore-final-state-write-complete"
    54 ];
    56 let CrashMonitorInternal = {
    58   /**
    59    * Notifications received during the current session.
    60    *
    61    * Object where a property with a value of |true| means that the
    62    * notification of the same name has been received at least once by
    63    * the CrashMonitor during this session. Notifications that have not
    64    * yet been received are not present as properties. |NOTIFICATIONS|
    65    * lists the notifications tracked by the CrashMonitor.
    66    */
    67   checkpoints: {},
    69   /**
    70    * Notifications received during previous session.
    71    *
    72    * Available after |loadPreviousCheckpoints|. Promise which resolves
    73    * to an object containing a set of properties, where a property
    74    * with a value of |true| means that the notification with the same
    75    * name as the property name was received at least once last
    76    * session.
    77    */
    78   previousCheckpoints: null,
    80   /* Deferred for AsyncShutdown blocker */
    81   profileBeforeChangeDeferred: Promise.defer(),
    83   /**
    84    * Path to checkpoint file.
    85    *
    86    * Each time a new notification is received, this file is written to
    87    * disc to reflect the information in |checkpoints|. Although Firefox for
    88    * Desktop and Metro share the same profile, they need to keep record of
    89    * crashes separately.
    90    */
    91   path: (Services.metro && Services.metro.immersive) ?
    92     OS.Path.join(OS.Constants.Path.profileDir, "metro", "sessionCheckpoints.json"):
    93     OS.Path.join(OS.Constants.Path.profileDir, "sessionCheckpoints.json"),
    95   /**
    96    * Load checkpoints from previous session asynchronously.
    97    *
    98    * @return {Promise} A promise that resolves/rejects once loading is complete
    99    */
   100   loadPreviousCheckpoints: function () {
   101     this.previousCheckpoints = Task.spawn(function*() {
   102       let data;
   103       try {
   104         data = yield OS.File.read(CrashMonitorInternal.path, { encoding: "utf-8" });
   105       } catch (ex if ex instanceof OS.File.Error) {
   106         if (!ex.becauseNoSuchFile) {
   107           Cu.reportError("Error while loading crash monitor data: " + ex.toString());
   108         }
   110         return null;
   111       }
   113       let notifications;
   114       try {
   115         notifications = JSON.parse(data);
   116       } catch (ex) {
   117         Cu.reportError("Error while parsing crash monitor data: " + ex);
   118         return null;
   119       }
   121       return Object.freeze(notifications);
   122     });
   124     return this.previousCheckpoints;
   125   }
   126 };
   128 this.CrashMonitor = {
   130   /**
   131    * Notifications received during previous session.
   132    *
   133    * Return object containing the set of notifications received last
   134    * session as keys with values set to |true|.
   135    *
   136    * @return {Promise} A promise resolving to previous checkpoints
   137    */
   138   get previousCheckpoints() {
   139     if (!CrashMonitorInternal.initialized) {
   140       throw new Error("CrashMonitor must be initialized before getting previous checkpoints");
   141     }
   143     return CrashMonitorInternal.previousCheckpoints
   144   },
   146   /**
   147    * Initialize CrashMonitor.
   148    *
   149    * Should only be called from the CrashMonitor XPCOM component.
   150    *
   151    * @return {Promise}
   152    */
   153   init: function () {
   154     if (CrashMonitorInternal.initialized) {
   155       throw new Error("CrashMonitor.init() must only be called once!");
   156     }
   158     let promise = CrashMonitorInternal.loadPreviousCheckpoints();
   159     // Add "profile-after-change" to checkpoint as this method is
   160     // called after receiving it
   161     CrashMonitorInternal.checkpoints["profile-after-change"] = true;
   163     NOTIFICATIONS.forEach(function (aTopic) {
   164       Services.obs.addObserver(this, aTopic, false);
   165     }, this);
   167     // Add shutdown blocker for profile-before-change
   168     AsyncShutdown.profileBeforeChange.addBlocker(
   169       "CrashMonitor: Writing notifications to file after receiving profile-before-change",
   170       CrashMonitorInternal.profileBeforeChangeDeferred.promise
   171     );
   173     CrashMonitorInternal.initialized = true;
   174     if (Services.metro && Services.metro.immersive) {
   175       OS.File.makeDir(OS.Path.join(OS.Constants.Path.profileDir, "metro"));
   176     }
   177     return promise;
   178   },
   180   /**
   181    * Handle registered notifications.
   182    *
   183    * Update checkpoint file for every new notification received.
   184    */
   185   observe: function (aSubject, aTopic, aData) {
   186     if (!(aTopic in CrashMonitorInternal.checkpoints)) {
   187       // If this is the first time this notification is received,
   188       // remember it and write it to file
   189       CrashMonitorInternal.checkpoints[aTopic] = true;
   190       Task.spawn(function() {
   191         try {
   192           let data = JSON.stringify(CrashMonitorInternal.checkpoints);
   194           /* Write to the checkpoint file asynchronously, off the main
   195            * thread, for performance reasons. Note that this means
   196            * that there's not a 100% guarantee that the file will be
   197            * written by the time the notification completes. The
   198            * exception is profile-before-change which has a shutdown
   199            * blocker. */
   200           yield OS.File.writeAtomic(
   201             CrashMonitorInternal.path,
   202             data, {tmpPath: CrashMonitorInternal.path + ".tmp"});
   204         } finally {
   205           // Resolve promise for blocker
   206           if (aTopic == "profile-before-change") {
   207             CrashMonitorInternal.profileBeforeChangeDeferred.resolve();
   208           }
   209         }
   210       });
   211     }
   213     if (NOTIFICATIONS.every(elem => elem in CrashMonitorInternal.checkpoints)) {
   214       // All notifications received, unregister observers
   215       NOTIFICATIONS.forEach(function (aTopic) {
   216         Services.obs.removeObserver(this, aTopic);
   217       }, this);
   218     }
   219   }
   220 };
   221 Object.freeze(this.CrashMonitor);

mercurial