Bug 1471524 - Add start/end notifications on RemoteSettings.pollChanges() r=glasserc
authorMathieu Leplatre <mathieu@mozilla.com>
Wed, 16 Jan 2019 14:19:27 +0000
changeset 511186 6e8f0fdbe2e21fbf8db2bbfe1cefd5ba13ea665b
parent 511185 330c558436dda0e7643a68a2d810b77f433ddc41
child 511194 1312db5d495953cc6e18b16d082d06d611a21166
child 511195 987910383af00b26d557da874c16d039fba18da5
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersglasserc
bugs1471524
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1471524 - Add start/end notifications on RemoteSettings.pollChanges() r=glasserc Add start/end notifications on RemoteSettings.pollChanges() Differential Revision: https://phabricator.services.mozilla.com/D16674
services/common/docs/RemoteSettings.rst
services/settings/remote-settings.js
services/settings/test/unit/test_remote_settings_poll.js
--- a/services/common/docs/RemoteSettings.rst
+++ b/services/common/docs/RemoteSettings.rst
@@ -145,16 +145,37 @@ It basically consists in:
 #. (*optional*) Allow attachments on entries
 
 And once done:
 
 #. Create, modify or delete entries and let reviewers approve the changes
 #. Wait for Firefox to pick-up the changes for your settings key
 
 
+Global Notifications
+====================
+
+The polling for changes process sends two notifications that observers can register to:
+
+* ``remote-settings:changes-poll-start``: Polling for changes is starting. triggered either by the scheduled timer or a push broadcast.
+* ``remote-settings:changes-poll-end``: Polling for changes has ended
+
+.. code-block:: javascript
+
+    const observer = {
+      observe(aSubject, aTopic, aData) {
+        Services.obs.removeObserver(this, "remote-settings:changes-poll-start");
+
+        const { expectedTimestamp } = JSON.parse(aData);
+        console.log("Polling started", expectedTimestamp ? "from push broadcast" : "by scheduled trigger");
+      },
+    };
+    Services.obs.addObserver(observer, "remote-settings:changes-poll-start");
+
+
 Advanced Options
 ================
 
 ``filterFunc``: custom filtering function
 -----------------------------------------
 
 By default, the entries returned by ``.get()`` are filtered based on the JEXL expression result from the ``filter_expression`` field. The ``filterFunc`` option allows to execute a custom filter (async) function, that should return the record (modified or not) if kept or a falsy value if filtered out.
 
--- a/services/settings/remote-settings.js
+++ b/services/settings/remote-settings.js
@@ -163,16 +163,18 @@ function remoteSettingsFunction() {
         UptakeTelemetry.report(TELEMETRY_HISTOGRAM_KEY,
                                UptakeTelemetry.STATUS.BACKOFF);
         throw new Error(`Server is asking clients to back off; retry in ${Math.ceil(remainingMilliseconds / 1000)}s.`);
       } else {
         gPrefs.clearUserPref(PREF_SETTINGS_SERVER_BACKOFF);
       }
     }
 
