Bug 1331629 - Handle Backoff header in blocklist updater (r=mossop) draft
authorMathieu Leplatre <mathieu@mozilla.com>
Thu, 19 Jan 2017 15:40:26 +0100
changeset 479314 fec6948ea0f2cbb18be1fe5f799353ec09e4aa33
parent 479313 039b099e3c6d1a7f312e3cd209e8cea317f58a32
child 544653 ed899de38d823c8a6b18bb9a7ae6c1774c7c39da
push id44218
push usermleplatre@mozilla.com
push dateMon, 06 Feb 2017 13:24:41 +0000
reviewersmossop
bugs1331629
milestone54.0a1
Bug 1331629 - Handle Backoff header in blocklist updater (r=mossop) MozReview-Commit-ID: 21KOLxvCC4W
services/common/blocklist-updater.js
services/common/tests/unit/test_blocklist_updater.js
--- a/services/common/blocklist-updater.js
+++ b/services/common/blocklist-updater.js
@@ -7,16 +7,17 @@ this.EXPORTED_SYMBOLS = ["checkVersions"
 const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.importGlobalProperties(["fetch"]);
 const BlocklistClients = Cu.import("resource://services-common/blocklist-clients.js", {});
 
 const PREF_SETTINGS_SERVER              = "services.settings.server";
+const PREF_SETTINGS_SERVER_BACKOFF      = "services.settings.server.backoff";
 const PREF_BLOCKLIST_CHANGES_PATH       = "services.blocklist.changes.path";
 const PREF_BLOCKLIST_LAST_UPDATE        = "services.blocklist.last_update_seconds";
 const PREF_BLOCKLIST_LAST_ETAG          = "services.blocklist.last_etag";
 const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
 
 
 const gBlocklistClients = {
   [BlocklistClients.OneCRLBlocklistClient.collectionName]: BlocklistClients.OneCRLBlocklistClient,
@@ -24,20 +25,33 @@ const gBlocklistClients = {
   [BlocklistClients.GfxBlocklistClient.collectionName]: BlocklistClients.GfxBlocklistClient,
   [BlocklistClients.PluginBlocklistClient.collectionName]: BlocklistClients.PluginBlocklistClient,
   [BlocklistClients.PinningPreloadClient.collectionName]: BlocklistClients.PinningPreloadClient
 };
 
 // Add a blocklist client for testing purposes. Do not use for any other purpose
 this.addTestBlocklistClient = (name, client) => { gBlocklistClients[name] = client; }
 
+
 // This is called by the ping mechanism.
 // returns a promise that rejects if something goes wrong
 this.checkVersions = function() {
   return Task.spawn(function* syncClients() {
+
+    // Check if the server backoff time is elapsed.
+    if (Services.prefs.prefHasUserValue(PREF_SETTINGS_SERVER_BACKOFF)) {
+      const backoffReleaseTime = Services.prefs.getCharPref(PREF_SETTINGS_SERVER_BACKOFF);
+      const remainingMilliseconds = parseInt(backoffReleaseTime, 10) - Date.now();
+      if (remainingMilliseconds > 0) {
+        throw new Error(`Server is asking clients to back off; retry in ${Math.ceil(remainingMilliseconds / 1000)}s.`);
+      } else {
+        Services.prefs.clearUserPref(PREF_SETTINGS_SERVER_BACKOFF);
+      }
+    }
+
     // Fetch a versionInfo object that looks like:
     // {"data":[
     //   {
     //     "host":"kinto-ota.dev.mozaws.net",
     //     "last_modified":1450717104423,
     //     "bucket":"blocklists",
     //     "collection":"certificates"
     //    }]}
@@ -51,16 +65,25 @@ this.checkVersions = function() {
       const lastEtag = Services.prefs.getCharPref(PREF_BLOCKLIST_LAST_ETAG);
       if (lastEtag) {
         headers["If-None-Match"] = lastEtag;
       }
     }
 
     const response = yield fetch(changesEndpoint, {headers});
 
+    // Check if the server asked the clients to back off.
+    if (response.headers.has("Backoff")) {
+      const backoffSeconds = parseInt(response.headers.get("Backoff"), 10);
+      if (!isNaN(backoffSeconds)) {
+        const backoffReleaseTime = Date.now() + backoffSeconds * 1000;
+        Services.prefs.setCharPref(PREF_SETTINGS_SERVER_BACKOFF, backoffReleaseTime);
+      }
+    }
+
     let versionInfo;
     // No changes since last time. Go on with empty list of changes.
     if (response.status == 304) {
       versionInfo = {data: []};
     } else {
       versionInfo = yield response.json();
     }
 
--- a/services/common/tests/unit/test_blocklist_updater.js
+++ b/services/common/tests/unit/test_blocklist_updater.js
@@ -1,13 +1,14 @@
 Cu.import("resource://testing-common/httpd.js");
 
 var server;
 
 const PREF_SETTINGS_SERVER = "services.settings.server";
+const PREF_SETTINGS_SERVER_BACKOFF = "services.settings.server.backoff";
 const PREF_LAST_UPDATE = "services.blocklist.last_update_seconds";
 const PREF_LAST_ETAG = "services.blocklist.last_etag";
 const PREF_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
 
 // Check to ensure maybeSync is called with correct values when a changes
 // document contains information on when a collection was last modified
 add_task(function* test_check_maybeSync() {
   const changesPath = "/v1/buckets/monitor/collections/changes/records";
@@ -122,16 +123,43 @@ add_task(function* test_check_maybeSync(
   // set to a time in the future
   server.registerPathHandler(changesPath, handleResponse.bind(null, Date.now() + 10000));
 
   yield updater.checkVersions();
 
   clockDifference = Services.prefs.getIntPref(PREF_CLOCK_SKEW_SECONDS);
   // we previously set the serverTime to Date.now() + 10000 ms past epoch
   do_check_true(clockDifference <= 0 && clockDifference >= -10);
+
+  //
+  // Backoff
+  //
+  function simulateBackoffResponse(request, response) {
+    response.setHeader("Content-Type", "application/json; charset=UTF-8");
+    response.setHeader("Backoff", "10");
+    response.write(JSON.stringify({data: []}));
+    response.setStatusLine(null, 200, "OK");
+  }
+  server.registerPathHandler(changesPath, simulateBackoffResponse);
+  // First will work.
+  yield updater.checkVersions();
+  // Second will fail because we haven't waited.
+  try {
+    yield updater.checkVersions();
+    // The previous line should have thrown an error.
+    do_check_true(false);
+  } catch (e) {
+    do_check_true(/Server is asking clients to back off; retry in \d+s./.test(e.message));
+  }
+  // Once backoff time has expired, polling for changes can start again.
+  server.registerPathHandler(changesPath, handleResponse.bind(null, 2000));
+  Services.prefs.setCharPref(PREF_SETTINGS_SERVER_BACKOFF, `${Date.now() - 1000}`);
+  yield updater.checkVersions();
+  // Backoff tracking preference was cleared.
+  do_check_false(Services.prefs.prefHasUserValue(PREF_SETTINGS_SERVER_BACKOFF));
 });
 
 function run_test() {
   // Set up an HTTP Server
   server = new HttpServer();
   server.start(-1);
 
   run_next_test();