Bug 1385396 - Cache early setExperimentActive calls r?:gfritzsche draft
authorDoug Thayer <dothayer@mozilla.com>
Wed, 02 Aug 2017 15:10:34 -0700
changeset 620560 92e7e5669a2526307f68be8810711b0259d6936f
parent 615935 388d81ed93fa640f91d155f36254667c734157cf
child 640741 cfd191131b6597445610480b0b9d5430dff00267
push id72084
push userbmo:dothayer@mozilla.com
push dateThu, 03 Aug 2017 15:40:53 +0000
bugs1385396
milestone56.0a1
Bug 1385396 - Cache early setExperimentActive calls r?:gfritzsche Calling setExperimentActive too early during startup can change the order of some initialization. setExperimentActive probably shouldn't have this kind of effect, so simply cache early calls to it until gGlobalEnvironment has been initialized through other functions. Additionally, I am speculatively including work to ensure that setExperimentInactive and getActiveExperiments have the same behavior, while remaining correct by working from the same cache that setExperimentActive uses. MozReview-Commit-ID: IlzT1J0o6gK
toolkit/components/telemetry/TelemetryEnvironment.jsm
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -48,16 +48,21 @@ const MAX_EXPERIMENT_TYPE_LENGTH = 20;
 
 /**
  * This is a policy object used to override behavior for testing.
  */
 var Policy = {
   now: () => new Date(),
 };
 
+// This is used to buffer calls to setExperimentActive and friends, so that we
+// don't prematurely initialize our environment if it is called early during
+// startup.
+var gActiveExperimentStartupBuffer = new Map();
+
 var gGlobalEnvironment;
 function getGlobal() {
   if (!gGlobalEnvironment) {
     gGlobalEnvironment = new EnvironmentCache();
   }
   return gGlobalEnvironment;
 }
 
@@ -88,41 +93,57 @@ this.TelemetryEnvironment = {
    * This triggers a new subsession, subject to throttling.
    *
    * @param {String} id The id of the active experiment.
    * @param {String} branch The experiment branch.
    * @param {Object} [options] Optional object with options.
    * @param {String} [options.type=false] The specific experiment type.
    */
   setExperimentActive(id, branch, options = {}) {
-    return getGlobal().setExperimentActive(id, branch, options);
+    if (gGlobalEnvironment) {
+      gGlobalEnvironment.setExperimentActive(id, branch, options);
+    } else {
+      gActiveExperimentStartupBuffer.set(id, {branch, options});
+    }
   },
 
   /**
    * Remove an experiment annotation from the environment.
    * If the annotation exists, a new subsession will triggered.
    *
    * @param {String} id The id of the active experiment.
    */
   setExperimentInactive(id) {
-    return getGlobal().setExperimentInactive(id);
+    if (gGlobalEnvironment) {
+      gGlobalEnvironment.setExperimentInactive(id);
+    } else {
+      gActiveExperimentStartupBuffer.delete(id);
+    }
   },
 
   /**
    * Returns an object containing the data for the active experiments.
    *
    * The returned object is of the format:
    *
    * {
    *   "<experiment id>": { branch: "<branch>" },
    *   // …
    * }
    */
   getActiveExperiments() {
-    return getGlobal().getActiveExperiments();
+    if (gGlobalEnvironment) {
+      return gGlobalEnvironment.getActiveExperiments();
+    }
+
+    const result = {};
+    for (const [id, {branch}] of gActiveExperimentStartupBuffer.entries()) {
+      result[id] = branch;
+    }
+    return result;
   },
 
   shutdown() {
     return getGlobal().shutdown();
   },
 
   // Policy to use when saving preferences. Exported for using them in tests.
   RECORD_PREF_STATE: 1, // Don't record the preference value
@@ -841,16 +862,21 @@ function EnvironmentCache() {
   p = [ this._addonBuilder.init() ];
 
   this._currentEnvironment.profile = {};
   p.push(this._updateProfile());
   if (AppConstants.MOZ_BUILD_APP == "browser") {
     p.push(this._updateAttribution());
   }
 
+  for (const [id, {branch, options}] of gActiveExperimentStartupBuffer.entries()) {
+    this.setExperimentActive(id, branch, options);
+  }
+  gActiveExperimentStartupBuffer = null;
+
   let setup = () => {
     this._initTask = null;
     this._startWatchingPrefs();
     this._addonBuilder.watchForChanges();
     this._updateGraphicsFeatures();
     return this.currentEnvironment;
   };