Backed out changeset e6189da47385 (bug 1377533) for frequent xpcshell failures in test_ext_storage_sync_crypto.js a=backout
authorWes Kocher <wkocher@mozilla.com>
Thu, 31 Aug 2017 10:27:03 -0700
changeset 378017 19b12e888f715e6a34ed5dcb5ba3349d08c814e1
parent 378016 e6aa4869c63544cdbcd6b2fbb7fbc9ec9a56bcd7
child 378018 dc3195f3e50fa6df5f149a360f56702c877f5660
push id50153
push userkwierso@gmail.com
push dateThu, 31 Aug 2017 17:27:18 +0000
treeherderautoland@19b12e888f71 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1377533
milestone57.0a1
backs oute6189da47385a9a7286259d013103634432722ba
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
Backed out changeset e6189da47385 (bug 1377533) for frequent xpcshell failures in test_ext_storage_sync_crypto.js a=backout MozReview-Commit-ID: BLXHot0UsDU
services/common/blocklist-clients.js
services/common/tests/unit/test_blocklist_certificates.js
services/common/tests/unit/test_blocklist_clients.js
services/common/tests/unit/test_blocklist_pinning.js
services/common/tests/unit/test_blocklist_signatures.js
toolkit/components/extensions/ExtensionStorageSync.jsm
--- a/services/common/blocklist-clients.js
+++ b/services/common/blocklist-clients.js
@@ -44,20 +44,20 @@ const PREF_BLOCKLIST_PINNING_BUCKET     
 const PREF_BLOCKLIST_PINNING_COLLECTION      = "services.blocklist.pinning.collection";
 const PREF_BLOCKLIST_PINNING_CHECKED_SECONDS = "services.blocklist.pinning.checked";
 const PREF_BLOCKLIST_GFX_COLLECTION          = "services.blocklist.gfx.collection";
 const PREF_BLOCKLIST_GFX_CHECKED_SECONDS     = "services.blocklist.gfx.checked";
 const PREF_BLOCKLIST_ENFORCE_SIGNING         = "services.blocklist.signing.enforced";
 
 const INVALID_SIGNATURE = "Invalid content/signature";
 
-// This was the default path in earlier versions of
+// FIXME: this was the default path in earlier versions of
 // FirefoxAdapter, so for backwards compatibility we maintain this
 // filename, even though it isn't descriptive of who is using it.