+    Services.obs.notifyObservers(null, "remote-settings:changes-poll-start", JSON.stringify({ expectedTimestamp }));
+
     const lastEtag = gPrefs.getCharPref(PREF_SETTINGS_LAST_ETAG, "");
 
     let pollResult;
     try {
       pollResult = await Utils.fetchLatestChanges(remoteSettings.pollingEndpoint, { expectedTimestamp, lastEtag });
     } catch (e) {
       // Report polling error to Uptake Telemetry.
       let report;
@@ -221,16 +223,17 @@ function remoteSettingsFunction() {
       const client = await _client(bucket, collection);
       if (!client) {
         continue;
       }
       // Start synchronization! It will be a no-op if the specified `lastModified` equals
       // the one in the local database.
       try {
         await client.maybeSync(last_modified, { loadDump });
+
         // Save last time this client was successfully synced.
         Services.prefs.setIntPref(client.lastCheckTimePref, checkedServerTimeInSeconds);
       } catch (e) {
         if (!firstError) {
           firstError = e;
           firstError.details = change;
         }
       }
@@ -240,17 +243,17 @@ function remoteSettingsFunction() {
       throw firstError;
     }
 
     // Save current Etag for next poll.
     if (currentEtag) {
       gPrefs.setCharPref(PREF_SETTINGS_LAST_ETAG, currentEtag);
     }
 
-    Services.obs.notifyObservers(null, "remote-settings-changes-polled");
+    Services.obs.notifyObservers(null, "remote-settings:changes-poll-end");
   };
 
   /**
    * Returns an object with polling status information and the list of
    * known remote settings collections.
    */
   remoteSettings.inspect = async () => {
     const { changes, currentEtag: serverTimestamp } = await Utils.fetchLatestChanges(remoteSettings.pollingEndpoint);
--- a/services/settings/test/unit/test_remote_settings_poll.js
+++ b/services/settings/test/unit/test_remote_settings_poll.js
@@ -55,16 +55,40 @@ function run_test() {
 
   registerCleanupFunction(function() {
     server.stop(function() { });
   });
 }
 
 add_task(clear_state);
 
+
+add_task(async function test_an_event_is_sent_on_start() {
+  server.registerPathHandler(CHANGES_PATH, (request, response) => {
+    response.write(JSON.stringify({ data: [] }));
+    response.setHeader("ETag", '"42"');
+    response.setHeader("Date", (new Date()).toUTCString());
+    response.setStatusLine(null, 200, "OK");
+  });
+  let notificationObserved = null;
+  const observer = {
+    observe(aSubject, aTopic, aData) {
+      Services.obs.removeObserver(this, "remote-settings:changes-poll-start");
+      notificationObserved = JSON.parse(aData);
+    },
+  };
+  Services.obs.addObserver(observer, "remote-settings:changes-poll-start");
+
+  await RemoteSettings.pollChanges({ expectedTimestamp: 13 });
+
+  Assert.equal(notificationObserved.expectedTimestamp, 13, "start notification should have been observed");
+});
+add_task(clear_state);
+
+
 add_task(async function test_check_success() {
   const startHistogram = getUptakeTelemetrySnapshot(TELEMETRY_HISTOGRAM_KEY);
   const serverTime = 8000;
 
   server.registerPathHandler(CHANGES_PATH, serveChangesEntries(serverTime, [{
     id: "330a0c5f-fadf-ff0b-40c8-4eb0d924ff6a",
     last_modified: 1100,
     host: "localhost",
@@ -81,25 +105,25 @@ add_task(async function test_check_succe
   // add a test kinto client that will respond to lastModified information
   // for a collection called 'test-collection'.
   // Let's use a bucket that is not the default one (`test-bucket`).
   Services.prefs.setCharPref("services.settings.test_bucket", "test-bucket");
   const c = RemoteSettings("test-collection", { bucketNamePref: "services.settings.test_bucket" });
   let maybeSyncCalled = false;
   c.maybeSync = () => { maybeSyncCalled = true; };
 
-  // Ensure that the remote-settings-changes-polled notification works
+  // Ensure that the remote-settings:changes-poll-end notification works
   let notificationObserved = false;
   const observer = {
     observe(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(this, "remote-settings-changes-polled");
+      Services.obs.removeObserver(this, "remote-settings:changes-poll-end");
       notificationObserved = true;
     },
   };
-  Services.obs.addObserver(observer, "remote-settings-changes-polled");
+  Services.obs.addObserver(observer, "remote-settings:changes-poll-end");
 
   await RemoteSettings.pollChanges();
 
   // It didn't fail, hence we are sure that the unknown collection ``some-other-bucket/test-collection``
   // was ignored, otherwise it would have tried to reach the network.
 
   Assert.ok(maybeSyncCalled, "maybeSync was called");
   Assert.ok(notificationObserved, "a notification should have been observed");
@@ -126,17 +150,17 @@ add_task(async function test_update_time
     id: "028261ad-16d4-40c2-a96a-66f72914d125",
     last_modified: 42,
     host: "localhost",
     bucket: "main",
     collection: "whatever-collection",
   }]));
 
   await new Promise((resolve) => {
-    const e = "remote-settings-changes-polled";
+    const e = "remote-settings:changes-poll-end";
     const changesPolledObserver = {
       observe(aSubject, aTopic, aData) {
         Services.obs.removeObserver(this, e);
         resolve();
       },
     };
     Services.obs.addObserver(changesPolledObserver, e);
     remoteSettings.notify(null);
@@ -159,25 +183,25 @@ add_task(async function test_check_up_to
       response.setHeader("Date", (new Date(serverTime)).toUTCString());
       response.setStatusLine(null, 304, "Service Not Modified");
     }
   }
   server.registerPathHandler(CHANGES_PATH, server304);
 
   Services.prefs.setCharPref(PREF_LAST_ETAG, '"1100"');
 
-  // Ensure that the remote-settings-changes-polled notification is sent.
+  // Ensure that the remote-settings:changes-poll-end notification is sent.
   let notificationObserved = false;
   const observer = {
     observe(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(this, "remote-settings-changes-polled");
+      Services.obs.removeObserver(this, "remote-settings:changes-poll-end");
       notificationObserved = true;
     },
   };
-  Services.obs.addObserver(observer, "remote-settings-changes-polled");
+  Services.obs.addObserver(observer, "remote-settings:changes-poll-end");
 
   // If server has no change, a 304 is received, maybeSync() is not called.
   let maybeSyncCalled = false;
   const c = RemoteSettings("test-collection", {
     bucketName: "test-bucket",
   });
   c.maybeSync = () => { maybeSyncCalled = true; };
 
@@ -252,16 +276,17 @@ add_task(async function test_client_last
   Services.prefs.setIntPref(c.lastCheckTimePref, 0);
 
   await RemoteSettings.pollChanges({ expectedTimestamp: '"42"' });
 
   notEqual(Services.prefs.getIntPref(c.lastCheckTimePref), 0);
 });
 add_task(clear_state);
 
+
 add_task(async function test_success_with_partial_list() {
   function partialList(request, response) {
     const entries = [{
       id: "028261ad-16d4-40c2-a96a-66f72914d125",
       last_modified: 43,
       host: "localhost",
       bucket: "main",
       collection: "cid-1",
@@ -348,21 +373,21 @@ add_task(async function test_server_erro
     }));
     response.setStatusLine(null, 503, "Service Unavailable");
   }
   server.registerPathHandler(CHANGES_PATH, simulateErrorResponse);
 
   let notificationObserved = false;
   const observer = {
     observe(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(this, "remote-settings-changes-polled");
+      Services.obs.removeObserver(this, "remote-settings:changes-poll-end");
       notificationObserved = true;
     },
   };
-  Services.obs.addObserver(observer, "remote-settings-changes-polled");
+  Services.obs.addObserver(observer, "remote-settings:changes-poll-end");
   Services.prefs.setIntPref(PREF_LAST_UPDATE, 42);
 
   // pollChanges() fails with adequate error and no notification.
   let error;
   try {
     await RemoteSettings.pollChanges();
   } catch (e) {
     error = e;