Bug 1332024 - Add a method to explicitly finalize `JSONFile` objects. r?MattN draft
authorKit Cambridge <kit@yakshaving.ninja>
Wed, 18 Jan 2017 10:05:52 -0800
changeset 463224 bf6a164d93ea0e06f4e22081850f83682bbcd095
parent 461059 825c9cc5f51f60d3776556621a18118c5b3aa088
child 463225 f7f6708734a68603172ee8a33fd3dfc16d60fab8
push id41993
push userbmo:kit@mozilla.com
push dateWed, 18 Jan 2017 18:11:09 +0000
reviewersMattN
bugs1332024
milestone53.0a1
Bug 1332024 - Add a method to explicitly finalize `JSONFile` objects. r?MattN MozReview-Commit-ID: 4VjIyeMNXOJ
services/sync/tests/unit/test_telemetry.js
toolkit/modules/JSONFile.jsm
--- a/services/sync/tests/unit/test_telemetry.js
+++ b/services/sync/tests/unit/test_telemetry.js
@@ -58,16 +58,22 @@ function BogusEngine(service) {
 
 BogusEngine.prototype = Object.create(SteamEngine.prototype);
 
 async function cleanAndGo(engine, server) {
   Svc.Prefs.resetBranch("");
   Svc.Prefs.set("log.logger.engine.rotary", "Trace");
   Service.recordManager.clearCache();
   engine._tracker.clearChangedIDs();
+  // Bug 1325523: we create new engine and tracker instances for each test.
+  // Since the tracker persists in the background, it's possible for items
+  // tracked in one test to survive until the next test. This causes
+  // intermittent failures, so we explicitly finalize storage between tests
+  // to make sure all our changes are persisted.
+  await engine._tracker._storage.finalize();
   await promiseStopServer(server);
 }
 
 // Avoid addon manager complaining about not being initialized
 Service.engineManager.unregister("addons");
 
 add_identity_test(this, async function test_basic() {
   let helper = track_collections_helper();
@@ -431,16 +437,17 @@ add_task(async function test_initial_syn
       }
       greaterOrEqual(e.took, 1);
       ok(!!e.outgoing)
       equal(e.outgoing.length, 1);
       notEqual(e.outgoing[0].sent, undefined);
       equal(e.outgoing[0].failed, undefined);
     }
   } finally {
+    Service.engineManager.unregister(engine);
     await cleanAndGo(engine, server);
   }
 });
 
 add_task(async function test_nserror() {
   Service.engineManager.register(SteamEngine);
   let engine = Service.engineManager.get("steam");
   engine.enabled = true;
--- a/toolkit/modules/JSONFile.jsm
+++ b/toolkit/modules/JSONFile.jsm
@@ -98,17 +98,17 @@ function JSONFile(config) {
   }
 
   if (config.saveDelayMs === undefined) {
     config.saveDelayMs = kSaveDelayMs;
   }
   this._saver = new DeferredTask(() => this._save(), config.saveDelayMs);
 
   AsyncShutdown.profileBeforeChange.addBlocker("JSON store: writing data",
-                                               () => this._saver.finalize());
+                                               () => this.finalize());
 }
 
 JSONFile.prototype = {
   /**
    * String containing the file path where data should be saved.
    */
   path: "",
 
@@ -277,9 +277,26 @@ JSONFile.prototype = {
   }),
 
   /**
    * Synchronously work on the data just loaded into memory.
    */
   _processLoadedData(data) {
     this.data = this._dataPostProcessor ? this._dataPostProcessor(data) : data;
   },
+
+  /**
+   * Ensures that all data is persisted to disk, and prevents future calls to
+   * `saveSoon`. This is called automatically on shutdown, but can also be
+   * called explicitly when the object is no longer needed. Finalizing the
+   * object multiple times is a no-op.
+   */
+  finalize: Task.async(function* () {
+    if (this._finalized) {
+      return;
+    }
+    this._finalized = true;
+    yield this._saver.finalize();
+    this._data = null;
+    this.dataReady = false;
+  }),
+  _finalized: false,
 };