Bug 1539104 - Add a way to detect supported storage connection operations. r=mak
authorLina Cambridge <lina@yakshaving.ninja>
Tue, 02 Apr 2019 18:49:21 +0000
changeset 467629 db20a3f4324ec9fc29bb21bda77672e7e7640529
parent 467628 3239894a74021dd5d970a8861efc0d793a1c3001
child 467630 09b6d709b2da40d15d257ce70bc63b0e6943a178
push id35806
push userrgurzau@mozilla.com
push dateWed, 03 Apr 2019 04:07:39 +0000
treeherdermozilla-central@45808ab18609 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1539104
milestone68.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 1539104 - Add a way to detect supported storage connection operations. r=mak In retrospect, overloading `Connection::connectionReady` to mean "is ready _and_ supports this operation" wasn't a good idea. This commit reverts that change (cfd44c936a9b), and adds two new methods: * `Connection::operationSupported`, to check if a connection supports sync or async operations. This method is public. * `Connection::ensureOperationSupported`, that asserts or returns an error if the connection doesn't support an operation. This is private. `operationSupported` is used by callers like `Service::minimizeMemory` to detect if the connection supports sync operations, since both sync and async connections implement `mozIStorageConnection` now. Finally, some callers used `!mDBConn` to check if the connection was ready, while others used `connectionReady()`. This commit changes them to use the latter. Differential Revision: https://phabricator.services.mozilla.com/D24974
storage/mozStorageConnection.cpp
storage/mozStorageConnection.h
storage/mozStorageService.cpp
storage/test/unit/test_minimizeMemory.js
storage/test/unit/xpcshell.ini
--- a/storage/mozStorageConnection.cpp
+++ b/storage/mozStorageConnection.cpp
@@ -558,17 +558,17 @@ NS_IMETHODIMP_(MozExternalRefCountType) 
     delete (this);
     return 0;
   }
   return count;
 }
 
 int32_t Connection::getSqliteRuntimeStatus(int32_t aStatusOption,
                                            int32_t *aMaxValue) {
-  MOZ_ASSERT(mDBConn, "A connection must exist at this point");
+  MOZ_ASSERT(connectionReady(), "A connection must exist at this point");
   int curr = 0, max = 0;
   DebugOnly<int> rc =
       ::sqlite3_db_status(mDBConn, aStatusOption, &curr, &max, 0);
   MOZ_ASSERT(NS_SUCCEEDED(convertResultCode(rc)));
   if (aMaxValue) *aMaxValue = max;
   return curr;
 }
 
