Bug 1331629 - Handle Backoff header in blocklist updater (r=mossop)
MozReview-Commit-ID: 21KOLxvCC4W
--- 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();