-const KINTO_STORAGE_PATH = "kinto.sqlite";
+this.KINTO_STORAGE_PATH    = "kinto.sqlite";
 
 
 
 function mergeChanges(collection, localRecords, changes) {
   const records = {};
   // Local records by id.
   localRecords.forEach((record) => records[record.id] = collection.cleanLocalFields(record));
   // All existing records are replaced by the version from the server.
@@ -110,47 +110,16 @@ class BlocklistClient {
 
   get filename() {
     // Replace slash by OS specific path separator (eg. Windows)
     const identifier = OS.Path.join(...this.identifier.split("/"));
     return `${identifier}.json`;
   }
 
   /**
-   * Open the underlying Kinto collection, using the appropriate adapter and
-   * options. This acts as a context manager where the connection is closed
-   * once the specified `callback` has finished.
-   *
-   * @param {callback} function           the async function to execute with the open SQlite connection.
-   * @param {Object}   options            additional advanced options.
-   * @param {string}   options.bucket     override bucket name of client (default: this.bucketName)
-   * @param {string}   options.collection override collection name of client (default: this.collectionName)
-   * @param {string}   options.path       override default Sqlite path (default: kinto.sqlite)
-   * @param {string}   options.hooks      hooks to execute on synchronization (see Kinto.js docs)
-   */
-  async openCollection(callback, options = {}) {
-    const { bucket = this.bucketName, path = KINTO_STORAGE_PATH } = options;
-    if (!this._kinto) {
-      this._kinto = new Kinto({bucket, adapter: FirefoxAdapter});
-    }
-    let sqliteHandle;
-    try {
-      sqliteHandle = await FirefoxAdapter.openConnection({path});
-      const colOptions = Object.assign({adapterOptions: {sqliteHandle}}, options);
-      const {collection: collectionName = this.collectionName} = options;
-      const collection = this._kinto.collection(collectionName, colOptions);
-      return await callback(collection);
-    } finally {
-      if (sqliteHandle) {
-        await sqliteHandle.close();
-      }
-    }
-  }
-
-  /**
    * Load the the JSON file distributed with the release for this blocklist.
    *
    * For Bug 1257565 this method will have to try to load the file from the profile,
    * in order to leverage the updateJSONBlocklist() below, which writes a new
    * dump each time the collection changes.
    */
   async loadDumpFile() {
     // Replace OS specific path separator by / for URI.
@@ -212,123 +181,138 @@ class BlocklistClient {
    * @return {Promise}              which rejects on sync or process failure.
    */
   async maybeSync(lastModified, serverTime, options = {loadDump: true}) {
     const {loadDump} = options;
     const remote = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
     const enforceCollectionSigning =
       Services.prefs.getBoolPref(PREF_BLOCKLIST_ENFORCE_SIGNING);
 
+    if (!this._kinto) {
+      this._kinto = new Kinto({
+        bucket: this.bucketName,
+        adapter: FirefoxAdapter,
+      });
+    }
+
     // if there is a signerName and collection signing is enforced, add a
     // hook for incoming changes that validates the signature
-    const colOptions = {};
+    let hooks;
     if (this.signerName && enforceCollectionSigning) {
-      colOptions.hooks = {
+      hooks = {
         "incoming-changes": [(payload, collection) => {
           return this.validateCollectionSignature(remote, payload, collection);
         }]
       }
     }
 
+    let sqliteHandle;
     let reportStatus = null;
     try {
-      return await this.openCollection(async (collection) => {
-        // Synchronize remote data into a local Sqlite DB.
-        let collectionLastModified = await collection.db.getLastModified();
+      // Synchronize remote data into a local Sqlite DB.
+      sqliteHandle = await FirefoxAdapter.openConnection({path: KINTO_STORAGE_PATH});
+      const options = {
+        hooks,
+        adapterOptions: {sqliteHandle},
+      };
+      const collection = this._kinto.collection(this.collectionName, options);
 
-        // If there is no data currently in the collection, attempt to import
-        // initial data from the application defaults.
-        // This allows to avoid synchronizing the whole collection content on
-        // cold start.
-        if (!collectionLastModified && loadDump) {
-          try {
-            const initialData = await this.loadDumpFile();
-            await collection.loadDump(initialData.data);
-            collectionLastModified = await collection.db.getLastModified();
-          } catch (e) {
-            // Report but go-on.
-            Cu.reportError(e);
-          }
-        }
+      let collectionLastModified = await collection.db.getLastModified();
 
-        // If the data is up to date, there's no need to sync. We still need
-        // to record the fact that a check happened.
-        if (lastModified <= collectionLastModified) {
-          this.updateLastCheck(serverTime);
-          reportStatus = UptakeTelemetry.STATUS.UP_TO_DATE;
-          return;
-        }
-
-        // Fetch changes from server.
+      // If there is no data currently in the collection, attempt to import
+      // initial data from the application defaults.
+      // This allows to avoid synchronizing the whole collection content on
+      // cold start.
+      if (!collectionLastModified && loadDump) {
         try {
-          // Server changes have priority during synchronization.
-          const strategy = Kinto.syncStrategy.SERVER_WINS;
-          const {ok} = await collection.sync({remote, strategy});
-          if (!ok) {
-            // Some synchronization conflicts occured.
-            reportStatus = UptakeTelemetry.STATUS.CONFLICT_ERROR;
-            throw new Error("Sync failed");
-          }
+          const initialData = await this.loadDumpFile();
+          await collection.loadDump(initialData.data);
+          collectionLastModified = await collection.db.getLastModified();
         } catch (e) {
-          if (e.message == INVALID_SIGNATURE) {
-            // Signature verification failed during synchronzation.
-            reportStatus = UptakeTelemetry.STATUS.SIGNATURE_ERROR;
-            // if sync fails with a signature error, it's likely that our
-            // local data has been modified in some way.
-            // We will attempt to fix this by retrieving the whole
-            // remote collection.
-            const payload = await fetchRemoteCollection(remote, collection);
-            try {
-              await this.validateCollectionSignature(remote, payload, collection, {ignoreLocal: true});
-            } catch (e) {
-              reportStatus = UptakeTelemetry.STATUS.SIGNATURE_RETRY_ERROR;
-              throw e;
-            }
-            // if the signature is good (we haven't thrown), and the remote
-            // last_modified is newer than the local last_modified, replace the
-            // local data
-            const localLastModified = await collection.db.getLastModified();
-            if (payload.last_modified >= localLastModified) {
-              await collection.clear();
-              await collection.loadDump(payload.data);
-            }
-          } else {
-            // The sync has thrown, it can be a network or a general error.
-            if (/NetworkError/.test(e.message)) {
-              reportStatus = UptakeTelemetry.STATUS.NETWORK_ERROR;
-            } else if (/Backoff/.test(e.message)) {
-              reportStatus = UptakeTelemetry.STATUS.BACKOFF;
-            } else {
-              reportStatus = UptakeTelemetry.STATUS.SYNC_ERROR;
-            }
+          // Report but go-on.
+          Cu.reportError(e);
+        }
+      }
+
+      // If the data is up to date, there's no need to sync. We still need
+      // to record the fact that a check happened.
+      if (lastModified <= collectionLastModified) {
+        this.updateLastCheck(serverTime);
+        reportStatus = UptakeTelemetry.STATUS.UP_TO_DATE;
+        return;
+      }
+
+      // Fetch changes from server.
+      try {
+        // Server changes have priority during synchronization.
+        const strategy = Kinto.syncStrategy.SERVER_WINS;
+        const {ok} = await collection.sync({remote, strategy});
+        if (!ok) {
+          // Some synchronization conflicts occured.
+          reportStatus = UptakeTelemetry.STATUS.CONFLICT_ERROR;
+          throw new Error("Sync failed");
+        }
+      } catch (e) {
+        if (e.message == INVALID_SIGNATURE) {
+          // Signature verification failed during synchronzation.
+          reportStatus = UptakeTelemetry.STATUS.SIGNATURE_ERROR;
+          // if sync fails with a signature error, it's likely that our
+          // local data has been modified in some way.
+          // We will attempt to fix this by retrieving the whole
+          // remote collection.
+          const payload = await fetchRemoteCollection(remote, collection);
+          try {
+            await this.validateCollectionSignature(remote, payload, collection, {ignoreLocal: true});
+          } catch (e) {
+            reportStatus = UptakeTelemetry.STATUS.SIGNATURE_RETRY_ERROR;
             throw e;
           }
-        }
-        // Read local collection of records.
-        const {data} = await collection.list();
-
-        // Handle the obtained records (ie. apply locally).
-        try {
-          await this.processCallback(data);
-        } catch (e) {
-          reportStatus = UptakeTelemetry.STATUS.APPLY_ERROR;
+          // if the signature is good (we haven't thrown), and the remote
+          // last_modified is newer than the local last_modified, replace the
+          // local data
+          const localLastModified = await collection.db.getLastModified();
+          if (payload.last_modified >= localLastModified) {
+            await collection.clear();
+            await collection.loadDump(payload.data);
+          }
+        } else {
+          // The sync has thrown, it can be a network or a general error.
+          if (/NetworkError/.test(e.message)) {
+            reportStatus = UptakeTelemetry.STATUS.NETWORK_ERROR;
+          } else if (/Backoff/.test(e.message)) {
+            reportStatus = UptakeTelemetry.STATUS.BACKOFF;
+          } else {
+            reportStatus = UptakeTelemetry.STATUS.SYNC_ERROR;
+          }
           throw e;
         }
+      }
+      // Read local collection of records.
+      const {data} = await collection.list();
 
-        // Track last update.
-        this.updateLastCheck(serverTime);
+      // Handle the obtained records (ie. apply locally).
+      try {
+        await this.processCallback(data);
+      } catch (e) {
+        reportStatus = UptakeTelemetry.STATUS.APPLY_ERROR;
+        throw e;
+      }
 
-      }, colOptions);
+      // Track last update.
+      this.updateLastCheck(serverTime);
     } catch (e) {
       // No specific error was tracked, mark it as unknown.
       if (reportStatus === null) {
         reportStatus = UptakeTelemetry.STATUS.UNKNOWN_ERROR;
       }
       throw e;
     } finally {
+      if (sqliteHandle) {
+        await sqliteHandle.close();
+      }
       // No error was reported, this is a success!
       if (reportStatus === null) {
         reportStatus = UptakeTelemetry.STATUS.SUCCESS;
       }
       // Report success/error status to Telemetry.
       UptakeTelemetry.report(this.identifier, reportStatus);
     }
   }
--- a/services/common/tests/unit/test_blocklist_certificates.js
+++ b/services/common/tests/unit/test_blocklist_certificates.js
@@ -1,19 +1,38 @@
 const { Constructor: CC } = Components;
 
 Cu.import("resource://testing-common/httpd.js");
 
 const { OneCRLBlocklistClient } = Cu.import("resource://services-common/blocklist-clients.js", {});
+const { Kinto } = Cu.import("resource://services-common/kinto-offline-client.js", {});
+const { FirefoxAdapter } = Cu.import("resource://services-common/kinto-storage-adapter.js", {});
 
 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
   "nsIBinaryInputStream", "setInputStream");
 
 let server;
 
+// set up what we need to make storage adapters
+let sqliteHandle;
+const KINTO_FILENAME = "kinto.sqlite";
+
+function do_get_kinto_collection(collectionName) {
+  let config = {
+    // Set the remote to be some server that will cause test failure when
+    // hit since we should never hit the server directly, only via maybeSync()
+    remote: "https://firefox.settings.services.mozilla.com/v1/",
+    // Set up the adapter and bucket as normal
+    adapter: FirefoxAdapter,
+    adapterOptions: {sqliteHandle},
+    bucket: "blocklists"
+  };
+  return new Kinto(config).collection(collectionName);
+}
+
 // Some simple tests to demonstrate that the logic inside maybeSync works
 // correctly and that simple kinto operations are working as expected. There
 // are more tests for core Kinto.js (and its storage adapter) in the
 // xpcshell tests under /services/common
 add_task(async function test_something() {
   const configPath = "/v1/";
   const recordsPath = "/v1/buckets/blocklists/collections/certificates/records";
 
@@ -43,62 +62,55 @@ add_task(async function test_something()
     }
   }
   server.registerPathHandler(configPath, handleResponse);
   server.registerPathHandler(recordsPath, handleResponse);
 
   // Test an empty db populates
   await OneCRLBlocklistClient.maybeSync(2000, Date.now());
 
-  await OneCRLBlocklistClient.openCollection(async (collection) => {
-    // Open the collection, verify it's been populated:
-    const list = await collection.list();
-    // We know there will be initial values from the JSON dump.
-    // (at least as many as in the dump shipped when this test was written).
-    do_check_true(list.data.length >= 363);
-  });
+  sqliteHandle = await FirefoxAdapter.openConnection({path: KINTO_FILENAME});
+  const collection = do_get_kinto_collection("certificates");
+
+  // Open the collection, verify it's been populated:
+  let list = await collection.list();
+  // We know there will be initial values from the JSON dump.
+  // (at least as many as in the dump shipped when this test was written).
+  do_check_true(list.data.length >= 363);
 
   // No sync will be intented if maybeSync() is up-to-date.
   Services.prefs.clearUserPref("services.settings.server");
   Services.prefs.setIntPref("services.blocklist.onecrl.checked", 0);
   // Use any last_modified older than highest shipped in JSON dump.
   await OneCRLBlocklistClient.maybeSync(123456, Date.now());
   // Last check value was updated.
   do_check_neq(0, Services.prefs.getIntPref("services.blocklist.onecrl.checked"));
 
   // Restore server pref.
   Services.prefs.setCharPref("services.settings.server", dummyServerURL);
-
-  await OneCRLBlocklistClient.openCollection(async (collection) => {
-    // clear the collection, save a non-zero lastModified so we don't do
-    // import of initial data when we sync again.
-    await collection.clear();
-    // a lastModified value of 1000 means we get a remote collection with a
-    // single record
-    await collection.db.saveLastModified(1000);
-  });
-
+  // clear the collection, save a non-zero lastModified so we don't do
+  // import of initial data when we sync again.
+  await collection.clear();
+  // a lastModified value of 1000 means we get a remote collection with a
+  // single record
+  await collection.db.saveLastModified(1000);
   await OneCRLBlocklistClient.maybeSync(2000, Date.now());
 
-  await OneCRLBlocklistClient.openCollection(async (collection) => {
-    // Open the collection, verify it's been updated:
-    // Our test data now has two records; both should be in the local collection
-    const list = await collection.list();
-    do_check_eq(list.data.length, 1);
-  });
+  // Open the collection, verify it's been updated:
+  // Our test data now has two records; both should be in the local collection
+  list = await collection.list();
+  do_check_eq(list.data.length, 1);
 
   // Test the db is updated when we call again with a later lastModified value
   await OneCRLBlocklistClient.maybeSync(4000, Date.now());
 
-  await OneCRLBlocklistClient.openCollection(async (collection) => {
-    // Open the collection, verify it's been updated:
-    // Our test data now has two records; both should be in the local collection
-    const list = await collection.list();
-    do_check_eq(list.data.length, 3);
-  });
+  // Open the collection, verify it's been updated:
+  // Our test data now has two records; both should be in the local collection
+  list = await collection.list();
+  do_check_eq(list.data.length, 3);
 
   // Try to maybeSync with the current lastModified value - no connection
   // should be attempted.
   // Clear the kinto base pref so any connections will cause a test failure
   Services.prefs.clearUserPref("services.settings.server");
   await OneCRLBlocklistClient.maybeSync(4000, Date.now());
 
   // Try again with a lastModified value at some point in the past
@@ -126,16 +138,17 @@ function run_test() {
   // Set up an HTTP Server
   server = new HttpServer();
   server.start(-1);
 
   run_next_test();
 
   do_register_cleanup(function() {
     server.stop(() => { });
+    return sqliteHandle.close();
   });
 }
 
 // get a response for a given request from sample data
 function getSampleResponse(req, port) {
   const responses = {
     "OPTIONS": {
       "sampleHeaders": [
--- a/services/common/tests/unit/test_blocklist_clients.js
+++ b/services/common/tests/unit/test_blocklist_clients.js
@@ -1,46 +1,66 @@
 const { Constructor: CC } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://gre/modules/Timer.jsm");
 const { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
 const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
 
+const { Kinto } = Cu.import("resource://services-common/kinto-offline-client.js", {});
+const { FirefoxAdapter } = Cu.import("resource://services-common/kinto-storage-adapter.js", {});
 const BlocklistClients = Cu.import("resource://services-common/blocklist-clients.js", {});
 const { UptakeTelemetry } = Cu.import("resource://services-common/uptake-telemetry.js", {});
 
 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
   "nsIBinaryInputStream", "setInputStream");
+const kintoFilename = "kinto.sqlite";
 
 const gBlocklistClients = [
   {client: BlocklistClients.AddonBlocklistClient, testData: ["i808", "i720", "i539"]},
   {client: BlocklistClients.PluginBlocklistClient, testData: ["p1044", "p32", "p28"]},
   {client: BlocklistClients.GfxBlocklistClient, testData: ["g204", "g200", "g36"]},
 ];
 
 
 let server;
 
+function kintoCollection(collectionName, sqliteHandle) {
+  const config = {
+    // Set the remote to be some server that will cause test failure when
+    // hit since we should never hit the server directly, only via maybeSync()
+    remote: "https://firefox.settings.services.mozilla.com/v1/",
+    adapter: FirefoxAdapter,
+    adapterOptions: {sqliteHandle},
+    bucket: "blocklists"
+  };
+  return new Kinto(config).collection(collectionName);
+}
+
 async function readJSON(filepath) {
   const binaryData = await OS.File.read(filepath);
   const textData = (new TextDecoder()).decode(binaryData);
   return Promise.resolve(JSON.parse(textData));
 }
 
 async function clear_state() {
   for (let {client} of gBlocklistClients) {
     // Remove last server times.
     Services.prefs.clearUserPref(client.lastCheckTimePref);
 
     // Clear local DB.
-    await client.openCollection(async (collection) => {
+    let sqliteHandle;
+    try {
+      sqliteHandle = await FirefoxAdapter.openConnection({path: kintoFilename});
+      const collection = kintoCollection(client.collectionName, sqliteHandle);
       await collection.clear();
-    });
+    } finally {
+      await sqliteHandle.close();
+    }
 
     // Remove JSON dumps folders in profile dir.
     const dumpFile = OS.Path.join(OS.Constants.Path.profileDir, client.filename);
     const folder = OS.Path.dirname(dumpFile);
     await OS.File.removeDir(folder, { ignoreAbsent: true });
   }
 }
 
@@ -95,49 +115,53 @@ function run_test() {
 }
 
 add_task(async function test_initial_dump_is_loaded_as_synced_when_collection_is_empty() {
   for (let {client} of gBlocklistClients) {
     // Test an empty db populates, but don't reach server (specified timestamp <= dump).
     await client.maybeSync(1, Date.now());
 
     // Open the collection, verify the loaded data has status to synced:
-    await client.openCollection(async (collection) => {
-      const list = await collection.list();
-      equal(list.data[0]._status, "synced");
-    });
+    const sqliteHandle = await FirefoxAdapter.openConnection({path: kintoFilename});
+    const collection = kintoCollection(client.collectionName, sqliteHandle);
+    const list = await collection.list();
+    equal(list.data[0]._status, "synced");
+    await sqliteHandle.close();
   }
 });
 add_task(clear_state);
 
 add_task(async function test_records_obtained_from_server_are_stored_in_db() {
   for (let {client} of gBlocklistClients) {
     // Test an empty db populates
     await client.maybeSync(2000, Date.now(), {loadDump: false});
 
     // Open the collection, verify it's been populated:
     // Our test data has a single record; it should be in the local collection
-    await client.openCollection(async (collection) => {
-      const list = await collection.list();
-      equal(list.data.length, 1);
-    });
+    const sqliteHandle = await FirefoxAdapter.openConnection({path: kintoFilename});
+    let collection = kintoCollection(client.collectionName, sqliteHandle);
+    let list = await collection.list();
+    equal(list.data.length, 1);
+    await sqliteHandle.close();
   }
 });
 add_task(clear_state);
 
 add_task(async function test_records_changes_are_overwritten_by_server_changes() {
   const {client} = gBlocklistClients[0];
 
   // Create some local conflicting data, and make sure it syncs without error.
-  await client.openCollection(async (collection) => {
-    await collection.create({
-      "versionRange": [],
-      "id": "9d500963-d80e-3a91-6e74-66f3811b99cc"
-    }, { useRecordId: true });
-  });
+  const sqliteHandle = await FirefoxAdapter.openConnection({path: kintoFilename});
+  const collection = kintoCollection(client.collectionName, sqliteHandle);
+  await collection.create({
+    "versionRange": [],
+    "id": "9d500963-d80e-3a91-6e74-66f3811b99cc"
+  }, { useRecordId: true });
+  await sqliteHandle.close();
+
   await client.maybeSync(2000, Date.now(), {loadDump: false});
 });
 add_task(clear_state);
 
 add_task(async function test_list_is_written_to_file_in_profile() {
   for (let {client, testData} of gBlocklistClients) {
     const filePath = OS.Path.join(OS.Constants.Path.profileDir, client.filename);
     const profFile = new FileUtils.File(filePath);
@@ -255,44 +279,45 @@ add_task(async function test_telemetry_r
   checkUptakeTelemetry(startHistogram, endHistogram, expectedIncrements);
 });
 add_task(clear_state);
 
 add_task(async function test_telemetry_reports_if_sync_fails() {
   const {client} = gBlocklistClients[0];
   const serverTime = Date.now();
 
-  await client.openCollection(async (collection) => {
-    await collection.db.saveLastModified(9999);
-  });
+  const sqliteHandle = await FirefoxAdapter.openConnection({path: kintoFilename});
+  const collection = kintoCollection(client.collectionName, sqliteHandle);
+  await collection.db.saveLastModified(9999);
+  await sqliteHandle.close();
 
   const startHistogram = getUptakeTelemetrySnapshot(client.identifier);
 
   try {
     await client.maybeSync(10000, serverTime);
   } catch (e) {}
 
   const endHistogram = getUptakeTelemetrySnapshot(client.identifier);
   const expectedIncrements = {[UptakeTelemetry.STATUS.SYNC_ERROR]: 1};
   checkUptakeTelemetry(startHistogram, endHistogram, expectedIncrements);
 });
 add_task(clear_state);
 
 add_task(async function test_telemetry_reports_unknown_errors() {
   const {client} = gBlocklistClients[0];
   const serverTime = Date.now();
-  const backup = client.openCollection;
-  client.openCollection = () => { throw new Error("Internal"); };
+  const backup = FirefoxAdapter.openConnection;
+  FirefoxAdapter.openConnection = () => { throw new Error("Internal"); };
   const startHistogram = getUptakeTelemetrySnapshot(client.identifier);
 
   try {
     await client.maybeSync(2000, serverTime);
   } catch (e) {}
 
-  client.openCollection = backup;
+  FirefoxAdapter.openConnection = backup;
   const endHistogram = getUptakeTelemetrySnapshot(client.identifier);
   const expectedIncrements = {[UptakeTelemetry.STATUS.UNKNOWN_ERROR]: 1};
   checkUptakeTelemetry(startHistogram, endHistogram, expectedIncrements);
 });
 add_task(clear_state);
 
 // get a response for a given request from sample data
 function getSampleResponse(req, port) {
--- a/services/common/tests/unit/test_blocklist_pinning.js
+++ b/services/common/tests/unit/test_blocklist_pinning.js
@@ -1,17 +1,24 @@
 "use strict"
 
 const { Constructor: CC } = Components;
 
 Cu.import("resource://testing-common/httpd.js");
 
+const { Kinto } = Cu.import("resource://services-common/kinto-offline-client.js", {});
+const { FirefoxAdapter } = Cu.import("resource://services-common/kinto-storage-adapter.js", {});
+
 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
   "nsIBinaryInputStream", "setInputStream");
 
+const PREF_BLOCKLIST_PINNING_COLLECTION = "services.blocklist.pinning.collection";
+const COLLECTION_NAME = "pins";
+const KINTO_STORAGE_PATH    = "kinto.sqlite";
+
 // First, we need to setup appInfo or we can't do version checks
 var id = "xpcshell@tests.mozilla.org";
 var appName = "XPCShell";
 var version = "1";
 var platformVersion = "1.9.2";
 Cu.import("resource://testing-common/AppInfo.jsm", this);
 
 updateAppInfo({
@@ -19,19 +26,40 @@ updateAppInfo({
   ID: id,
   version,
   platformVersion: platformVersion ? platformVersion : "1.0",
   crashReporter: true,
 });
 
 let server;
 
+
+function do_get_kinto_collection(connection, collectionName) {
+  let config = {
+    // Set the remote to be some server that will cause test failure when
+    // hit since we should never hit the server directly (any non-local
+    // request causes failure in tests), only via maybeSync()
+    remote: "https://firefox.settings.services.mozilla.com/v1/",
+    // Set up the adapter and bucket as normal
+    adapter: FirefoxAdapter,
+    adapterOptions: {sqliteHandle: connection},
+    bucket: "pinning"
+  };
+  let kintoClient = new Kinto(config);
+  return kintoClient.collection(collectionName);
+}
+
 // Some simple tests to demonstrate that the core preload sync operations work
 // correctly and that simple kinto operations are working as expected.
 add_task(async function test_something() {
+  // set the collection name explicitly - since there will be version
+  // specific collection names in prefs
+  Services.prefs.setCharPref(PREF_BLOCKLIST_PINNING_COLLECTION,
+                             COLLECTION_NAME);
+
   const { PinningPreloadClient } = Cu.import("resource://services-common/blocklist-clients.js", {});
 
   const configPath = "/v1/";
   const recordsPath = "/v1/buckets/pinning/collections/pins/records";
 
   Services.prefs.setCharPref("services.settings.server",
                              `http://localhost:${server.identity.primaryPort}/v1`);
 
@@ -73,36 +101,37 @@ add_task(async function test_something()
   ok(!sss.isSecureURI(sss.HEADER_HSTS,
                       Services.io.newURI("https://four.example.com"), 0));
   ok(!sss.isSecureURI(sss.HEADER_HSTS,
                       Services.io.newURI("https://five.example.com"), 0));
 
   // Test an empty db populates
   await PinningPreloadClient.maybeSync(2000, Date.now());
 
+  let connection = await FirefoxAdapter.openConnection({path: KINTO_STORAGE_PATH});
+
   // Open the collection, verify it's been populated:
   // Our test data has a single record; it should be in the local collection
-  await PinningPreloadClient.openCollection(async (collection) => {
-    const list = await collection.list();
-    do_check_eq(list.data.length, 1);
-  });
+  let collection = do_get_kinto_collection(connection, COLLECTION_NAME);
+  let list = await collection.list();
+  do_check_eq(list.data.length, 1);
 
   // check that a pin exists for one.example.com
   ok(sss.isSecureURI(sss.HEADER_HPKP,
                      Services.io.newURI("https://one.example.com"), 0));
 
   // Test the db is updated when we call again with a later lastModified value
   await PinningPreloadClient.maybeSync(4000, Date.now());
 
   // Open the collection, verify it's been updated:
   // Our data now has four new records; all should be in the local collection
-  await PinningPreloadClient.openCollection(async (collection) => {
-    const list = await collection.list();
-    do_check_eq(list.data.length, 5);
-  });
+  collection = do_get_kinto_collection(connection, COLLECTION_NAME);
+  list = await collection.list();
+  do_check_eq(list.data.length, 5);
+  await connection.close();
 
   // check that a pin exists for two.example.com and three.example.com
   ok(sss.isSecureURI(sss.HEADER_HPKP,
                      Services.io.newURI("https://two.example.com"), 0));
   ok(sss.isSecureURI(sss.HEADER_HPKP,
                      Services.io.newURI("https://three.example.com"), 0));
 
   // check that a pin does not exist for four.example.com - it's in the
--- a/services/common/tests/unit/test_blocklist_signatures.js
+++ b/services/common/tests/unit/test_blocklist_signatures.js
@@ -1,26 +1,32 @@
 "use strict";
 
 Cu.import("resource://services-common/blocklist-updater.js");
 Cu.import("resource://testing-common/httpd.js");
 
+const { Kinto } = Cu.import("resource://services-common/kinto-offline-client.js", {});
+const { FirefoxAdapter } = Cu.import("resource://services-common/kinto-storage-adapter.js", {});
 const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const { OneCRLBlocklistClient } = Cu.import("resource://services-common/blocklist-clients.js", {});
 const { UptakeTelemetry } = Cu.import("resource://services-common/uptake-telemetry.js", {});
 
 let server;
 
+const PREF_BLOCKLIST_BUCKET            = "services.blocklist.bucket";
 const PREF_BLOCKLIST_ENFORCE_SIGNING   = "services.blocklist.signing.enforced";
+const PREF_BLOCKLIST_ONECRL_COLLECTION = "services.blocklist.onecrl.collection";
 const PREF_SETTINGS_SERVER             = "services.settings.server";
 const PREF_SIGNATURE_ROOT              = "security.content.signature.root_hash";
 
 // Telemetry reports.
 const TELEMETRY_HISTOGRAM_KEY = OneCRLBlocklistClient.identifier;
 
+const kintoFilename = "kinto.sqlite";
+
 const CERT_DIR = "test_blocklist_signatures/";
 const CHAIN_FILES =
     ["collection_signing_ee.pem",
      "collection_signing_int.pem",
      "collection_signing_root.pem"];
 
 function getFileData(file) {
   const stream = Cc["@mozilla.org/network/file-input-stream;1"]
@@ -49,21 +55,39 @@ function getCertChain() {
   const chain = [];
   for (let file of CHAIN_FILES) {
     chain.push(getFileData(do_get_file(CERT_DIR + file)));
   }
   return chain.join("\n");
 }
 
 async function checkRecordCount(count) {
-  await OneCRLBlocklistClient.openCollection(async (collection) => {
-    // Check we have the expected number of records
-    const records = await collection.list();
-    do_check_eq(count, records.data.length);
-  });
+  // open the collection manually
+  const base = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
+  const bucket = Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET);
+  const collectionName =
+      Services.prefs.getCharPref(PREF_BLOCKLIST_ONECRL_COLLECTION);
+
+  const sqliteHandle = await FirefoxAdapter.openConnection({path: kintoFilename});
+  const config = {
+    remote: base,
+    bucket,
+    adapter: FirefoxAdapter,
+    adapterOptions: {sqliteHandle},
+  };
+
+  const db = new Kinto(config);
+  const collection = db.collection(collectionName);
+
+  // Check we have the expected number of records
+  let records = await collection.list();
+  do_check_eq(count, records.data.length);
+
+  // Close the collection so the test can exit cleanly
+  await sqliteHandle.close();
 }
 
 // Check to ensure maybeSync is called with correct values when a changes
 // document contains information on when a collection was last modified
 add_task(async function test_check_signatures() {
   const port = server.identity.primaryPort;
 
   // a response to give the client when the cert chain is expected
--- a/toolkit/components/extensions/ExtensionStorageSync.jsm
+++ b/toolkit/components/extensions/ExtensionStorageSync.jsm
@@ -52,16 +52,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   CollectionKeyManager: "resource://services-sync/record.js",
   CommonUtils: "resource://services-common/utils.js",
   CryptoUtils: "resource://services-crypto/utils.js",
   fxAccounts: "resource://gre/modules/FxAccounts.jsm",
   KintoHttpClient: "resource://services-common/kinto-http-client.js",
   Kinto: "resource://services-common/kinto-offline-client.js",
   FirefoxAdapter: "resource://services-common/kinto-storage-adapter.js",
   Observers: "resource://services-common/observers.js",
+  Sqlite: "resource://gre/modules/Sqlite.jsm",
   Utils: "resource://services-sync/util.js",
 });
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "prefPermitsStorageSync",
                                       STORAGE_SYNC_ENABLED_PREF, true);
 XPCOMUtils.defineLazyPreferenceGetter(this, "prefStorageSyncServerURL",
                                       STORAGE_SYNC_SERVER_URL_PREF,
                                       KINTO_DEFAULT_SERVER_URL);
@@ -331,27 +332,37 @@ global.KeyRingEncryptionRemoteTransforme
  * Fields in the object returned by this Promise:
  *
  * - connection: a Sqlite connection. Meant for internal use only.
  * - kinto: a KintoBase object, suitable for using in Firefox. All
  *   collections in this database will use the same Sqlite connection.
  */
 const storageSyncInit = (async function() {
   const path = "storage-sync.sqlite";
-  const connection = await FirefoxAdapter.openConnection({path});
+  const opts = {path, sharedMemoryCache: false};
+  const connection = await Sqlite.openConnection(opts);
+  await FirefoxAdapter._init(connection);
   return {
     connection,
     kinto: new Kinto({
       adapter: FirefoxAdapter,
       adapterOptions: {sqliteHandle: connection},
       timeout: KINTO_REQUEST_TIMEOUT,
     }),
   };
 })();
 
+AsyncShutdown.profileBeforeChange.addBlocker(
+  "ExtensionStorageSync: close Sqlite handle",
+  async function() {
+    const ret = await storageSyncInit;
+    const {connection} = ret;
+    await connection.close();
+  }
+);
 // Kinto record IDs have two condtions:
 //
 // - They must contain only ASCII alphanumerics plus - and _. To fix
 // this, we encode all non-letters using _C_, where C is the
 // percent-encoded character, so space becomes _20_
 // and underscore becomes _5F_.
 //
 // - They must start with an ASCII letter. To ensure this, we prefix
@@ -1214,27 +1225,17 @@ class ExtensionStorageSync {
   }
 
   addOnChangedListener(extension, listener, context) {
     let listeners = this.listeners.get(extension) || new Set();
     listeners.add(listener);
     this.listeners.set(extension, listeners);
 
     // Force opening the collection so that we will sync for this extension.
-    // This happens asynchronously, even though the surface API is synchronous.
-    return this.getCollection(extension, context)
-      .catch((e) => {
-        // We can ignore failures that happen during shutdown here. First, we
-        // can't report in any way. And second, a failure to open the collection
-        // does not matter, because there won't be any message to listen to.
-        // See Bug 1395215.
-        if (!(/Kinto storage adapter connection closing/.test(e.message))) {
-          throw e;
-        }
-      });
+    return this.getCollection(extension, context);
   }
 
   removeOnChangedListener(extension, listener) {
     let listeners = this.listeners.get(extension);
     listeners.delete(listener);
     if (listeners.size == 0) {
       this.listeners.delete(extension);
     }