@@ -590,17 +590,18 @@ nsIEventTarget *Connection::getAsyncExec
       return nullptr;
     }
   }
 
   return mAsyncExecutionThread;
 }
 
 nsresult Connection::initialize() {
-  NS_ASSERTION(!mDBConn, "Initialize called on already opened database!");
+  NS_ASSERTION(!connectionReady(),
+               "Initialize called on already opened database!");
   MOZ_ASSERT(!mIgnoreLockingMode, "Can't ignore locking on an in-memory db.");
   AUTO_PROFILER_LABEL("Connection::initialize", OTHER);
 
   // in memory database requested, sqlite uses a magic file name
   int srv = ::sqlite3_open_v2(":memory:", &mDBConn, mFlags, GetVFSName());
   if (srv != SQLITE_OK) {
     mDBConn = nullptr;
     return convertResultCode(srv);
@@ -619,17 +620,18 @@ nsresult Connection::initialize() {
   nsresult rv = initializeInternal();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult Connection::initialize(nsIFile *aDatabaseFile) {
   NS_ASSERTION(aDatabaseFile, "Passed null file!");
-  NS_ASSERTION(!mDBConn, "Initialize called on already opened database!");
+  NS_ASSERTION(!connectionReady(),
+               "Initialize called on already opened database!");
   AUTO_PROFILER_LABEL("Connection::initialize", OTHER);
 
   mDatabaseFile = aDatabaseFile;
 
   nsAutoString path;
   nsresult rv = aDatabaseFile->GetPath(path);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -661,17 +663,18 @@ nsresult Connection::initialize(nsIFile 
   rv = initializeInternal();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult Connection::initialize(nsIFileURL *aFileURL) {
   NS_ASSERTION(aFileURL, "Passed null file URL!");
-  NS_ASSERTION(!mDBConn, "Initialize called on already opened database!");
+  NS_ASSERTION(!connectionReady(),
+               "Initialize called on already opened database!");
   AUTO_PROFILER_LABEL("Connection::initialize", OTHER);
 
   nsCOMPtr<nsIFile> databaseFile;
   nsresult rv = aFileURL->GetFile(getter_AddRefs(databaseFile));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString spec;
   rv = aFileURL->GetSpec(spec);
@@ -821,17 +824,20 @@ void Connection::initializeFailed() {
   MOZ_ALWAYS_TRUE(::sqlite3_close(mDBConn) == SQLITE_OK);
   mDBConn = nullptr;
   sharedDBMutex.destroy();
 }
 
 nsresult Connection::databaseElementExists(
     enum DatabaseElementType aElementType, const nsACString &aElementName,
     bool *_exists) {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // 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
   nsCString query("SELECT name FROM (SELECT * FROM ");
@@ -928,35 +934,37 @@ nsresult Connection::setClosedState() {
     // 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;
 }
 
-nsresult Connection::connectionReady(ConnectionOperation aOperation) {
-  if (NS_WARN_IF(aOperation == SYNCHRONOUS &&
+bool Connection::operationSupported(ConnectionOperation aOperationType) {
+  if (NS_WARN_IF(aOperationType == SYNCHRONOUS &&
                  mSupportedOperations == ASYNCHRONOUS && NS_IsMainThread())) {
-    MOZ_ASSERT(false,
-               "Don't use async connections synchronously on the main thread");
-    return NS_ERROR_NOT_AVAILABLE;
+    return false;
   }
-  if (!mDBConn) {
-    return NS_ERROR_NOT_INITIALIZED;
-  }
-  return NS_OK;
+  return true;
+}
+
+nsresult Connection::ensureOperationSupported(
+    ConnectionOperation aOperationType) {
+  MOZ_ASSERT(operationSupported(aOperationType),
+             "Don't use async connections synchronously on the main thread");
+  return operationSupported(aOperationType) ? NS_OK : NS_ERROR_NOT_AVAILABLE;
 }
 
 bool Connection::isConnectionReadyOnThisThread() {
-  MOZ_ASSERT_IF(mDBConn, !mConnectionClosed);
+  MOZ_ASSERT_IF(connectionReady(), !mConnectionClosed);
   if (mAsyncExecutionThread && mAsyncExecutionThread->IsOnCurrentThread()) {
     return true;
   }
-  return mDBConn != nullptr;
+  return connectionReady();
 }
 
 bool Connection::isClosing() {
   MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
   return mAsyncExecutionThreadShuttingDown && !mConnectionClosed;
 }
 
 bool Connection::isClosed() {
@@ -1223,25 +1231,25 @@ Connection::GetInterface(const nsIID &aI
   return NS_ERROR_NO_INTERFACE;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// mozIStorageConnection
 
 NS_IMETHODIMP
 Connection::Close() {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
   return synchronousClose();
 }
 
 nsresult Connection::synchronousClose() {
-  if (!mDBConn) {
+  if (!connectionReady()) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
 #ifdef DEBUG
   // Since we're accessing mAsyncExecutionThread, we need to be on the opener
   // thread. We make this check outside of debug code below in setClosedState,
   // but this is here to be explicit.
   bool onOpenerThread = false;
@@ -1274,43 +1282,49 @@ nsresult Connection::synchronousClose() 
   nsresult rv = setClosedState();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return internalClose(nativeConn);
 }
 
 NS_IMETHODIMP
 Connection::SpinningSynchronousClose() {
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
   if (threadOpenedOn != NS_GetCurrentThread()) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
   // As currently implemented, we can't spin to wait for an existing AsyncClose.
   // Our only existing caller will never have called close; assert if misused
   // so that no new callers assume this works after an AsyncClose.
-  nsresult rv = connectionReady(SYNCHRONOUS);
-  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-  if (NS_FAILED(rv)) {
+  MOZ_DIAGNOSTIC_ASSERT(connectionReady());
+  if (!connectionReady()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   RefPtr<CloseListener> listener = new CloseListener();
   rv = AsyncClose(listener);
   NS_ENSURE_SUCCESS(rv, rv);
   MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return listener->mClosed; }));
   MOZ_ASSERT(isClosed(), "The connection should be closed at this point");
 
   return rv;
 }
 
 NS_IMETHODIMP
 Connection::AsyncClose(mozIStorageCompletionCallback *aCallback) {
   NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
   // Check if AsyncClose or Close were already invoked.
-  nsresult rv = connectionReady(ASYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // 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:
   //
@@ -1402,17 +1416,20 @@ Connection::AsyncClose(mozIStorageComple
 }
 
 NS_IMETHODIMP
 Connection::AsyncClone(bool aReadOnly,
                        mozIStorageCompletionCallback *aCallback) {
   AUTO_PROFILER_LABEL("Connection::AsyncClone", OTHER);
 
   NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
-  nsresult rv = connectionReady(ASYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
   if (!mDatabaseFile) return NS_ERROR_UNEXPECTED;
 
   int flags = mFlags;
   if (aReadOnly) {
     // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY.
@@ -1582,17 +1599,20 @@ nsresult Connection::initializeClone(Con
 }
 
 NS_IMETHODIMP
 Connection::Clone(bool aReadOnly, mozIStorageConnection **_connection) {
   MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
 
   AUTO_PROFILER_LABEL("Connection::Clone", OTHER);
 
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
   if (!mDatabaseFile) return NS_ERROR_UNEXPECTED;
 
   int flags = mFlags;
   if (aReadOnly) {
     // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY.
@@ -1611,104 +1631,124 @@ Connection::Clone(bool aReadOnly, mozISt
 
   NS_IF_ADDREF(*_connection = clone);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::Interrupt() {
   MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
-  if (!mDBConn) {
+  if (!connectionReady()) {
     return NS_ERROR_NOT_INITIALIZED;
   }
-  if (mSupportedOperations == SYNCHRONOUS || !(mFlags & SQLITE_OPEN_READONLY)) {
+  if (operationSupported(SYNCHRONOUS) || !(mFlags & SQLITE_OPEN_READONLY)) {
+    // Interrupting a synchronous connection from the same thread doesn't make
+    // sense, and read-write connections aren't safe to interrupt.
     return NS_ERROR_INVALID_ARG;
   }
   ::sqlite3_interrupt(mDBConn);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::GetDefaultPageSize(int32_t *_defaultPageSize) {
   *_defaultPageSize = Service::getDefaultPageSize();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::GetConnectionReady(bool *_ready) {
   MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
-  *_ready = !!mDBConn;
+  *_ready = connectionReady();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::GetDatabaseFile(nsIFile **_dbFile) {
-  nsresult rv = connectionReady(ASYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   NS_IF_ADDREF(*_dbFile = mDatabaseFile);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::GetLastInsertRowID(int64_t *_id) {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   sqlite_int64 id = ::sqlite3_last_insert_rowid(mDBConn);
   *_id = id;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::GetAffectedRows(int32_t *_rows) {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   *_rows = ::sqlite3_changes(mDBConn);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::GetLastError(int32_t *_error) {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   *_error = ::sqlite3_errcode(mDBConn);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::GetLastErrorString(nsACString &_errorString) {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   const char *serr = ::sqlite3_errmsg(mDBConn);
   _errorString.Assign(serr);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::GetSchemaVersion(int32_t *_version) {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   nsCOMPtr<mozIStorageStatement> stmt;
   (void)CreateStatement(NS_LITERAL_CSTRING("PRAGMA user_version"),
                         getter_AddRefs(stmt));
   NS_ENSURE_TRUE(stmt, NS_ERROR_OUT_OF_MEMORY);
@@ -1718,32 +1758,38 @@ Connection::GetSchemaVersion(int32_t *_v
   if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult)
     *_version = stmt->AsInt32(0);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::SetSchemaVersion(int32_t aVersion) {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   nsAutoCString stmt(NS_LITERAL_CSTRING("PRAGMA user_version = "));
   stmt.AppendInt(aVersion);
 
   return ExecuteSimpleSQL(stmt);
 }
 
 NS_IMETHODIMP
 Connection::CreateStatement(const nsACString &aSQLStatement,
                             mozIStorageStatement **_stmt) {
   NS_ENSURE_ARG_POINTER(_stmt);
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   RefPtr<Statement> statement(new Statement());
   NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
 
   rv = statement->initialize(this, mDBConn, aSQLStatement);
@@ -1754,17 +1800,20 @@ Connection::CreateStatement(const nsACSt
   *_stmt = rawPtr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::CreateAsyncStatement(const nsACString &aSQLStatement,
                                  mozIStorageAsyncStatement **_stmt) {
   NS_ENSURE_ARG_POINTER(_stmt);
-  nsresult rv = connectionReady(ASYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   RefPtr<AsyncStatement> statement(new AsyncStatement());
   NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
 
   rv = statement->initialize(this, mDBConn, aSQLStatement);
@@ -1774,17 +1823,20 @@ Connection::CreateAsyncStatement(const n
   statement.forget(&rawPtr);
   *_stmt = rawPtr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::ExecuteSimpleSQL(const nsACString &aSQLStatement) {
   CHECK_MAINTHREAD_ABUSE();
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   int srv = executeSql(mDBConn, PromiseFlatCString(aSQLStatement).get());
   return convertResultCode(srv);
 }
 
@@ -1844,17 +1896,20 @@ Connection::TableExists(const nsACString
 
 NS_IMETHODIMP
 Connection::IndexExists(const nsACString &aIndexName, bool *_exists) {
   return databaseElementExists(INDEX, aIndexName, _exists);
 }
 
 NS_IMETHODIMP
 Connection::GetTransactionInProgress(bool *_inProgress) {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   *_inProgress = mTransactionInProgress;
   return NS_OK;
 }
@@ -1869,17 +1924,20 @@ NS_IMETHODIMP
 Connection::SetDefaultTransactionType(int32_t aType) {
   NS_ENSURE_ARG_RANGE(aType, TRANSACTION_DEFERRED, TRANSACTION_EXCLUSIVE);
   mDefaultTransactionType = aType;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::BeginTransaction() {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return beginTransactionInternal(mDBConn, mDefaultTransactionType);
 }
 
 nsresult Connection::beginTransactionInternal(sqlite3 *aNativeConnection,
@@ -1901,17 +1959,20 @@ nsresult Connection::beginTransactionInt
       return NS_ERROR_ILLEGAL_VALUE;
   }
   if (NS_SUCCEEDED(rv)) mTransactionInProgress = true;
   return rv;
 }
 
 NS_IMETHODIMP
 Connection::CommitTransaction() {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return commitTransactionInternal(mDBConn);
 }
 
 nsresult Connection::commitTransactionInternal(sqlite3 *aNativeConnection) {
@@ -1920,17 +1981,20 @@ nsresult Connection::commitTransactionIn
   nsresult rv =
       convertResultCode(executeSql(aNativeConnection, "COMMIT TRANSACTION"));
   if (NS_SUCCEEDED(rv)) mTransactionInProgress = false;
   return rv;
 }
 
 NS_IMETHODIMP
 Connection::RollbackTransaction() {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return rollbackTransactionInternal(mDBConn);
 }
 
 nsresult Connection::rollbackTransactionInternal(sqlite3 *aNativeConnection) {
@@ -1940,17 +2004,20 @@ nsresult Connection::rollbackTransaction
   nsresult rv =
       convertResultCode(executeSql(aNativeConnection, "ROLLBACK TRANSACTION"));
   if (NS_SUCCEEDED(rv)) mTransactionInProgress = false;
   return rv;
 }
 
 NS_IMETHODIMP
 Connection::CreateTable(const char *aTableName, const char *aTableSchema) {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   SmprintfPointer buf =
       ::mozilla::Smprintf("CREATE TABLE %s (%s)", aTableName, aTableSchema);
   if (!buf) return NS_ERROR_OUT_OF_MEMORY;
 
@@ -1958,17 +2025,20 @@ Connection::CreateTable(const char *aTab
 
   return convertResultCode(srv);
 }
 
 NS_IMETHODIMP
 Connection::CreateFunction(const nsACString &aFunctionName,
                            int32_t aNumArguments,
                            mozIStorageFunction *aFunction) {
-  nsresult rv = connectionReady(ASYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Check to see if this function is already defined.  We only check the name
   // because a function can be defined with the same body but different names.
   SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE);
@@ -1984,17 +2054,20 @@ Connection::CreateFunction(const nsACStr
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::CreateAggregateFunction(const nsACString &aFunctionName,
                                     int32_t aNumArguments,
                                     mozIStorageAggregateFunction *aFunction) {
-  nsresult rv = connectionReady(ASYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Check to see if this function name is already defined.
   SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE);
 
@@ -2013,17 +2086,20 @@ Connection::CreateAggregateFunction(cons
                        aNumArguments};
   mFunctions.Put(aFunctionName, info);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::RemoveFunction(const nsACString &aFunctionName) {
-  nsresult rv = connectionReady(ASYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   NS_ENSURE_TRUE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE);
 
   int srv = ::sqlite3_create_function(
@@ -2035,17 +2111,20 @@ Connection::RemoveFunction(const nsACStr
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::SetProgressHandler(int32_t aGranularity,
                                mozIStorageProgressHandler *aHandler,
                                mozIStorageProgressHandler **_oldHandler) {
-  nsresult rv = connectionReady(ASYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Return previous one
   SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   NS_IF_ADDREF(*_oldHandler = mProgressHandler);
 
@@ -2056,35 +2135,45 @@ Connection::SetProgressHandler(int32_t a
   mProgressHandler = aHandler;
   ::sqlite3_progress_handler(mDBConn, aGranularity, sProgressHelper, this);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::RemoveProgressHandler(mozIStorageProgressHandler **_oldHandler) {
-  if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   // Return previous one
   SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   NS_IF_ADDREF(*_oldHandler = mProgressHandler);
 
   mProgressHandler = nullptr;
   ::sqlite3_progress_handler(mDBConn, 0, nullptr, nullptr);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::SetGrowthIncrement(int32_t aChunkSize,
                                const nsACString &aDatabaseName) {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
+
   // Bug 597215: Disk space is extremely limited on Android
   // so don't preallocate space. This is also not effective
   // on log structured file systems used by Android devices
 #if !defined(ANDROID) && !defined(MOZ_PLATFORM_MAEMO)
   // Don't preallocate if less than 500MiB is available.
   int64_t bytesAvailable;
   rv = mDatabaseFile->GetDiskSpaceAvailable(&bytesAvailable);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -2098,17 +2187,20 @@ Connection::SetGrowthIncrement(int32_t a
                                    : nullptr,
                                SQLITE_FCNTL_CHUNK_SIZE, &aChunkSize);
 #endif
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::EnableModule(const nsACString &aModuleName) {
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   for (auto &gModule : gModules) {
     struct Module *m = &gModule;
     if (aModuleName.Equals(m->name)) {
       int srv = m->registerFunc(mDBConn, m->name);
@@ -2125,17 +2217,20 @@ Connection::EnableModule(const nsACStrin
 already_AddRefed<QuotaObject> GetQuotaObjectForFile(sqlite3_file *pFile);
 
 NS_IMETHODIMP
 Connection::GetQuotaObjects(QuotaObject **aDatabaseQuotaObject,
                             QuotaObject **aJournalQuotaObject) {
   MOZ_ASSERT(aDatabaseQuotaObject);
   MOZ_ASSERT(aJournalQuotaObject);
 
-  nsresult rv = connectionReady(SYNCHRONOUS);
+  if (!connectionReady()) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   sqlite3_file *file;
   int srv = ::sqlite3_file_control(mDBConn, nullptr, SQLITE_FCNTL_FILE_POINTER,
                                    &file);
   if (srv != SQLITE_OK) {
--- a/storage/mozStorageConnection.h
+++ b/storage/mozStorageConnection.h
@@ -231,26 +231,28 @@ class Connection final : public mozIStor
    */
   nsresult beginTransactionInternal(
       sqlite3 *aNativeConnection,
       int32_t aTransactionType = TRANSACTION_DEFERRED);
   nsresult commitTransactionInternal(sqlite3 *aNativeConnection);
   nsresult rollbackTransactionInternal(sqlite3 *aNativeConnection);
 
   /**
-   * Indicates if this database connection is ready and supports the given
-   * operation.
+   * Indicates if this database connection is open.
+   */
+  inline bool connectionReady() { return mDBConn != nullptr; }
+
+  /**
+   * Indicates if this database connection supports the given operation.
    *
    * @param  aOperationType
    *         The operation type, sync or async.
-   * @throws NS_ERROR_NOT_AVAILABLE if the operation isn't supported on this
-   *         connection.
-   * @throws NS_ERROR_NOT_INITIALIZED if the connection isn't set up.
+   * @return `true` if the operation is supported, `false` otherwise.
    */
-  nsresult connectionReady(ConnectionOperation aOperationType);
+  bool operationSupported(ConnectionOperation aOperationType);
 
   /**
    * 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.
@@ -341,16 +343,22 @@ class Connection final : public mozIStor
   bool findFunctionByInstance(nsISupports *aInstance);
 
   static int sProgressHelper(void *aArg);
   // Generic progress handler
   // Dispatch call to registered progress handler,
   // if there is one. Do nothing in other cases.
   int progressHandler();
 
+  /**
+   * Like `operationSupported`, but throws (and, in a debug build, asserts) if
+   * the operation is unsupported.
+   */
+  nsresult ensureOperationSupported(ConnectionOperation aOperationType);
+
   sqlite3 *mDBConn;
   nsCOMPtr<nsIFileURL> mFileURL;
   nsCOMPtr<nsIFile> mDatabaseFile;
 
   /**
    * The filename that will be reported to telemetry for this connection. By
    * default this will be the leaf of the path to the database file.
    */
--- a/storage/mozStorageService.cpp
+++ b/storage/mozStorageService.cpp
@@ -114,18 +114,17 @@ Service::CollectReports(nsIHandleReportC
     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.
       // 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);
-      nsresult rv = conn->connectionReady(Connection::ASYNCHRONOUS);
-      if (NS_FAILED(rv)) {
+      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('/');
 
@@ -299,27 +298,24 @@ void Service::getConnections(
 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.
-    nsresult rv = conn->connectionReady(Connection::ASYNCHRONOUS);
-    if (NS_FAILED(rv)) {
+    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;
 
-    if (!syncConn) {
+    if (!conn->operationSupported(Connection::SYNCHRONOUS)) {
       // This is a mozIStorageAsyncConnection, it can only be used on the main
       // thread, so we can do a straight API call.
       nsCOMPtr<mozIStoragePendingStatement> ps;
       DebugOnly<nsresult> rv = conn->ExecuteSimpleSQLAsync(
           shrinkPragma, nullptr, getter_AddRefs(ps));
       MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
     } else if (NS_SUCCEEDED(
                    conn->threadOpenedOn->IsOnCurrentThread(&onOpenedThread)) &&
new file mode 100644
--- /dev/null
+++ b/storage/test/unit/test_minimizeMemory.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This file tests that invoking `Service::minimizeMemory` succeeds for sync
+// and async connections.
+
+function minimizeMemory() {
+  Services.storage.QueryInterface(Ci.nsIObserver)
+                  .observe(null, "memory-pressure", null);
+}
+
+add_task(async function test_minimizeMemory_async_connection() {
+  let db = await openAsyncDatabase(getTestDB());
+  minimizeMemory();
+  await asyncClose(db);
+});
+
+add_task(async function test_minimizeMemory_sync_connection() {
+  let db = getOpenedDatabase();
+  minimizeMemory();
+  db.close();
+});
--- a/storage/test/unit/xpcshell.ini
+++ b/storage/test/unit/xpcshell.ini
@@ -24,16 +24,17 @@ fail-if = os == "android"
 # on debug builds, so we can only test on non-debug builds.
 skip-if = debug
 [test_connection_interrupt.js]
 [test_js_helpers.js]
 [test_levenshtein.js]
 [test_like.js]
 [test_like_escape.js]
 [test_locale_collation.js]
+[test_minimizeMemory.js]
 [test_page_size_is_32k.js]
 [test_sqlite_secure_delete.js]
 [test_statement_executeAsync.js]
 [test_statement_wrapper_automatically.js]
 [test_storage_aggregates.js]
 [test_storage_connection.js]
 # Bug 676981: test fails consistently on Android
 fail-if = os == "android"