Bug 1371945 - Avoid a possible thread-safety problem with unfinalized statements. r=asuth
authorMarco Bonardo <mbonardo@mozilla.com>
Fri, 16 Jun 2017 17:43:23 +0200
changeset 365017 eacdba5020640bc50ba6690e9e3eb57ee7a8f2c8
parent 365016 ffe7ab5f00779d2389eb7b4daa58c9e7d6bbce23
child 365018 733c828d5ba39ea1602f5585daf3a7f4b3777d37
push id32058
push userkwierso@gmail.com
push dateWed, 21 Jun 2017 01:24:44 +0000
treeherdermozilla-central@c55e582aee5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1371945
milestone56.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 1371945 - Avoid a possible thread-safety problem with unfinalized statements. r=asuth Avoids a thread-safety race condition on shutdown where we could try to finalize a statement twice. Allows the async thread to be referenced until ShutdownAsyncThread, so async finalizers can make use of it. Removes the no more useful mAsyncExecutionThreadIsAlive. Nullifies the sqlite3_mutex pointer when the connection is closed, since it would be a dangling pointer. Use a ScopeExit to ensure the connection and the mutex pointers are always nullified on failure. Makes asyncClose bailout early if a Close method was already invoked before. Makes AsyncInitDatabase not use AsyncClose to just shutdown the async thread. Fixes various unfinalized statements in consumers code. Makes mConnectionClosed better follow mDBConn status. Replaces some mutex locking isClosed() calls with lockless isConnectionReadyOnThisThread. MozReview-Commit-ID: 6sftFehsQTt
extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js
extensions/cookie/test/unit/test_permmanager_migrate_7-8.js
netwerk/cookie/test/unit/test_bug1321912.js
services/common/tests/unit/test_async_querySpinningly.js
storage/SQLiteMutex.h
storage/mozStorageAsyncStatement.cpp
storage/mozStorageAsyncStatementExecution.cpp
storage/mozStorageConnection.cpp
storage/mozStorageConnection.h
storage/mozStorageService.cpp
storage/mozStorageStatement.cpp
storage/test/unit/head_storage.js
storage/test/unit/test_storage_connection.js
--- a/extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
@@ -8,20 +8,16 @@ var PERMISSIONS_FILE_NAME = "permissions
 
 function GetPermissionsFile(profile)
 {
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
-function run_test() {
-  run_next_test();
-}
-
 add_task(function* test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 4;
 
   db.executeSimpleSQL(
@@ -54,17 +50,21 @@ add_task(function* test() {
     stmtInsert.bindByName("type", type);
     stmtInsert.bindByName("permission", permission);
     stmtInsert.bindByName("expireType", expireType);
     stmtInsert.bindByName("expireTime", expireTime);
     stmtInsert.bindByName("modificationTime", modificationTime);
     stmtInsert.bindByName("appId", appId);
     stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
 
-    stmtInsert.execute();
+    try {
+      stmtInsert.execute();
+    } finally {
+      stmtInsert.reset();
+    }
 
     return {
       id: thisId,
       host: host,
       type: type,
       permission: permission,
       expireType: expireType,
       expireTime: expireTime,
@@ -194,14 +194,18 @@ add_task(function* test() {
     let db = Services.storage.openDatabase(GetPermissionsFile(profile));
     do_check_true(db.tableExists("moz_perms"));
     do_check_true(db.tableExists("moz_hosts"));
     do_check_false(db.tableExists("moz_hosts_is_backup"));
     do_check_false(db.tableExists("moz_perms_v6"));
 
     // The moz_hosts table should still exist but be empty
     let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
-    mozHostsCount.executeStep();
-    do_check_eq(mozHostsCount.getInt64(0), 0);
+    try {
+      mozHostsCount.executeStep();
+      do_check_eq(mozHostsCount.getInt64(0), 0);
+    } finally {
+      mozHostsCount.finalize();
+    }
 
     db.close();
   }
 });
--- a/extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
@@ -37,20 +37,16 @@ function GetPermissionsFile(profile)
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
 /*
  * Done nsINavHistoryService code
  */
 
-function run_test() {
-  run_next_test();
-}
-
 add_task(function test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
 
   // Make sure that we can't resolve the nsINavHistoryService
   try {
     Cc['@mozilla.org/browser/nav-history-service;1'].getService(Ci.nsINavHistoryService);
     do_check_true(false, "There shouldn't have been a nsINavHistoryService");
@@ -91,17 +87,21 @@ add_task(function test() {
     stmtInsert.bindByName("type", type);
     stmtInsert.bindByName("permission", permission);
     stmtInsert.bindByName("expireType", expireType);
     stmtInsert.bindByName("expireTime", expireTime);
     stmtInsert.bindByName("modificationTime", modificationTime);
     stmtInsert.bindByName("appId", appId);
     stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
 
-    stmtInsert.execute();
+    try {
+      stmtInsert.execute();
+    } finally {
+      stmtInsert.reset();
+    }
 
     return {
       id: thisId,
       host: host,
       type: type,
       permission: permission,
       expireType: expireType,
       expireTime: expireTime,
@@ -211,16 +211,20 @@ add_task(function test() {
     let db = Services.storage.openDatabase(GetPermissionsFile(profile));
     do_check_true(db.tableExists("moz_perms"));
     do_check_true(db.tableExists("moz_hosts"));
     do_check_false(db.tableExists("moz_hosts_is_backup"));
     do_check_false(db.tableExists("moz_perms_v6"));
 
     // The moz_hosts table should still exist but be empty
     let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
-    mozHostsCount.executeStep();
-    do_check_eq(mozHostsCount.getInt64(0), 0);
+    try {
+      mozHostsCount.executeStep();
+      do_check_eq(mozHostsCount.getInt64(0), 0);
+    } finally {
+      mozHostsCount.finalize();
+    }
 
     db.close();
   }
 
   cleanupFactory();
 });
--- a/extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
@@ -8,20 +8,16 @@ var PERMISSIONS_FILE_NAME = "permissions
 
 function GetPermissionsFile(profile)
 {
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
-function run_test() {
-  run_next_test();
-}
-
 add_task(function* test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 5;
 
   /*
@@ -76,17 +72,21 @@ add_task(function* test() {
     stmt5Insert.bindByName("id", thisId);
     stmt5Insert.bindByName("origin", origin);
     stmt5Insert.bindByName("type", type);
     stmt5Insert.bindByName("permission", permission);
     stmt5Insert.bindByName("expireType", expireType);
     stmt5Insert.bindByName("expireTime", expireTime);
     stmt5Insert.bindByName("modificationTime", modificationTime);
 
-    stmt5Insert.execute();
+    try {
+      stmt5Insert.execute();
+    } finally {
+      stmt5Insert.reset();
+    }
 
     return {
       id: thisId,
       origin: origin,
       type: type,
       permission: permission,
       expireType: expireType,
       expireTime: expireTime,
@@ -102,17 +102,21 @@ add_task(function* test() {
     stmtInsert.bindByName("type", type);
     stmtInsert.bindByName("permission", permission);
     stmtInsert.bindByName("expireType", expireType);
     stmtInsert.bindByName("expireTime", expireTime);
     stmtInsert.bindByName("modificationTime", modificationTime);
     stmtInsert.bindByName("appId", appId);
     stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
 
-    stmtInsert.execute();
+    try {
+      stmtInsert.execute();
+    } finally {
+      stmtInsert.reset();
+    }
 
     return {
       id: thisId,
       host: host,
       type: type,
       permission: permission,
       expireType: expireType,
       expireTime: expireTime,
@@ -146,16 +150,17 @@ add_task(function* test() {
     insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
   ];
 
   // CLose the db connection
+  stmt5Insert.finalize();
   stmtInsert.finalize();
   db.close();
   stmtInsert = null;
   db = null;
 
   let expected = [
     // The http:// entries under foo.com won't be inserted, as there are history entries for foo.com,
     // and http://foo.com or a subdomain are never visited.
@@ -248,37 +253,48 @@ add_task(function* test() {
     let db = Services.storage.openDatabase(GetPermissionsFile(profile));
     do_check_true(db.tableExists("moz_perms"));
     do_check_true(db.tableExists("moz_hosts"));
     do_check_false(db.tableExists("moz_hosts_is_backup"));
     do_check_true(db.tableExists("moz_perms_v6"));
 
     // The moz_hosts table should still exist but be empty
     let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
-    mozHostsCount.executeStep();
-    do_check_eq(mozHostsCount.getInt64(0), 0);
+    try {
+      mozHostsCount.executeStep();
+      do_check_eq(mozHostsCount.getInt64(0), 0);
+    } finally {
+      mozHostsCount.finalize();
+    }
 
     // Check that the moz_perms_v6 table contains the backup of the entry we created
     let mozPermsV6Stmt = db.createStatement("SELECT " +
                                             "origin, type, permission, expireType, expireTime, modificationTime " +
                                             "FROM moz_perms_v6 WHERE id = :id");
-
-    // Check that the moz_hosts table still contains the correct values.
-    created5.forEach((it) => {
-      mozPermsV6Stmt.reset();
-      mozPermsV6Stmt.bindByName("id", it.id);
-      mozPermsV6Stmt.executeStep();
-      do_check_eq(mozPermsV6Stmt.getUTF8String(0), it.origin);
-      do_check_eq(mozPermsV6Stmt.getUTF8String(1), it.type);
-      do_check_eq(mozPermsV6Stmt.getInt64(2), it.permission);
-      do_check_eq(mozPermsV6Stmt.getInt64(3), it.expireType);
-      do_check_eq(mozPermsV6Stmt.getInt64(4), it.expireTime);
-      do_check_eq(mozPermsV6Stmt.getInt64(5), it.modificationTime);
-    });
+    try {
+      // Check that the moz_hosts table still contains the correct values.
+      created5.forEach((it) => {
+        mozPermsV6Stmt.reset();
+        mozPermsV6Stmt.bindByName("id", it.id);
+        mozPermsV6Stmt.executeStep();
+        do_check_eq(mozPermsV6Stmt.getUTF8String(0), it.origin);
+        do_check_eq(mozPermsV6Stmt.getUTF8String(1), it.type);
+        do_check_eq(mozPermsV6Stmt.getInt64(2), it.permission);
+        do_check_eq(mozPermsV6Stmt.getInt64(3), it.expireType);
+        do_check_eq(mozPermsV6Stmt.getInt64(4), it.expireTime);
+        do_check_eq(mozPermsV6Stmt.getInt64(5), it.modificationTime);
+      });
+    } finally {
+      mozPermsV6Stmt.finalize();
+    }
 
     // Check that there are the right number of values
     let mozPermsV6Count = db.createStatement("SELECT count(*) FROM moz_perms_v6");
-    mozPermsV6Count.executeStep();
-    do_check_eq(mozPermsV6Count.getInt64(0), created5.length);
+    try {
+      mozPermsV6Count.executeStep();
+      do_check_eq(mozPermsV6Count.getInt64(0), created5.length);
+    } finally {
+      mozPermsV6Count.finalize();
+    }
 
     db.close();
   }
 });
--- a/extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
@@ -8,20 +8,16 @@ var PERMISSIONS_FILE_NAME = "permissions
 
 function GetPermissionsFile(profile)
 {
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
-function run_test() {
-  run_next_test();
-}
-
 add_task(function test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 5;
 
   /*
@@ -53,17 +49,21 @@ add_task(function test() {
     stmt5Insert.bindByName("id", thisId);
     stmt5Insert.bindByName("origin", origin);
     stmt5Insert.bindByName("type", type);
     stmt5Insert.bindByName("permission", permission);
     stmt5Insert.bindByName("expireType", expireType);
     stmt5Insert.bindByName("expireTime", expireTime);
     stmt5Insert.bindByName("modificationTime", modificationTime);
 
-    stmt5Insert.execute();
+    try {
+      stmt5Insert.execute();
+    } finally {
+      stmt5Insert.reset();
+    }
 
     return {
       id: thisId,
       host: origin,
       type: type,
       permission: permission,
       expireType: expireType,
       expireTime: expireTime,
@@ -137,32 +137,39 @@ add_task(function test() {
     do_check_true(db.tableExists("moz_hosts"));
     do_check_false(db.tableExists("moz_hosts_is_backup"));
     do_check_false(db.tableExists("moz_perms_v6"));
 
     let mozHostsStmt = db.createStatement("SELECT " +
                                           "host, type, permission, expireType, expireTime, " +
                                           "modificationTime, appId, isInBrowserElement " +
                                           "FROM moz_hosts WHERE id = :id");
-
-    // Check that the moz_hosts table still contains the correct values.
-    created4.forEach((it) => {
-      mozHostsStmt.reset();
-      mozHostsStmt.bindByName("id", it.id);
-      mozHostsStmt.executeStep();
-      do_check_eq(mozHostsStmt.getUTF8String(0), it.host);
-      do_check_eq(mozHostsStmt.getUTF8String(1), it.type);
-      do_check_eq(mozHostsStmt.getInt64(2), it.permission);
-      do_check_eq(mozHostsStmt.getInt64(3), it.expireType);
-      do_check_eq(mozHostsStmt.getInt64(4), it.expireTime);
-      do_check_eq(mozHostsStmt.getInt64(5), it.modificationTime);
-      do_check_eq(mozHostsStmt.getInt64(6), it.appId);
-      do_check_eq(mozHostsStmt.getInt64(7), it.isInBrowserElement);
-    });
+    try {
+      // Check that the moz_hosts table still contains the correct values.
+      created4.forEach((it) => {
+        mozHostsStmt.reset();
+        mozHostsStmt.bindByName("id", it.id);
+        mozHostsStmt.executeStep();
+        do_check_eq(mozHostsStmt.getUTF8String(0), it.host);
+        do_check_eq(mozHostsStmt.getUTF8String(1), it.type);
+        do_check_eq(mozHostsStmt.getInt64(2), it.permission);
+        do_check_eq(mozHostsStmt.getInt64(3), it.expireType);
+        do_check_eq(mozHostsStmt.getInt64(4), it.expireTime);
+        do_check_eq(mozHostsStmt.getInt64(5), it.modificationTime);
+        do_check_eq(mozHostsStmt.getInt64(6), it.appId);
+        do_check_eq(mozHostsStmt.getInt64(7), it.isInBrowserElement);
+      });
+    } finally {
+      mozHostsStmt.finalize();
+    }
 
     // Check that there are the right number of values
     let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
-    mozHostsCount.executeStep();
-    do_check_eq(mozHostsCount.getInt64(0), created4.length);
+    try {
+      mozHostsCount.executeStep();
+      do_check_eq(mozHostsCount.getInt64(0), created4.length);
+    } finally {
+      mozHostsCount.finalize();
+    }
 
     db.close();
   }
 });
--- a/extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
@@ -8,20 +8,16 @@ var PERMISSIONS_FILE_NAME = "permissions
 
 function GetPermissionsFile(profile)
 {
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
-function run_test() {
-  run_next_test();
-}
-
 add_task(function* test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 6;
 
   /*
@@ -76,17 +72,21 @@ add_task(function* test() {
     stmt6Insert.bindByName("id", thisId);
     stmt6Insert.bindByName("origin", origin);
     stmt6Insert.bindByName("type", type);
     stmt6Insert.bindByName("permission", permission);
     stmt6Insert.bindByName("expireType", expireType);
     stmt6Insert.bindByName("expireTime", expireTime);
     stmt6Insert.bindByName("modificationTime", modificationTime);
 
-    stmt6Insert.execute();
+    try {
+      stmt6Insert.execute();
+    } finally {
+      stmt6Insert.reset();
+    }
 
     return {
       id: thisId,
       origin: origin,
       type: type,
       permission: permission,
       expireType: expireType,
       expireTime: expireTime,
@@ -102,17 +102,21 @@ add_task(function* test() {
     stmtInsert.bindByName("type", type);
     stmtInsert.bindByName("permission", permission);
     stmtInsert.bindByName("expireType", expireType);
     stmtInsert.bindByName("expireTime", expireTime);
     stmtInsert.bindByName("modificationTime", modificationTime);
     stmtInsert.bindByName("appId", appId);
     stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
 
-    stmtInsert.execute();
+    try {
+      stmtInsert.execute();
+    } finally {
+      stmtInsert.reset();
+    }
 
     return {
       id: thisId,
       host: host,
       type: type,
       permission: permission,
       expireType: expireType,
       expireTime: expireTime,
@@ -146,16 +150,17 @@ add_task(function* test() {
     insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
   ];
 
   // CLose the db connection
+  stmt6Insert.finalize();
   stmtInsert.finalize();
   db.close();
   stmtInsert = null;
   db = null;
 
   let expected = [
     // The http:// entries under foo.com won't be inserted, as there are history entries for foo.com,
     // and http://foo.com or a subdomain are never visited.
@@ -248,37 +253,48 @@ add_task(function* test() {
     let db = Services.storage.openDatabase(GetPermissionsFile(profile));
     do_check_true(db.tableExists("moz_perms"));
     do_check_true(db.tableExists("moz_hosts"));
     do_check_false(db.tableExists("moz_hosts_is_backup"));
     do_check_true(db.tableExists("moz_perms_v6"));
 
     // The moz_hosts table should still exist but be empty
     let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
-    mozHostsCount.executeStep();
-    do_check_eq(mozHostsCount.getInt64(0), 0);
+    try {
+      mozHostsCount.executeStep();
+      do_check_eq(mozHostsCount.getInt64(0), 0);
+    } finally {
+      mozHostsCount.finalize();
+    }
 
     // Check that the moz_perms_v6 table contains the backup of the entry we created
     let mozPermsV6Stmt = db.createStatement("SELECT " +
                                             "origin, type, permission, expireType, expireTime, modificationTime " +
                                             "FROM moz_perms_v6 WHERE id = :id");
-
-    // Check that the moz_hosts table still contains the correct values.
-    created6.forEach((it) => {
-      mozPermsV6Stmt.reset();
-      mozPermsV6Stmt.bindByName("id", it.id);
-      mozPermsV6Stmt.executeStep();
-      do_check_eq(mozPermsV6Stmt.getUTF8String(0), it.origin);
-      do_check_eq(mozPermsV6Stmt.getUTF8String(1), it.type);
-      do_check_eq(mozPermsV6Stmt.getInt64(2), it.permission);
-      do_check_eq(mozPermsV6Stmt.getInt64(3), it.expireType);
-      do_check_eq(mozPermsV6Stmt.getInt64(4), it.expireTime);
-      do_check_eq(mozPermsV6Stmt.getInt64(5), it.modificationTime);
-    });
+    try {
+      // Check that the moz_hosts table still contains the correct values.
+      created6.forEach((it) => {
+        mozPermsV6Stmt.reset();
+        mozPermsV6Stmt.bindByName("id", it.id);
+        mozPermsV6Stmt.executeStep();
+        do_check_eq(mozPermsV6Stmt.getUTF8String(0), it.origin);
+        do_check_eq(mozPermsV6Stmt.getUTF8String(1), it.type);
+        do_check_eq(mozPermsV6Stmt.getInt64(2), it.permission);
+        do_check_eq(mozPermsV6Stmt.getInt64(3), it.expireType);
+        do_check_eq(mozPermsV6Stmt.getInt64(4), it.expireTime);
+        do_check_eq(mozPermsV6Stmt.getInt64(5), it.modificationTime);
+      });
+    } finally {
+      mozPermsV6Stmt.finalize();
+    }
 
     // Check that there are the right number of values
     let mozPermsV6Count = db.createStatement("SELECT count(*) FROM moz_perms_v6");
-    mozPermsV6Count.executeStep();
-    do_check_eq(mozPermsV6Count.getInt64(0), created6.length);
+    try {
+      mozPermsV6Count.executeStep();
+      do_check_eq(mozPermsV6Count.getInt64(0), created6.length);
+    } finally {
+      mozPermsV6Count.finalize();
+    }
 
     db.close();
   }
 });
--- a/extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js
@@ -8,20 +8,16 @@ var PERMISSIONS_FILE_NAME = "permissions
 
 function GetPermissionsFile(profile)
 {
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
-function run_test() {
-  run_next_test();
-}
-
 add_task(function test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 6;
 
   /*
@@ -53,17 +49,21 @@ add_task(function test() {
     stmt6Insert.bindByName("id", thisId);
     stmt6Insert.bindByName("origin", origin);
     stmt6Insert.bindByName("type", type);
     stmt6Insert.bindByName("permission", permission);
     stmt6Insert.bindByName("expireType", expireType);
     stmt6Insert.bindByName("expireTime", expireTime);
     stmt6Insert.bindByName("modificationTime", modificationTime);
 
-    stmt6Insert.execute();
+    try {
+      stmt6Insert.execute();
+    } finally {
+      stmt6Insert.reset();
+    }
 
     return {
       id: thisId,
       host: origin,
       type: type,
       permission: permission,
       expireType: expireType,
       expireTime: expireTime,
@@ -131,32 +131,39 @@ add_task(function test() {
     do_check_true(db.tableExists("moz_hosts"));
     do_check_false(db.tableExists("moz_hosts_is_backup"));
     do_check_false(db.tableExists("moz_perms_v6"));
 
     let mozHostsStmt = db.createStatement("SELECT " +
                                           "host, type, permission, expireType, expireTime, " +
                                           "modificationTime, appId, isInBrowserElement " +
                                           "FROM moz_hosts WHERE id = :id");
-
-    // Check that the moz_hosts table still contains the correct values.
-    created4.forEach((it) => {
-      mozHostsStmt.reset();
-      mozHostsStmt.bindByName("id", it.id);
-      mozHostsStmt.executeStep();
-      do_check_eq(mozHostsStmt.getUTF8String(0), it.host);
-      do_check_eq(mozHostsStmt.getUTF8String(1), it.type);
-      do_check_eq(mozHostsStmt.getInt64(2), it.permission);
-      do_check_eq(mozHostsStmt.getInt64(3), it.expireType);
-      do_check_eq(mozHostsStmt.getInt64(4), it.expireTime);
-      do_check_eq(mozHostsStmt.getInt64(5), it.modificationTime);
-      do_check_eq(mozHostsStmt.getInt64(6), it.appId);
-      do_check_eq(mozHostsStmt.getInt64(7), it.isInBrowserElement);
-    });
+    try {
+      // Check that the moz_hosts table still contains the correct values.
+      created4.forEach((it) => {
+        mozHostsStmt.reset();
+        mozHostsStmt.bindByName("id", it.id);
+        mozHostsStmt.executeStep();
+        do_check_eq(mozHostsStmt.getUTF8String(0), it.host);
+        do_check_eq(mozHostsStmt.getUTF8String(1), it.type);
+        do_check_eq(mozHostsStmt.getInt64(2), it.permission);
+        do_check_eq(mozHostsStmt.getInt64(3), it.expireType);
+        do_check_eq(mozHostsStmt.getInt64(4), it.expireTime);
+        do_check_eq(mozHostsStmt.getInt64(5), it.modificationTime);
+        do_check_eq(mozHostsStmt.getInt64(6), it.appId);
+        do_check_eq(mozHostsStmt.getInt64(7), it.isInBrowserElement);
+      });
+    } finally {
+      mozHostsStmt.finalize();
+    }
 
     // Check that there are the right number of values
     let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
-    mozHostsCount.executeStep();
-    do_check_eq(mozHostsCount.getInt64(0), created4.length);
+    try {
+      mozHostsCount.executeStep();
+      do_check_eq(mozHostsCount.getInt64(0), created4.length);
+    } finally {
+      mozHostsCount.finalize();
+    }
 
     db.close();
   }
 });
--- a/extensions/cookie/test/unit/test_permmanager_migrate_7-8.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_7-8.js
@@ -8,20 +8,16 @@ var PERMISSIONS_FILE_NAME = "permissions
 
 function GetPermissionsFile(profile)
 {
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
-function run_test() {
-  run_next_test();
-}
-
 add_task(function* test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 7;
 
   /*
@@ -81,17 +77,21 @@ add_task(function* test() {
     stmt6Insert.bindByName("id", thisId);
     stmt6Insert.bindByName("origin", origin);
     stmt6Insert.bindByName("type", type);
     stmt6Insert.bindByName("permission", permission);
     stmt6Insert.bindByName("expireType", expireType);
     stmt6Insert.bindByName("expireTime", expireTime);
     stmt6Insert.bindByName("modificationTime", modificationTime);
 
-    stmt6Insert.execute();
+    try {
+      stmt6Insert.execute();
+    } finally {
+      stmt6Insert.reset();
+    }
 
     return {
       id: thisId,
       origin: origin,
       type: type,
       permission: permission,
       expireType: expireType,
       expireTime: expireTime,
@@ -107,17 +107,21 @@ add_task(function* test() {
     stmtInsert.bindByName("type", type);
     stmtInsert.bindByName("permission", permission);
     stmtInsert.bindByName("expireType", expireType);
     stmtInsert.bindByName("expireTime", expireTime);
     stmtInsert.bindByName("modificationTime", modificationTime);
     stmtInsert.bindByName("appId", appId);
     stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
 
-    stmtInsert.execute();
+    try {
+      stmtInsert.execute();
+    } finally {
+      stmtInsert.reset();
+    }
 
     return {
       id: thisId,
       host: host,
       type: type,
       permission: permission,
       expireType: expireType,
       expireTime: expireTime,
@@ -156,16 +160,17 @@ add_task(function* test() {
     insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
   ];
 
   // CLose the db connection
+  stmt6Insert.finalize();
   stmtInsert.finalize();
   db.close();
   stmtInsert = null;
   db = null;
 
   let expected = [
     // We should have kept the previously migrated entries
     ["https://foo.com", "A", 2, 0, 0, 0],
@@ -228,19 +233,27 @@ add_task(function* test() {
     let db = Services.storage.openDatabase(GetPermissionsFile(profile));
     do_check_true(db.tableExists("moz_perms"));
     do_check_true(db.tableExists("moz_hosts"));
     do_check_false(db.tableExists("moz_hosts_is_backup"));
     do_check_false(db.tableExists("moz_perms_v6"));
 
     // The moz_hosts table should still exist but be empty
     let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
-    mozHostsCount.executeStep();
-    do_check_eq(mozHostsCount.getInt64(0), 0);
+    try {
+      mozHostsCount.executeStep();
+      do_check_eq(mozHostsCount.getInt64(0), 0);
+    } finally {
+      mozHostsCount.finalize();
+    }
 
     // Check that there are the right number of values in the permissions database
     let mozPermsCount = db.createStatement("SELECT count(*) FROM moz_perms");
-    mozPermsCount.executeStep();
-    do_check_eq(mozPermsCount.getInt64(0), expected.length);
+    try {
+      mozPermsCount.executeStep();
+      do_check_eq(mozPermsCount.getInt64(0), expected.length);
+    } finally {
+      mozPermsCount.finalize();
+    }
 
     db.close();
   }
 });
--- a/netwerk/cookie/test/unit/test_bug1321912.js
+++ b/netwerk/cookie/test/unit/test_bug1321912.js
@@ -51,28 +51,35 @@ conn.executeSimpleSQL("INSERT INTO moz_c
 
 // Now start the cookie service, and then check the fields in the table.
 
 const cs = Cc["@mozilla.org/cookieService;1"].
            getService(Ci.nsICookieService);
 
 do_check_true(conn.schemaVersion, 8);
 let stmt = conn.createStatement("SELECT sql FROM sqlite_master " +
-                                "WHERE type = 'table' AND " +
-                                "      name = 'moz_cookies'");
-do_check_true(stmt.executeStep());
-let sql = stmt.getString(0);
-do_check_eq(sql.indexOf("appId"), -1);
+                                  "WHERE type = 'table' AND " +
+                                  "      name = 'moz_cookies'");
+try {
+  do_check_true(stmt.executeStep());
+  let sql = stmt.getString(0);
+  do_check_eq(sql.indexOf("appId"), -1);
+} finally {
+  stmt.finalize();
+}
 
 stmt = conn.createStatement("SELECT * FROM moz_cookies " +
                             "WHERE baseDomain = 'foo.com' AND " +
                             "      host = '.foo.com' AND " +
                             "      name = 'foo' AND " +
                             "      value = 'bar=baz' AND " +
                             "      path = '/' AND " +
                             "      expiry = " + now + " AND " +
                             "      lastAccessed = " + now + " AND " +
                             "      creationTime = " + now + " AND " +
                             "      isSecure = 1 AND " +
                             "      isHttpOnly = 1");
-do_check_true(stmt.executeStep());
-
+try {
+  do_check_true(stmt.executeStep());
+} finally {
+  stmt.finalize();
+}
 conn.close();
--- a/services/common/tests/unit/test_async_querySpinningly.js
+++ b/services/common/tests/unit/test_async_querySpinningly.js
@@ -14,26 +14,27 @@ const SQLITE_CONSTRAINT_VIOLATION = 19; 
 // This test is a bit hacky - it was originally written to use the
 // formhistory.sqlite database using the nsIFormHistory2 sync APIs. However,
 // that's now been deprecated in favour of the async FormHistory.jsm.
 // Rather than re-write the test completely, we cheat - we use FormHistory.jsm
 // to initialize the database, then we just re-open it for these tests.
 
 // Init the forms database.
 FormHistory.schemaVersion;
+FormHistory.shutdown();
 
 // and open the database it just created.
 let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
 dbFile.append("formhistory.sqlite");
 let dbConnection = Services.storage.openUnsharedDatabase(dbFile);
 
 do_register_cleanup(() => {
   let cb = Async.makeSpinningCallback();
   dbConnection.asyncClose(cb);
-  cb.wait();
+  return cb.wait();
 });
 
 function querySpinningly(query, names) {
   let q = dbConnection.createStatement(query);
   let r = Async.querySpinningly(q, names);
   q.finalize();
   return r;
 }
--- a/storage/SQLiteMutex.h
+++ b/storage/SQLiteMutex.h
@@ -45,31 +45,39 @@ public:
    */
   void initWithMutex(sqlite3_mutex *aMutex)
   {
     NS_ASSERTION(aMutex, "You must pass in a valid mutex!");
     NS_ASSERTION(!mMutex, "A mutex has already been set for this!");
     mMutex = aMutex;
   }
 
+  /**
+   * After a connection has been successfully closed, its mutex is a dangling
+   * pointer, and as such it should be destroyed.
+   */
+  void destroy() {
+    mMutex = NULL;
+  }
+
 #if !defined(DEBUG) || defined(MOZ_SYSTEM_SQLITE)
   /**
    * Acquires the mutex.
    */
   void lock()
   {
-    sqlite3_mutex_enter(mMutex);
+    ::sqlite3_mutex_enter(mMutex);
   }
 
   /**
    * Releases the mutex.
    */
   void unlock()
   {
-    sqlite3_mutex_leave(mMutex);
+    ::sqlite3_mutex_leave(mMutex);
   }
 
   /**
    * Asserts that the current thread owns the mutex.
    */
   void assertCurrentThreadOwns()
   {
   }
@@ -79,48 +87,48 @@ public:
    */
   void assertNotCurrentThreadOwns()
   {
   }
 
 #else
   void lock()
   {
-    NS_ASSERTION(mMutex, "No mutex associated with this wrapper!");
+    MOZ_ASSERT(mMutex, "No mutex associated with this wrapper!");
 
     // While SQLite Mutexes may be recursive, in our own code we do not want to
     // treat them as such.
 
     CheckAcquire();
-    sqlite3_mutex_enter(mMutex);
+    ::sqlite3_mutex_enter(mMutex);
     Acquire(); // Call is protected by us holding the mutex.
   }
 
   void unlock()
   {
-    NS_ASSERTION(mMutex, "No mutex associated with this wrapper!");
+    MOZ_ASSERT(mMutex, "No mutex associated with this wrapper!");
 
     // While SQLite Mutexes may be recursive, in our own code we do not want to
     // treat them as such.
     Release(); // Call is protected by us holding the mutex.
-    sqlite3_mutex_leave(mMutex);
+    ::sqlite3_mutex_leave(mMutex);
   }
 
   void assertCurrentThreadOwns()
   {
-    NS_ASSERTION(mMutex, "No mutex associated with this wrapper!");
-    NS_ASSERTION(sqlite3_mutex_held(mMutex),
-                 "Mutex is not held, but we expect it to be!");
+    MOZ_ASSERT(mMutex, "No mutex associated with this wrapper!");
+    MOZ_ASSERT(sqlite3_mutex_held(mMutex),
+               "Mutex is not held, but we expect it to be!");
   }
 
   void assertNotCurrentThreadOwns()
   {
-    NS_ASSERTION(mMutex, "No mutex associated with this wrapper!");
-    NS_ASSERTION(sqlite3_mutex_notheld(mMutex),
-                 "Mutex is held, but we expect it to not be!");
+    MOZ_ASSERT(mMutex, "No mutex associated with this wrapper!");
+    MOZ_ASSERT(sqlite3_mutex_notheld(mMutex),
+               "Mutex is held, but we expect it to not be!");
   }
 #endif // ifndef DEBUG
 
 private:
   sqlite3_mutex *mMutex;
 };
 
 /**
--- a/storage/mozStorageAsyncStatement.cpp
+++ b/storage/mozStorageAsyncStatement.cpp
@@ -113,17 +113,17 @@ AsyncStatement::AsyncStatement()
 }
 
 nsresult
 AsyncStatement::initialize(Connection *aDBConnection,
                            sqlite3 *aNativeConnection,
                            const nsACString &aSQLStatement)
 {
   MOZ_ASSERT(aDBConnection, "No database connection given!");
-  MOZ_ASSERT(!aDBConnection->isClosed(), "Database connection should be valid");
+  MOZ_ASSERT(aDBConnection->isConnectionReadyOnThisThread(), "Database connection should be valid");
   MOZ_ASSERT(aNativeConnection, "No native connection given!");
 
   mDBConnection = aDBConnection;
   mNativeConnection = aNativeConnection;
   mSQLString = aSQLStatement;
 
   MOZ_LOG(gStorageLog, LogLevel::Debug, ("Inited async statement '%s' (0x%p)",
                                       mSQLString.get(), this));
--- a/storage/mozStorageAsyncStatementExecution.cpp
+++ b/storage/mozStorageAsyncStatementExecution.cpp
@@ -578,17 +578,17 @@ AsyncExecuteStatements::Cancel()
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIRunnable
 
 NS_IMETHODIMP
 AsyncExecuteStatements::Run()
 {
-  MOZ_ASSERT(!mConnection->isClosed());
+  MOZ_ASSERT(mConnection->isConnectionReadyOnThisThread());
 
   // Do not run if we have been canceled.
   {
     MutexAutoLock lockedScope(mMutex);
     if (mCancelRequested)
       mState = CANCELED;
   }
   if (mState == CANCELED)
--- a/storage/mozStorageConnection.cpp
+++ b/storage/mozStorageConnection.cpp
@@ -15,16 +15,17 @@
 #include "nsIFileURL.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/quota/QuotaObject.h"
+#include "mozilla/ScopeExit.h"
 
 #include "mozIStorageAggregateFunction.h"
 #include "mozIStorageCompletionCallback.h"
 #include "mozIStorageFunction.h"
 
 #include "mozStorageAsyncStatementExecution.h"
 #include "mozStorageSQLFunctions.h"
 #include "mozStorageConnection.h"
@@ -375,37 +376,31 @@ WaitForUnlockNotify(sqlite3* aDatabase)
 
 namespace {
 
 class AsyncCloseConnection final: public Runnable
 {
 public:
   AsyncCloseConnection(Connection *aConnection,
                        sqlite3 *aNativeConnection,
-                       nsIRunnable *aCallbackEvent,
-                       already_AddRefed<nsIThread> aAsyncExecutionThread)
+                       nsIRunnable *aCallbackEvent)
   : mConnection(aConnection)
   , mNativeConnection(aNativeConnection)
   , mCallbackEvent(aCallbackEvent)
-  , mAsyncExecutionThread(aAsyncExecutionThread)
   {
   }
 
   NS_IMETHOD Run() override
   {
-#ifdef DEBUG
     // This code is executed on the background thread
-    bool onAsyncThread = false;
-    (void)mAsyncExecutionThread->IsOnCurrentThread(&onAsyncThread);
-    MOZ_ASSERT(onAsyncThread);
-#endif // DEBUG
+    MOZ_ASSERT(NS_GetCurrentThread() != mConnection->threadOpenedOn);
 
-    nsCOMPtr<nsIRunnable> event = NewRunnableMethod<nsCOMPtr<nsIThread>>
-      (mConnection, &Connection::shutdownAsyncThread, mAsyncExecutionThread);
-    (void)NS_DispatchToMainThread(event);
+    nsCOMPtr<nsIRunnable> event =
+      NewRunnableMethod(mConnection, &Connection::shutdownAsyncThread);
+    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(event));
 
     // Internal close.
     (void)mConnection->internalClose(mNativeConnection);
 
     // Callback
     if (mCallbackEvent) {
       nsCOMPtr<nsIThread> thread;
       (void)NS_GetMainThread(getter_AddRefs(thread));
@@ -418,17 +413,16 @@ public:
   ~AsyncCloseConnection() override {
     NS_ReleaseOnMainThread(mConnection.forget());
     NS_ReleaseOnMainThread(mCallbackEvent.forget());
   }
 private:
   RefPtr<Connection> mConnection;
   sqlite3 *mNativeConnection;
   nsCOMPtr<nsIRunnable> mCallbackEvent;
-  nsCOMPtr<nsIThread> mAsyncExecutionThread;
 };
 
 /**
  * An event used to initialize the clone of a connection.
  *
  * Must be executed on the clone's async execution thread.
  */
 class AsyncInitializeClone final: public Runnable
@@ -503,19 +497,16 @@ Connection::Connection(Service *aService
                        int aFlags,
                        bool aAsyncOnly,
                        bool aIgnoreLockingMode)
 : sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex")
 , sharedDBMutex("Connection::sharedDBMutex")
 , threadOpenedOn(do_GetCurrentThread())
 , mDBConn(nullptr)
 , mAsyncExecutionThreadShuttingDown(false)
-#ifdef DEBUG
-, mAsyncExecutionThreadIsAlive(false)
-#endif
 , mConnectionClosed(false)
 , mTransactionInProgress(false)
 , mProgressHandler(nullptr)
 , mFlags(aFlags)
 , mIgnoreLockingMode(aIgnoreLockingMode)
 , mStorageService(aService)
 , mAsyncOnly(aAsyncOnly)
 {
@@ -524,19 +515,17 @@ Connection::Connection(Service *aService
   mStorageService->registerConnection(this);
 }
 
 Connection::~Connection()
 {
   (void)Close();
 
   MOZ_ASSERT(!mAsyncExecutionThread,
-             "AsyncClose has not been invoked on this connection!");
-  MOZ_ASSERT(!mAsyncExecutionThreadIsAlive,
-             "The async execution thread should have been shutdown!");
+             "The async thread has not been shutdown properly!");
 }
 
 NS_IMPL_ADDREF(Connection)
 
 NS_INTERFACE_MAP_BEGIN(Connection)
   NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncConnection)
   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(mozIStorageConnection, !mAsyncOnly)
@@ -576,37 +565,34 @@ Connection::getSqliteRuntimeStatus(int32
   if (aMaxValue)
     *aMaxValue = max;
   return curr;
 }
 
 nsIEventTarget *
 Connection::getAsyncExecutionTarget()
 {
-  NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
+  NS_ENSURE_TRUE(threadOpenedOn == NS_GetCurrentThread(), nullptr);
 
-  // If we are shutting down the asynchronous thread, don't hand out any more
-  // references to the thread.
-  if (mAsyncExecutionThreadShuttingDown)
+  // Don't return the asynchronous thread if we are shutting down.
+  if (mAsyncExecutionThreadShuttingDown) {
     return nullptr;
+  }
 
+  // Create the async thread if there's none yet.
   if (!mAsyncExecutionThread) {
     static nsThreadPoolNaming naming;
     nsresult rv = NS_NewNamedThread(naming.GetNextThreadName("mozStorage"),
                                     getter_AddRefs(mAsyncExecutionThread));
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to create async thread.");
       return nullptr;
     }
   }
 
-#ifdef DEBUG
-  mAsyncExecutionThreadIsAlive = true;
-#endif
-
   return mAsyncExecutionThread;
 }
 
 nsresult
 Connection::initialize()
 {
   NS_ASSERTION (!mDBConn, "Initialize called on already opened database!");
   MOZ_ASSERT(!mIgnoreLockingMode, "Can't ignore locking on an in-memory db.");
@@ -699,16 +685,26 @@ Connection::initialize(nsIFileURL *aFile
   return NS_OK;
 }
 
 nsresult
 Connection::initializeInternal()
 {
   MOZ_ASSERT(mDBConn);
 
+  auto guard = MakeScopeExit([&]() {
+    {
+      MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
+      mConnectionClosed = true;
+    }
+    MOZ_ALWAYS_TRUE(::sqlite3_close(mDBConn) == SQLITE_OK);
+    mDBConn = nullptr;
+    sharedDBMutex.destroy();
+  });
+
   if (mFileURL) {
     const char* dbPath = ::sqlite3_db_filename(mDBConn, "main");
     MOZ_ASSERT(dbPath);
 
     const char* telemetryFilename =
       ::sqlite3_uri_parameter(dbPath, "telemetryFilename");
     if (telemetryFilename) {
       if (NS_WARN_IF(*telemetryFilename == '\0')) {
@@ -740,49 +736,45 @@ Connection::initializeInternal()
   int64_t pageSize = Service::getDefaultPageSize();
 
   // Set page_size to the preferred default value.  This is effective only if
   // the database has just been created, otherwise, if the database does not
   // use WAL journal mode, a VACUUM operation will updated its page_size.
   nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
                               "PRAGMA page_size = ");
   pageSizeQuery.AppendInt(pageSize);
-  nsresult rv = ExecuteSimpleSQL(pageSizeQuery);
-  NS_ENSURE_SUCCESS(rv, rv);
+  int srv = executeSql(mDBConn, pageSizeQuery.get());
+  if (srv != SQLITE_OK) {
+    return convertResultCode(srv);
+  }
 
   // Setting the cache_size forces the database open, verifying if it is valid
   // or corrupt.  So this is executed regardless it being actually needed.
   // The cache_size is calculated from the actual page_size, to save memory.
   nsAutoCString cacheSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
                                "PRAGMA cache_size = ");
   cacheSizeQuery.AppendInt(-MAX_CACHE_SIZE_KIBIBYTES);
-  int srv = executeSql(mDBConn, cacheSizeQuery.get());
+  srv = executeSql(mDBConn, cacheSizeQuery.get());
   if (srv != SQLITE_OK) {
-    ::sqlite3_close(mDBConn);
-    mDBConn = nullptr;
     return convertResultCode(srv);
   }
 
 #if defined(MOZ_MEMORY_TEMP_STORE_PRAGMA)
   (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA temp_store = 2;"));
 #endif
 
   // Register our built-in SQL functions.
   srv = registerFunctions(mDBConn);
   if (srv != SQLITE_OK) {
-    ::sqlite3_close(mDBConn);
-    mDBConn = nullptr;
     return convertResultCode(srv);
   }
 
   // Register our built-in SQL collating sequences.
   srv = registerCollations(mDBConn, mStorageService);
   if (srv != SQLITE_OK) {
-    ::sqlite3_close(mDBConn);
-    mDBConn = nullptr;
     return convertResultCode(srv);
   }
 
   // Set the synchronous PRAGMA, according to the preference.
   switch (Service::getSynchronousPref()) {
     case 2:
       (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "PRAGMA synchronous = FULL;"));
@@ -793,20 +785,38 @@ Connection::initializeInternal()
       break;
     case 1:
     default:
       (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "PRAGMA synchronous = NORMAL;"));
       break;
   }
 
+  // Initialization succeeded, we can stop guarding for failures.
+  guard.release();
   return NS_OK;
 }
 
 nsresult
+Connection::initializeOnAsyncThread(nsIFile* aStorageFile) {
+  MOZ_ASSERT(threadOpenedOn != NS_GetCurrentThread());
+  nsresult rv = aStorageFile ? initialize(aStorageFile)
+                             : initialize();
+  if (NS_FAILED(rv)) {
+    // Shutdown the async thread, since initialization failed.
+    MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
+    mAsyncExecutionThreadShuttingDown = true;
+    nsCOMPtr<nsIRunnable> event =
+      NewRunnableMethod(this, &Connection::shutdownAsyncThread);
+    Unused << NS_DispatchToMainThread(event);
+  }
+  return rv;
+}
+
+nsresult
 Connection::databaseElementExists(enum DatabaseElementType aElementType,
                                   const nsACString &aElementName,
                                   bool *_exists)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
   // When constructing the query, make sure to SELECT the correct db's sqlite_master
   // if the user is prefixing the element with a specific db. ex: sample.test
@@ -902,82 +912,87 @@ Connection::setClosedState()
   }
 
   // Flag that we are shutting down the async thread, so that
   // getAsyncExecutionTarget knows not to expose/create the async thread.
   {
     MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
     NS_ENSURE_FALSE(mAsyncExecutionThreadShuttingDown, NS_ERROR_UNEXPECTED);
     mAsyncExecutionThreadShuttingDown = true;
+
+    // Set the property to null before closing the connection, otherwise the other
+    // functions in the module may try to use the connection after it is closed.
+    mDBConn = nullptr;
   }
-
-  // Set the property to null before closing the connection, otherwise the other
-  // functions in the module may try to use the connection after it is closed.
-  mDBConn = nullptr;
-
   return NS_OK;
 }
 
 bool
 Connection::connectionReady()
 {
   return mDBConn != nullptr;
 }
 
 bool
+Connection::isConnectionReadyOnThisThread()
+{
+  MOZ_ASSERT_IF(mDBConn, !mConnectionClosed);
+  if (mAsyncExecutionThread &&
+      mAsyncExecutionThread->IsOnCurrentThread()) {
+    return true;
+  }
+  return  connectionReady();
+}
+
+bool
 Connection::isClosing()
 {
-  bool shuttingDown = false;
-  {
-    MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
-    shuttingDown = mAsyncExecutionThreadShuttingDown;
-  }
-  return shuttingDown && !isClosed();
+  MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
+  return mAsyncExecutionThreadShuttingDown && !mConnectionClosed;
 }
 
 bool
 Connection::isClosed()
 {
   MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
   return mConnectionClosed;
 }
 
 bool
+Connection::isClosed(MutexAutoLock& lock)
+{
+  return mConnectionClosed;
+}
+
+bool
 Connection::isAsyncExecutionThreadAvailable()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  return !!mAsyncExecutionThread;
+  MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
+  return mAsyncExecutionThread && !mAsyncExecutionThreadShuttingDown;
 }
 
 void
-Connection::shutdownAsyncThread(nsIThread *aThread) {
-  MOZ_ASSERT(!mAsyncExecutionThread);
-  MOZ_ASSERT(mAsyncExecutionThreadIsAlive);
+Connection::shutdownAsyncThread() {
+  MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
+  MOZ_ASSERT(mAsyncExecutionThread);
   MOZ_ASSERT(mAsyncExecutionThreadShuttingDown);
 
-  DebugOnly<nsresult> rv = aThread->Shutdown();
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-#ifdef DEBUG
-  mAsyncExecutionThreadIsAlive = false;
-#endif
+  MOZ_ALWAYS_SUCCEEDS(mAsyncExecutionThread->Shutdown());
+  mAsyncExecutionThread = nullptr;
 }
 
 nsresult
 Connection::internalClose(sqlite3 *aNativeConnection)
 {
-  // Sanity checks to make sure we are in the proper state before calling this.
-  // aNativeConnection can be null if OpenAsyncDatabase failed and is now just
-  // cleaning up the async thread.
-  MOZ_ASSERT(!isClosed());
-
 #ifdef DEBUG
   { // Make sure we have marked our async thread as shutting down.
     MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
-    NS_ASSERTION(mAsyncExecutionThreadShuttingDown,
-                 "Did not call setClosedState!");
+    MOZ_ASSERT(mAsyncExecutionThreadShuttingDown,
+               "Did not call setClosedState!");
+    MOZ_ASSERT(!isClosed(lockedScope), "Unexpected closed state");
   }
 #endif // DEBUG
 
   if (MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) {
     nsAutoCString leafName(":memory");
     if (mDatabaseFile)
         (void)mDatabaseFile->GetNativeLeafName(leafName);
     MOZ_LOG(gStorageLog, LogLevel::Debug, ("Closing connection to '%s'",
@@ -994,21 +1009,23 @@ Connection::internalClose(sqlite3 *aNati
     MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
     mConnectionClosed = true;
   }
 
   // Nothing else needs to be done if we don't have a connection here.
   if (!aNativeConnection)
     return NS_OK;
 
-  int srv = sqlite3_close(aNativeConnection);
+  int srv = ::sqlite3_close(aNativeConnection);
 
   if (srv == SQLITE_BUSY) {
+    // Nothing else should change the connection or statements status until we
+    // are done here.
+    SQLiteMutexAutoLock lockedScope(sharedDBMutex);
     // We still have non-finalized statements. Finalize them.
-
     sqlite3_stmt *stmt = nullptr;
     while ((stmt = ::sqlite3_next_stmt(aNativeConnection, stmt))) {
       MOZ_LOG(gStorageLog, LogLevel::Debug,
              ("Auto-finalizing SQL statement '%s' (%p)",
               ::sqlite3_sql(stmt),
               stmt));
 
 #ifdef DEBUG
@@ -1036,21 +1053,22 @@ Connection::internalClose(sqlite3 *aNati
       if (srv == SQLITE_OK) {
         stmt = nullptr;
       }
     }
 
     // Now that all statements have been finalized, we
     // should be able to close.
     srv = ::sqlite3_close(aNativeConnection);
-
   }
 
-  if (srv != SQLITE_OK) {
-    MOZ_ASSERT(srv == SQLITE_OK,
+  if (srv == SQLITE_OK) {
+    sharedDBMutex.destroy();
+  } else {
+    MOZ_ASSERT(false,
                "sqlite3_close failed. There are probably outstanding statements that are listed above!");
   }
 
   return convertResultCode(srv);
 }
 
 nsCString
 Connection::getFilename()
@@ -1069,17 +1087,17 @@ Connection::stepStatement(sqlite3 *aNati
   bool checkedMainThread = false;
   TimeStamp startTime = TimeStamp::Now();
 
   // The connection may have been closed if the executing statement has been
   // created and cached after a call to asyncClose() but before the actual
   // sqlite3_close().  This usually happens when other tasks using cached
   // statements are asynchronously scheduled for execution and any of them ends
   // up after asyncClose. See bug 728653 for details.
-  if (isClosed())
+  if (!isConnectionReadyOnThisThread())
     return SQLITE_MISUSE;
 
   (void)::sqlite3_extended_result_codes(aNativeConnection, 1);
 
   int srv;
   while ((srv = ::sqlite3_step(aStatement)) == SQLITE_LOCKED_SHAREDCACHE) {
     if (!checkedMainThread) {
       checkedMainThread = true;
@@ -1114,17 +1132,17 @@ Connection::stepStatement(sqlite3 *aNati
 }
 
 int
 Connection::prepareStatement(sqlite3 *aNativeConnection, const nsCString &aSQL,
                              sqlite3_stmt **_stmt)
 {
   // We should not even try to prepare statements after the connection has
   // been closed.
-  if (isClosed())
+  if (!isConnectionReadyOnThisThread())
     return SQLITE_MISUSE;
 
   bool checkedMainThread = false;
 
   (void)::sqlite3_extended_result_codes(aNativeConnection, 1);
 
   int srv;
   while((srv = ::sqlite3_prepare_v2(aNativeConnection,
@@ -1171,17 +1189,17 @@ Connection::prepareStatement(sqlite3 *aN
 
   return rc;
 }
 
 
 int
 Connection::executeSql(sqlite3 *aNativeConnection, const char *aSqlString)
 {
-  if (isClosed())
+  if (!isConnectionReadyOnThisThread())
     return SQLITE_MISUSE;
 
   TimeStamp startTime = TimeStamp::Now();
   int srv = ::sqlite3_exec(aNativeConnection, aSqlString, nullptr, nullptr,
                            nullptr);
 
   // Report very slow SQL statements to Telemetry
   TimeDuration duration = TimeStamp::Now() - startTime;
@@ -1246,16 +1264,20 @@ Connection::Close()
 
   return internalClose(nativeConn);
 }
 
 NS_IMETHODIMP
 Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
 {
   NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
+  // Check if AsyncClose or Close were already invoked.
+  if (!mDBConn) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   // The two relevant factors at this point are whether we have a database
   // connection and whether we have an async execution thread.  Here's what the
   // states mean and how we handle them:
   //
   // - (mDBConn && asyncThread): The expected case where we are either an
   //   async connection or a sync connection that has been used asynchronously.
   //   Either way the caller must call us and not Close().  Nothing surprising
@@ -1329,19 +1351,17 @@ Connection::AsyncClose(mozIStorageComple
   // off it, to pass it through the close procedure.
   sqlite3 *nativeConn = mDBConn;
   nsresult rv = setClosedState();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Create and dispatch our close event to the background thread.
   nsCOMPtr<nsIRunnable> closeEvent = new AsyncCloseConnection(this,
                                                               nativeConn,
-                                                              completeEvent,
-                                                              mAsyncExecutionThread.forget());
-
+                                                              completeEvent);
   rv = asyncThread->Dispatch(closeEvent, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::AsyncClone(bool aReadOnly,
@@ -1512,16 +1532,17 @@ Connection::GetDefaultPageSize(int32_t *
 {
   *_defaultPageSize = Service::getDefaultPageSize();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::GetConnectionReady(bool *_ready)
 {
+  MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
   *_ready = connectionReady();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::GetDatabaseFile(nsIFile **_dbFile)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
--- a/storage/mozStorageConnection.h
+++ b/storage/mozStorageConnection.h
@@ -99,16 +99,21 @@ public:
    *
    * @param aFileURL
    *        The nsIFileURL of the location of the database to open, or create if it
    *        does not exist.
    */
   nsresult initialize(nsIFileURL *aFileURL);
 
   /**
+   * Same as initialize, but to be used on the async thread.
+   */
+  nsresult initializeOnAsyncThread(nsIFile* aStorageFile);
+
+  /**
    * Fetches runtime status information for this connection.
    *
    * @param aStatusOption One of the SQLITE_DBSTATUS options defined at
    *        http://www.sqlite.org/c3ref/c_dbstatus_options.html
    * @param [optional] aMaxValue if provided, will be set to the highest
    *        istantaneous value.
    * @return the current value for the specified option.
    */
@@ -134,19 +139,20 @@ public:
     return mDBConn && static_cast<bool>(::sqlite3_get_autocommit(mDBConn));
   };
 
   /**
    * Lazily creates and returns a background execution thread.  In the future,
    * the thread may be re-claimed if left idle, so you should call this
    * method just before you dispatch and not save the reference.
    *
-   * This must be called from the main thread.
+   * This must be called from the opener thread.
    *
-   * @returns an event target suitable for asynchronous statement execution.
+   * @return an event target suitable for asynchronous statement execution.
+   * @note This method will return null once AsyncClose() has been called.
    */
   nsIEventTarget *getAsyncExecutionTarget();
 
   /**
    * Mutex used by asynchronous statements to protect state.  The mutex is
    * declared on the connection object because there is no contention between
    * asynchronous statements (they are serialized on mAsyncExecutionThread).
    * Currently protects:
@@ -173,17 +179,17 @@ public:
   /**
    * Closes the SQLite database, and warns about any non-finalized statements.
    */
   nsresult internalClose(sqlite3 *aDBConn);
 
   /**
    * Shuts down the passed-in async thread.
    */
-  void shutdownAsyncThread(nsIThread *aAsyncThread);
+  void shutdownAsyncThread();
 
   /**
    * Obtains the filename of the connection.  Useful for logging.
    */
   nsCString getFilename();
 
   /**
    * Creates an sqlite3 prepared statement object from an SQL string.
@@ -219,33 +225,56 @@ public:
   nsresult beginTransactionInternal(sqlite3 *aNativeConnection,
                                     int32_t aTransactionType=TRANSACTION_DEFERRED);
   nsresult commitTransactionInternal(sqlite3 *aNativeConnection);
   nsresult rollbackTransactionInternal(sqlite3 *aNativeConnection);
 
   bool connectionReady();
 
   /**
-   * True if this connection is shutting down but not yet closed.
+   * Thread-aware version of connectionReady, results per caller's thread are:
+   *  - owner thread: Same as connectionReady().  True means we have a valid,
+   *    un-closed database connection and it's not going away until you invoke
+   *    Close() or AsyncClose().
+   *  - async thread: Returns true at all times because you can't schedule
+   *    runnables against the async thread after AsyncClose() has been called.
+   *    Therefore, the connection is still around if your code is running.
+   *  - any other thread: Race-prone Lies!  If you are main-thread code in
+   *    mozStorageService iterating over the list of connections, you need to
+   *    acquire the sharedAsyncExecutionMutex for the connection, invoke
+   *    connectionReady() while holding it, and then continue to hold it while
+   *    you do whatever you need to do.  This is because of off-main-thread
+   *    consumers like dom/cache and IndexedDB and other QuotaManager clients.
+   */
+  bool isConnectionReadyOnThisThread();
+
+  /**
+   * True if this connection has inited shutdown.
    */
   bool isClosing();
 
   /**
    * True if the underlying connection is closed.
    * Any sqlite resources may be lost when this returns true, so nothing should
    * try to use them.
+   * This locks on sharedAsyncExecutionMutex.
    */
   bool isClosed();
 
   /**
-  * True if the async execution thread is alive and able to be used (i.e., it
-  * is not in the process of shutting down.)
-  *
-  * This must be called from the main thread.
-  */
+   * Same as isClosed(), but takes a proof-of-lock instead of locking internally.
+   */
+  bool isClosed(MutexAutoLock& lock);
+
+  /**
+   * True if the async execution thread is alive and able to be used (i.e., it
+   * is not in the process of shutting down.)
+   *
+   * This must be called from the opener thread.
+   */
   bool isAsyncExecutionThreadAvailable();
 
   nsresult initializeClone(Connection *aClone, bool aReadOnly);
 
 private:
   ~Connection();
   nsresult initializeInternal();
 
@@ -311,17 +340,17 @@ private:
   */
   nsCString mTelemetryFilename;
 
   /**
    * Lazily created thread for asynchronous statement execution.  Consumers
    * should use getAsyncExecutionTarget rather than directly accessing this
    * field.
    *
-   * This must be accessed only on the main thread.
+   * This must be modified only on the opener thread.
    */
   nsCOMPtr<nsIThread> mAsyncExecutionThread;
 
   /**
    * Set to true by Close() or AsyncClose() prior to shutdown.
    *
    * If false, we guarantee both that the underlying sqlite3 database
    * connection is still open and that getAsyncExecutionTarget() can
@@ -331,24 +360,16 @@ private:
    * returns null.
    *
    * This variable should be accessed while holding the
    * sharedAsyncExecutionMutex.
    */
   bool mAsyncExecutionThreadShuttingDown;
 
   /**
-   * Tracks whether the async thread has been initialized and Shutdown() has
-   * not yet been invoked on it.
-   */
-#ifdef DEBUG
-  bool mAsyncExecutionThreadIsAlive;
-#endif
-
-  /**
    * Set to true just prior to calling sqlite3_close on the
    * connection.
    *
    * This variable should be accessed while holding the
    * sharedAsyncExecutionMutex.
    */
   bool mConnectionClosed;
 
--- a/storage/mozStorageService.cpp
+++ b/storage/mozStorageService.cpp
@@ -124,19 +124,21 @@ Service::CollectReports(nsIHandleReportC
   {
     nsTArray<RefPtr<Connection> > connections;
     getConnections(connections);
 
     for (uint32_t i = 0; i < connections.Length(); i++) {
       RefPtr<Connection> &conn = connections[i];
 
       // Someone may have closed the Connection, in which case we skip it.
-      bool isReady;
-      (void)conn->GetConnectionReady(&isReady);
-      if (!isReady) {
+      // Note that we have consumers of the synchronous API that are off the
+      // main-thread, like the DOM Cache and IndexedDB, and as such we must be
+      // sure that we have a connection.
+      MutexAutoLock lockedAsyncScope(conn->sharedAsyncExecutionMutex);
+      if (!conn->connectionReady()) {
           continue;
       }
 
       nsCString pathHead("explicit/storage/sqlite/");
       // This filename isn't privacy-sensitive, and so is never anonymized.
       pathHead.Append(conn->getFilename());
       pathHead.Append('/');
 
@@ -343,16 +345,18 @@ Service::getConnections(/* inout */ nsTA
 void
 Service::minimizeMemory()
 {
   nsTArray<RefPtr<Connection> > connections;
   getConnections(connections);
 
   for (uint32_t i = 0; i < connections.Length(); i++) {
     RefPtr<Connection> conn = connections[i];
+    // For non-main-thread owning/opening threads, we may be racing against them
+    // closing their connection or their thread.  That's okay, see below.
     if (!conn->connectionReady())
       continue;
 
     NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
     nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface(
       NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn));
     bool onOpenedThread = false;
 
@@ -371,20 +375,24 @@ Service::minimizeMemory()
           conn->ExecuteSimpleSQLAsync(shrinkPragma, nullptr, getter_AddRefs(ps));
         MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
       } else {
         conn->ExecuteSimpleSQL(shrinkPragma);
       }
     } else {
       // We are on the wrong thread, the query should be executed on the
       // opener thread, so we must dispatch to it.
+      // It's possible the connection is already closed or will be closed by the
+      // time our runnable runs.  ExecuteSimpleSQL will safely return with a
+      // failure in that case.  If the thread is shutting down or shut down, the
+      // dispatch will fail and that's okay.
       nsCOMPtr<nsIRunnable> event =
         NewRunnableMethod<const nsCString>(
           conn, &Connection::ExecuteSimpleSQL, shrinkPragma);
-      conn->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
+      Unused << conn->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
     }
   }
 }
 
 void
 Service::shutdown()
 {
   NS_IF_RELEASE(sXPConnect);
@@ -674,27 +682,18 @@ public:
     , mCallback(aCallback)
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
   NS_IMETHOD Run() override
   {
     MOZ_ASSERT(!NS_IsMainThread());
-    nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile)
-                               : mConnection->initialize();
+    nsresult rv = mConnection->initializeOnAsyncThread(mStorageFile);
     if (NS_FAILED(rv)) {
-      nsCOMPtr<nsIRunnable> closeRunnable =
-        NewRunnableMethod<mozIStorageCompletionCallback*>(
-          mConnection.get(),
-          &Connection::AsyncClose,
-          nullptr);
-      MOZ_ASSERT(closeRunnable);
-      MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(closeRunnable));
-
       return DispatchResult(rv, nullptr);
     }
 
     if (mGrowthIncrement >= 0) {
       // Ignore errors. In the future, we might wish to log them.
       (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString());
     }
 
@@ -933,27 +932,26 @@ Service::Observe(nsISupports *, const ch
     nsCOMPtr<nsIObserverService> os =
       mozilla::services::GetObserverService();
 
     for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
       (void)os->RemoveObserver(this, sObserverTopics[i]);
     }
 
     SpinEventLoopUntil([&]() -> bool {
-        // We must wait until all connections are closed.
-        nsTArray<RefPtr<Connection>> connections;
-        getConnections(connections);
-        for (auto& conn : connections) {
-          if (conn->isClosing()) {
-            return false;
-          }
+      // We must wait until all the closing connections are closed.
+      nsTArray<RefPtr<Connection>> connections;
+      getConnections(connections);
+      for (auto& conn : connections) {
+        if (conn->isClosing()) {
+          return false;
         }
-
-        return true;
-      });
+      }
+      return true;
+    });
 
     if (gShutdownChecks == SCM_CRASH) {
       nsTArray<RefPtr<Connection> > connections;
       getConnections(connections);
       for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
         if (!connections[i]->isClosed()) {
           MOZ_CRASH();
         }
--- a/storage/mozStorageStatement.cpp
+++ b/storage/mozStorageStatement.cpp
@@ -118,17 +118,18 @@ Statement::Statement()
 }
 
 nsresult
 Statement::initialize(Connection *aDBConnection,
                       sqlite3 *aNativeConnection,
                       const nsACString &aSQLStatement)
 {
   MOZ_ASSERT(aDBConnection, "No database connection given!");
-  MOZ_ASSERT(!aDBConnection->isClosed(), "Database connection should be valid");
+  MOZ_ASSERT(aDBConnection->isConnectionReadyOnThisThread(),
+             "Database connection should be valid");
   MOZ_ASSERT(!mDBStatement, "Statement already initialized!");
   MOZ_ASSERT(aNativeConnection, "No native connection given!");
 
   int srv = aDBConnection->prepareStatement(aNativeConnection,
                                             PromiseFlatCString(aSQLStatement),
                                             &mDBStatement);
   if (srv != SQLITE_OK) {
       MOZ_LOG(gStorageLog, LogLevel::Error,
@@ -339,78 +340,53 @@ Statement::Finalize()
 nsresult
 Statement::internalFinalize(bool aDestructing)
 {
   if (!mDBStatement)
     return NS_OK;
 
   int srv = SQLITE_OK;
 
-  if (!mDBConnection->isClosed()) {
-    //
-    // The connection is still open. While statement finalization and
-    // closing may, in some cases, take place in two distinct threads,
-    // we have a guarantee that the connection will remain open until
-    // this method terminates:
-    //
-    // a. The connection will be closed synchronously. In this case,
-    // there is no race condition, as everything takes place on the
-    // same thread.
-    //
-    // b. The connection is closed asynchronously and this code is
-    // executed on the opener thread. In this case, asyncClose() has
-    // not been called yet and will not be called before we return
-    // from this function.
-    //
-    // c. The connection is closed asynchronously and this code is
-    // executed on the async execution thread. In this case,
-    // AsyncCloseConnection::Run() has not been called yet and will
-    // not be called before we return from this function.
-    //
-    // In either case, the connection is still valid, hence closing
-    // here is safe.
-    //
-    MOZ_LOG(gStorageLog, LogLevel::Debug, ("Finalizing statement '%s' during garbage-collection",
-                                        ::sqlite3_sql(mDBStatement)));
-    srv = ::sqlite3_finalize(mDBStatement);
-  }
+  {
+    // If the statement ends up being finalized twice, the second finalization
+    // would apply to a dangling pointer and may cause unexpected consequences.
+    // Thus we must be sure that the connection state won't change during this
+    // operation, to avoid racing with finalizations made by the closing
+    // connection.  See Connection::internalClose().
+    MutexAutoLock lockedScope(mDBConnection->sharedAsyncExecutionMutex);
+    if (!mDBConnection->isClosed(lockedScope)) {
+      MOZ_LOG(gStorageLog, LogLevel::Debug, ("Finalizing statement '%s' during garbage-collection",
+                                          ::sqlite3_sql(mDBStatement)));
+      srv = ::sqlite3_finalize(mDBStatement);
+    }
 #ifdef DEBUG
-  else {
-    //
-    // The database connection is either closed or closing. The sqlite
-    // statement has either been finalized already by the connection
-    // or is about to be finalized by the connection.
-    //
-    // Finalizing it here would be useless and segfaultish.
-    //
-
-    SmprintfPointer msg = ::mozilla::Smprintf("SQL statement (%p) should have been finalized"
-      " before garbage-collection. For more details on this statement, set"
-      " NSPR_LOG_MESSAGES=mozStorage:5 .",
-      mDBStatement);
+    else {
+      // The database connection is closed. The sqlite
+      // statement has either been finalized already by the connection
+      // or is about to be finalized by the connection.
+      //
+      // Finalizing it here would be useless and segfaultish.
+      //
+      // Note that we can't display the statement itself, as the data structure
+      // is not valid anymore. However, the address shown here should help
+      // developers correlate with the more complete debug message triggered
+      // by AsyncClose().
 
-    //
-    // Note that we can't display the statement itself, as the data structure
-    // is not valid anymore. However, the address shown here should help
-    // developers correlate with the more complete debug message triggered
-    // by AsyncClose().
-    //
+      SmprintfPointer msg = ::mozilla::Smprintf("SQL statement (%p) should have been finalized"
+        " before garbage-collection. For more details on this statement, set"
+        " NSPR_LOG_MESSAGES=mozStorage:5 .",
+        mDBStatement);
+      NS_WARNING(msg.get());
 
-#if 0
-    // Deactivate the warning until we have fixed the exising culprit
-    // (see bug 914070).
-    NS_WARNING(msg.get());
-#endif // 0
-
-    // Use %s so we aren't exposing random strings to printf interpolation.
-    MOZ_LOG(gStorageLog, LogLevel::Warning, ("%s", msg.get()));
+      // Use %s so we aren't exposing random strings to printf interpolation.
+      MOZ_LOG(gStorageLog, LogLevel::Warning, ("%s", msg.get()));
+    }
+#endif // DEBUG
   }
 
-#endif
-
   mDBStatement = nullptr;
 
   if (mAsyncStatement) {
     // If the destructor called us, there are no pending async statements (they
     // hold a reference to us) and we can/must just kill the statement directly.
     if (aDestructing)
       destructorAsyncFinalize();
     else
@@ -588,17 +564,17 @@ Statement::ExecuteStep(bool *_moreResult
   // Bind any parameters first before executing.
   if (mParamsArray) {
     // If we have more than one row of parameters to bind, they shouldn't be
     // calling this method (and instead use executeAsync).
     if (mParamsArray->length() != 1)
       return NS_ERROR_UNEXPECTED;
 
     BindingParamsArray::iterator row = mParamsArray->begin();
-    nsCOMPtr<IStorageBindingParamsInternal> bindingInternal = 
+    nsCOMPtr<IStorageBindingParamsInternal> bindingInternal =
       do_QueryInterface(*row);
     nsCOMPtr<mozIStorageError> error = bindingInternal->bind(mDBStatement);
     if (error) {
       int32_t srv;
       (void)error->GetResult(&srv);
       return convertResultCode(srv);
     }
 
--- a/storage/test/unit/head_storage.js
+++ b/storage/test/unit/head_storage.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
     "resource://gre/modules/Promise.jsm");
 
 
 do_get_profile();
 var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
              getService(Ci.nsIProperties);
 
--- a/storage/test/unit/test_storage_connection.js
+++ b/storage/test/unit/test_storage_connection.js
@@ -231,41 +231,44 @@ add_task(function* test_asyncClose_succe
   stmt.executeAsync();
   stmt.finalize();
 
   yield asyncClose(getOpenedDatabase());
   // Reset gDBConn so that later tests will get a new connection object.
   gDBConn = null;
 });
 
-add_task(function* test_close_then_release_statement() {
-  // Testing the behavior in presence of a bad client that finalizes
-  // statements after the database has been closed (typically by
-  // letting the gc finalize the statement).
-  let db = getOpenedDatabase();
-  let stmt = createStatement("SELECT * FROM test -- test_close_then_release_statement");
-  db.close();
-  stmt.finalize(); // Finalize too late - this should not crash
-
-  // Reset gDBConn so that later tests will get a new connection object.
-  gDBConn = null;
-});
+// Would assert on debug builds.
+if (!AppConstants.DEBUG) {
+  add_task(function* test_close_then_release_statement() {
+    // Testing the behavior in presence of a bad client that finalizes
+    // statements after the database has been closed (typically by
+    // letting the gc finalize the statement).
+    let db = getOpenedDatabase();
+    let stmt = createStatement("SELECT * FROM test -- test_close_then_release_statement");
+    db.close();
+    stmt.finalize(); // Finalize too late - this should not crash
 
-add_task(function* test_asyncClose_then_release_statement() {
-  // Testing the behavior in presence of a bad client that finalizes
-  // statements after the database has been async closed (typically by
-  // letting the gc finalize the statement).
-  let db = getOpenedDatabase();
-  let stmt = createStatement("SELECT * FROM test -- test_asyncClose_then_release_statement");
-  yield asyncClose(db);
-  stmt.finalize(); // Finalize too late - this should not crash
+    // Reset gDBConn so that later tests will get a new connection object.
+    gDBConn = null;
+  });
 
-  // Reset gDBConn so that later tests will get a new connection object.
-  gDBConn = null;
-});
+  add_task(function* test_asyncClose_then_release_statement() {
+    // Testing the behavior in presence of a bad client that finalizes
+    // statements after the database has been async closed (typically by
+    // letting the gc finalize the statement).
+    let db = getOpenedDatabase();
+    let stmt = createStatement("SELECT * FROM test -- test_asyncClose_then_release_statement");
+    yield asyncClose(db);
+    stmt.finalize(); // Finalize too late - this should not crash
+
+    // Reset gDBConn so that later tests will get a new connection object.
+    gDBConn = null;
+  });
+}
 
 add_task(function* test_close_fails_with_async_statement_ran() {
   let deferred = Promise.defer();
   let stmt = createStatement("SELECT * FROM test");
   stmt.executeAsync();
   stmt.finalize();
 
   let db = getOpenedDatabase();
@@ -720,34 +723,40 @@ add_task(function* test_clone_attach_dat
     let db = getService().openUnsharedDatabase(file);
     conn.executeSimpleSQL(`ATTACH DATABASE '${db.databaseFile.path}' AS ${name}`);
     db.close();
   }
   attachDB(db1, "attached_1");
   attachDB(db1, "attached_2");
 
   // These should not throw.
-  db1.createStatement("SELECT * FROM attached_1.sqlite_master");
-  db1.createStatement("SELECT * FROM attached_2.sqlite_master");
+  let stmt = db1.createStatement("SELECT * FROM attached_1.sqlite_master");
+  stmt.finalize();
+  stmt = db1.createStatement("SELECT * FROM attached_2.sqlite_master");
+  stmt.finalize();
 
   // R/W clone.
   let db2 = db1.clone();
   do_check_true(db2.connectionReady);
 
   // These should not throw.
-  db2.createStatement("SELECT * FROM attached_1.sqlite_master");
-  db2.createStatement("SELECT * FROM attached_2.sqlite_master");
+  stmt = db2.createStatement("SELECT * FROM attached_1.sqlite_master");
+  stmt.finalize();
+  stmt = db2.createStatement("SELECT * FROM attached_2.sqlite_master");
+  stmt.finalize();
 
   // R/O clone.
   let db3 = db1.clone(true);
   do_check_true(db3.connectionReady);
 
   // These should not throw.
-  db3.createStatement("SELECT * FROM attached_1.sqlite_master");
-  db3.createStatement("SELECT * FROM attached_2.sqlite_master");
+  stmt = db3.createStatement("SELECT * FROM attached_1.sqlite_master");
+  stmt.finalize();
+  stmt = db3.createStatement("SELECT * FROM attached_2.sqlite_master");
+  stmt.finalize();
 
   db1.close();
   db2.close();
   db3.close();
 });
 
 add_task(function* test_getInterface() {
   let db = getOpenedDatabase();