From 8d53c1682e72bf13a6b358702b0361217b432d03 Mon Sep 17 00:00:00 2001 From: Marco Bonardo Date: Wed, 10 Sep 2014 12:46:14 +0200 Subject: [PATCH] Bug 1047811 - Part 1 - Allow to commit a main-thread Storage transaction asynchronously. r=asuth --- storage/public/mozStorageHelper.h | 168 ++++++++++-------- storage/src/mozStorageConnection.cpp | 1 + storage/src/mozStorageConnection.h | 8 + storage/test/storage_test_harness.h | 169 ++++++++++++++++++- storage/test/test_transaction_helper.cpp | 127 +++++++------- storage/test/test_true_async.cpp | 169 ------------------- storage/test/unit/test_storage_connection.js | 2 + 7 files changed, 335 insertions(+), 309 deletions(-) diff --git a/storage/public/mozStorageHelper.h b/storage/public/mozStorageHelper.h index a4ecda8f7c5b..5104baca87d8 100644 --- a/storage/public/mozStorageHelper.h +++ b/storage/public/mozStorageHelper.h @@ -7,10 +7,13 @@ #define MOZSTORAGEHELPER_H #include "nsAutoPtr.h" +#include "nsStringGlue.h" +#include "mozilla/DebugOnly.h" #include "mozIStorageAsyncConnection.h" #include "mozIStorageConnection.h" #include "mozIStorageStatement.h" +#include "mozIStoragePendingStatement.h" #include "nsError.h" /** @@ -18,59 +21,113 @@ * the transaction will be completed even if you have an exception or * return early. * - * aCommitOnComplete controls whether the transaction is committed or rolled - * back when it goes out of scope. A common use is to create an instance with - * commitOnComplete = FALSE (rollback), then call Commit on this object manually - * when your function completes successfully. + * A common use is to create an instance with aCommitOnComplete = false (rollback), + * then call Commit() on this object manually when your function completes + * successfully. * - * Note that nested transactions are not supported by sqlite, so if a transaction - * is already in progress, this object does nothing. Note that in this case, - * you may not get the transaction type you ask for, and you won't be able + * @note nested transactions are not supported by Sqlite, so if a transaction + * is already in progress, this object does nothing. Note that in this case, + * you may not get the transaction type you asked for, and you won't be able * to rollback. * - * Note: This class is templatized to be also usable with internal data - * structures. External users of this class should generally use - * |mozStorageTransaction| instead. + * @param aConnection + * The connection to create the transaction on. + * @param aCommitOnComplete + * Controls whether the transaction is committed or rolled back when + * this object goes out of scope. + * @param aType [optional] + * The transaction type, as defined in mozIStorageConnection. Defaults + * to TRANSACTION_DEFERRED. + * @param aAsyncCommit [optional] + * Whether commit should be executed asynchronously on the helper thread. + * This is a special option introduced as an interim solution to reduce + * main-thread fsyncs in Places. Can only be used on main-thread. + * + * WARNING: YOU SHOULD _NOT_ WRITE NEW MAIN-THREAD CODE USING THIS! + * + * Notice that async commit might cause synchronous statements to fail + * with SQLITE_BUSY. A possible mitigation strategy is to use + * PRAGMA busy_timeout, but notice that might cause main-thread jank. + * Finally, if the database is using WAL journaling mode, other + * connections won't see the changes done in async committed transactions + * until commit is complete. + * + * For all of the above reasons, this should only be used as an interim + * solution and avoided completely if possible. */ -template -class mozStorageTransactionBase +class mozStorageTransaction { public: - mozStorageTransactionBase(T* aConnection, - bool aCommitOnComplete, - int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED) + mozStorageTransaction(mozIStorageConnection* aConnection, + bool aCommitOnComplete, + int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED, + bool aAsyncCommit = false) : mConnection(aConnection), mHasTransaction(false), mCommitOnComplete(aCommitOnComplete), - mCompleted(false) + mCompleted(false), + mAsyncCommit(aAsyncCommit) { - // We won't try to get a transaction if one is already in progress. - if (mConnection) - mHasTransaction = NS_SUCCEEDED(mConnection->BeginTransactionAs(aType)); + if (mConnection) { + nsAutoCString query("BEGIN"); + switch(aType) { + case mozIStorageConnection::TRANSACTION_IMMEDIATE: + query.AppendLiteral(" IMMEDIATE"); + break; + case mozIStorageConnection::TRANSACTION_EXCLUSIVE: + query.AppendLiteral(" EXCLUSIVE"); + break; + case mozIStorageConnection::TRANSACTION_DEFERRED: + query.AppendLiteral(" DEFERRED"); + break; + default: + MOZ_ASSERT(false, "Unknown transaction type"); + } + // If a transaction is already in progress, this will fail, since Sqlite + // doesn't support nested transactions. + mHasTransaction = NS_SUCCEEDED(mConnection->ExecuteSimpleSQL(query)); + } } - ~mozStorageTransactionBase() + + ~mozStorageTransaction() { - if (mConnection && mHasTransaction && ! mCompleted) { - if (mCommitOnComplete) - mConnection->CommitTransaction(); - else - mConnection->RollbackTransaction(); + if (mConnection && mHasTransaction && !mCompleted) { + if (mCommitOnComplete) { + mozilla::DebugOnly rv = Commit(); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), + "A transaction didn't commit correctly"); + } + else { + mozilla::DebugOnly rv = Rollback(); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), + "A transaction didn't rollback correctly"); + } } } /** * Commits the transaction if one is in progress. If one is not in progress, * this is a NOP since the actual owner of the transaction outside of our - * scope is in charge of finally comitting or rolling back the transaction. + * scope is in charge of finally committing or rolling back the transaction. */ nsresult Commit() { - if (!mConnection || mCompleted) - return NS_OK; // no connection, or already done + if (!mConnection || mCompleted || !mHasTransaction) + return NS_OK; mCompleted = true; - if (! mHasTransaction) - return NS_OK; // transaction not ours, ignore - nsresult rv = mConnection->CommitTransaction(); + + // TODO (bug 559659): this might fail with SQLITE_BUSY, but we don't handle + // it, thus the transaction might stay open until the next COMMIT. + nsresult rv; + if (mAsyncCommit) { + nsCOMPtr ps; + rv = mConnection->ExecuteSimpleSQLAsync(NS_LITERAL_CSTRING("COMMIT"), + nullptr, getter_AddRefs(ps)); + } + else { + rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("COMMIT")); + } + if (NS_SUCCEEDED(rv)) mHasTransaction = false; @@ -78,22 +135,21 @@ public: } /** - * Rolls back the transaction in progress. You should only call this function - * if this object has a real transaction (HasTransaction() = true) because - * otherwise, there is no transaction to roll back. + * Rolls back the transaction if one is in progress. If one is not in progress, + * this is a NOP since the actual owner of the transaction outside of our + * scope is in charge of finally rolling back the transaction. */ nsresult Rollback() { - if (!mConnection || mCompleted) - return NS_OK; // no connection, or already done + if (!mConnection || mCompleted || !mHasTransaction) + return NS_OK; mCompleted = true; - if (! mHasTransaction) - return NS_ERROR_FAILURE; - // It is possible that a rollback will return busy, so we busy wait... + // TODO (bug 1062823): from Sqlite 3.7.11 on, rollback won't ever return + // a busy error, so this handling can be removed. nsresult rv = NS_OK; do { - rv = mConnection->RollbackTransaction(); + rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ROLLBACK")); if (rv == NS_ERROR_STORAGE_BUSY) (void)PR_Sleep(PR_INTERVAL_NO_WAIT); } while (rv == NS_ERROR_STORAGE_BUSY); @@ -104,42 +160,14 @@ public: return rv; } - /** - * Returns whether this object wraps a real transaction. False means that - * this object doesn't do anything because there was already a transaction in - * progress when it was created. - */ - bool HasTransaction() - { - return mHasTransaction; - } - - /** - * This sets the default action (commit or rollback) when this object goes - * out of scope. - */ - void SetDefaultAction(bool aCommitOnComplete) - { - mCommitOnComplete = aCommitOnComplete; - } - protected: - U mConnection; + nsCOMPtr mConnection; bool mHasTransaction; bool mCommitOnComplete; bool mCompleted; + bool mAsyncCommit; }; -/** - * An instance of the mozStorageTransaction<> family dedicated - * to |mozIStorageConnection|. - */ -typedef mozStorageTransactionBase > -mozStorageTransaction; - - - /** * This class wraps a statement so that it is guaraneed to be reset when * this object goes out of scope. diff --git a/storage/src/mozStorageConnection.cpp b/storage/src/mozStorageConnection.cpp index 854c5b7770db..20459632e971 100644 --- a/storage/src/mozStorageConnection.cpp +++ b/storage/src/mozStorageConnection.cpp @@ -1218,6 +1218,7 @@ Connection::initializeClone(Connection* aClone, bool aReadOnly) "journal_size_limit", "synchronous", "wal_autocheckpoint", + "busy_timeout" }; for (uint32_t i = 0; i < ArrayLength(pragmas); ++i) { // Read-only connections just need cache_size and temp_store pragmas. diff --git a/storage/src/mozStorageConnection.h b/storage/src/mozStorageConnection.h index 4d9b782dbae6..f1f13d57f9bf 100644 --- a/storage/src/mozStorageConnection.h +++ b/storage/src/mozStorageConnection.h @@ -120,6 +120,14 @@ public: ::sqlite3_commit_hook(mDBConn, aCallbackFn, aData); }; + /** + * Gets autocommit status. + */ + bool getAutocommit() { + MOZ_ASSERT(mDBConn, "A connection must exist at this point"); + return static_cast(::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 diff --git a/storage/test/storage_test_harness.h b/storage/test/storage_test_harness.h index f2c767003957..4497c2a1f92b 100644 --- a/storage/test/storage_test_harness.h +++ b/storage/test/storage_test_harness.h @@ -5,9 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "TestHarness.h" + #include "nsMemory.h" +#include "prthread.h" #include "nsThreadUtils.h" #include "nsDirectoryServiceDefs.h" +#include "mozilla/ReentrantMonitor.h" + #include "mozIStorageService.h" #include "mozIStorageConnection.h" #include "mozIStorageStatementCallback.h" @@ -18,7 +22,10 @@ #include "mozIStorageStatement.h" #include "mozIStoragePendingStatement.h" #include "mozIStorageError.h" -#include "nsThreadUtils.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIEventTarget.h" + +#include "sqlite3.h" static int gTotalTests = 0; static int gPassedTests = 0; @@ -53,13 +60,11 @@ static int gPassedTests = 0; do_check_true(aExpected == aActual) #else #include - // Print nsresult as uint32_t std::ostream& operator<<(std::ostream& aStream, const nsresult aInput) { return aStream << static_cast(aInput); } - #define do_check_eq(aExpected, aActual) \ PR_BEGIN_MACRO \ gTotalTests++; \ @@ -74,6 +79,8 @@ std::ostream& operator<<(std::ostream& aStream, const nsresult aInput) PR_END_MACRO #endif +#define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK) + already_AddRefed getService() { @@ -224,3 +231,159 @@ blocking_async_close(mozIStorageConnection *db) db->AsyncClose(spinner); spinner->SpinUntilCompleted(); } + +//////////////////////////////////////////////////////////////////////////////// +//// Mutex Watching + +/** + * Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on + * the caller (generally main) thread. We do this by decorating the sqlite + * mutex logic with our own code that checks what thread it is being invoked on + * and sets a flag if it is invoked on the main thread. We are able to easily + * decorate the SQLite mutex logic because SQLite allows us to retrieve the + * current function pointers being used and then provide a new set. + */ + +sqlite3_mutex_methods orig_mutex_methods; +sqlite3_mutex_methods wrapped_mutex_methods; + +bool mutex_used_on_watched_thread = false; +PRThread *watched_thread = nullptr; +/** + * Ugly hack to let us figure out what a connection's async thread is. If we + * were MOZILLA_INTERNAL_API and linked as such we could just include + * mozStorageConnection.h and just ask Connection directly. But that turns out + * poorly. + * + * When the thread a mutex is invoked on isn't watched_thread we save it to this + * variable. + */ +PRThread *last_non_watched_thread = nullptr; + +/** + * Set a flag if the mutex is used on the thread we are watching, but always + * call the real mutex function. + */ +extern "C" void wrapped_MutexEnter(sqlite3_mutex *mutex) +{ + PRThread *curThread = ::PR_GetCurrentThread(); + if (curThread == watched_thread) + mutex_used_on_watched_thread = true; + else + last_non_watched_thread = curThread; + orig_mutex_methods.xMutexEnter(mutex); +} + +extern "C" int wrapped_MutexTry(sqlite3_mutex *mutex) +{ + if (::PR_GetCurrentThread() == watched_thread) + mutex_used_on_watched_thread = true; + return orig_mutex_methods.xMutexTry(mutex); +} + +void hook_sqlite_mutex() +{ + // We need to initialize and teardown SQLite to get it to set up the + // default mutex handlers for us so we can steal them and wrap them. + do_check_ok(sqlite3_initialize()); + do_check_ok(sqlite3_shutdown()); + do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &orig_mutex_methods)); + do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped_mutex_methods)); + wrapped_mutex_methods.xMutexEnter = wrapped_MutexEnter; + wrapped_mutex_methods.xMutexTry = wrapped_MutexTry; + do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &wrapped_mutex_methods)); +} + +/** + * Call to clear the watch state and to set the watching against this thread. + * + * Check |mutex_used_on_watched_thread| to see if the mutex has fired since + * this method was last called. Since we're talking about the current thread, + * there are no race issues to be concerned about + */ +void watch_for_mutex_use_on_this_thread() +{ + watched_thread = ::PR_GetCurrentThread(); + mutex_used_on_watched_thread = false; +} + + +//////////////////////////////////////////////////////////////////////////////// +//// Thread Wedgers + +/** + * A runnable that blocks until code on another thread invokes its unwedge + * method. By dispatching this to a thread you can ensure that no subsequent + * runnables dispatched to the thread will execute until you invoke unwedge. + * + * The wedger is self-dispatching, just construct it with its target. + */ +class ThreadWedger : public nsRunnable +{ +public: + explicit ThreadWedger(nsIEventTarget *aTarget) + : mReentrantMonitor("thread wedger") + , unwedged(false) + { + aTarget->Dispatch(this, aTarget->NS_DISPATCH_NORMAL); + } + + NS_IMETHOD Run() + { + mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor); + + if (!unwedged) + automon.Wait(); + + return NS_OK; + } + + void unwedge() + { + mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor); + unwedged = true; + automon.Notify(); + } + +private: + mozilla::ReentrantMonitor mReentrantMonitor; + bool unwedged; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Async Helpers + +/** + * A horrible hack to figure out what the connection's async thread is. By + * creating a statement and async dispatching we can tell from the mutex who + * is the async thread, PRThread style. Then we map that to an nsIThread. + */ +already_AddRefed +get_conn_async_thread(mozIStorageConnection *db) +{ + // Make sure we are tracking the current thread as the watched thread + watch_for_mutex_use_on_this_thread(); + + // - statement with nothing to bind + nsCOMPtr stmt; + db->CreateAsyncStatement( + NS_LITERAL_CSTRING("SELECT 1"), + getter_AddRefs(stmt)); + blocking_async_execute(stmt); + stmt->Finalize(); + + nsCOMPtr threadMan = + do_GetService("@mozilla.org/thread-manager;1"); + nsCOMPtr asyncThread; + threadMan->GetThreadFromPRThread(last_non_watched_thread, + getter_AddRefs(asyncThread)); + + // Additionally, check that the thread we get as the background thread is the + // same one as the one we report from getInterface. + nsCOMPtr target = do_GetInterface(db); + nsCOMPtr allegedAsyncThread = do_QueryInterface(target); + PRThread *allegedPRThread; + (void)allegedAsyncThread->GetPRThread(&allegedPRThread); + do_check_eq(allegedPRThread, last_non_watched_thread); + return asyncThread.forget(); +} diff --git a/storage/test/test_transaction_helper.cpp b/storage/test/test_transaction_helper.cpp index 26b834dfd2a9..5f0a04b918ec 100644 --- a/storage/test/test_transaction_helper.cpp +++ b/storage/test/test_transaction_helper.cpp @@ -7,42 +7,19 @@ #include "storage_test_harness.h" #include "mozStorageHelper.h" +#include "mozStorageConnection.h" + +using namespace mozilla; +using namespace mozilla::storage; + +bool has_transaction(mozIStorageConnection* aDB) { + return !(static_cast(aDB)->getAutocommit()); +} /** * This file test our Transaction helper in mozStorageHelper.h. */ -void -test_HasTransaction() -{ - nsCOMPtr db(getMemoryDatabase()); - - // First test that it holds the transaction after it should have gotten one. - { - mozStorageTransaction transaction(db, false); - do_check_true(transaction.HasTransaction()); - (void)transaction.Commit(); - // And that it does not have a transaction after we have committed. - do_check_false(transaction.HasTransaction()); - } - - // Check that no transaction is had after a rollback. - { - mozStorageTransaction transaction(db, false); - do_check_true(transaction.HasTransaction()); - (void)transaction.Rollback(); - do_check_false(transaction.HasTransaction()); - } - - // Check that we do not have a transaction if one is already obtained. - mozStorageTransaction outerTransaction(db, false); - do_check_true(outerTransaction.HasTransaction()); - { - mozStorageTransaction innerTransaction(db, false); - do_check_false(innerTransaction.HasTransaction()); - } -} - void test_Commit() { @@ -52,11 +29,13 @@ test_Commit() // exists after the transaction falls out of scope. { mozStorageTransaction transaction(db, false); + do_check_true(has_transaction(db)); (void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE test (id INTEGER PRIMARY KEY)" )); (void)transaction.Commit(); } + do_check_false(has_transaction(db)); bool exists = false; (void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists); @@ -72,11 +51,13 @@ test_Rollback() // not exists after the transaction falls out of scope. { mozStorageTransaction transaction(db, true); + do_check_true(has_transaction(db)); (void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE test (id INTEGER PRIMARY KEY)" )); (void)transaction.Rollback(); } + do_check_false(has_transaction(db)); bool exists = true; (void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists); @@ -92,10 +73,12 @@ test_AutoCommit() // transaction falls out of scope. This means the Commit was successful. { mozStorageTransaction transaction(db, true); + do_check_true(has_transaction(db)); (void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE test (id INTEGER PRIMARY KEY)" )); } + do_check_false(has_transaction(db)); bool exists = false; (void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists); @@ -112,68 +95,78 @@ test_AutoRollback() // successful. { mozStorageTransaction transaction(db, false); + do_check_true(has_transaction(db)); (void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE test (id INTEGER PRIMARY KEY)" )); } + do_check_false(has_transaction(db)); bool exists = true; (void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists); do_check_false(exists); } -void -test_SetDefaultAction() -{ - nsCOMPtr db(getMemoryDatabase()); - - // First we test that rollback happens when we first set it to automatically - // commit. - { - mozStorageTransaction transaction(db, true); - (void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE test1 (id INTEGER PRIMARY KEY)" - )); - transaction.SetDefaultAction(false); - } - bool exists = true; - (void)db->TableExists(NS_LITERAL_CSTRING("test1"), &exists); - do_check_false(exists); - - // Now we do the opposite and test that a commit happens when we first set it - // to automatically rollback. - { - mozStorageTransaction transaction(db, false); - (void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE test2 (id INTEGER PRIMARY KEY)" - )); - transaction.SetDefaultAction(true); - } - exists = false; - (void)db->TableExists(NS_LITERAL_CSTRING("test2"), &exists); - do_check_true(exists); -} - void test_null_database_connection() { // We permit the use of the Transaction helper when passing a null database // in, so we need to make sure this still works without crashing. mozStorageTransaction transaction(nullptr, false); - - do_check_false(transaction.HasTransaction()); do_check_true(NS_SUCCEEDED(transaction.Commit())); do_check_true(NS_SUCCEEDED(transaction.Rollback())); } +void +test_async_Commit() +{ + // note this will be active for any following test. + hook_sqlite_mutex(); + + nsCOMPtr db(getMemoryDatabase()); + + // -- wedge the thread + nsCOMPtr target(get_conn_async_thread(db)); + do_check_true(target); + nsRefPtr wedger (new ThreadWedger(target)); + + { + mozStorageTransaction transaction(db, false, + mozIStorageConnection::TRANSACTION_DEFERRED, + true); + do_check_true(has_transaction(db)); + (void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TABLE test (id INTEGER PRIMARY KEY)" + )); + (void)transaction.Commit(); + } + do_check_true(has_transaction(db)); + + // -- unwedge the async thread + wedger->unwedge(); + + // Ensure the transaction has done its job by enqueueing an async execution. + nsCOMPtr stmt; + (void)db->CreateAsyncStatement(NS_LITERAL_CSTRING( + "SELECT NULL" + ), getter_AddRefs(stmt)); + blocking_async_execute(stmt); + stmt->Finalize(); + do_check_false(has_transaction(db)); + bool exists = false; + (void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists); + do_check_true(exists); + + blocking_async_close(db); +} + void (*gTests[])(void) = { - test_HasTransaction, test_Commit, test_Rollback, test_AutoCommit, test_AutoRollback, - test_SetDefaultAction, test_null_database_connection, + test_async_Commit, }; const char *file = __FILE__; diff --git a/storage/test/test_true_async.cpp b/storage/test/test_true_async.cpp index 4782494930b7..beea9fb0a6f8 100644 --- a/storage/test/test_true_async.cpp +++ b/storage/test/test_true_async.cpp @@ -5,175 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "storage_test_harness.h" -#include "prthread.h" -#include "nsIEventTarget.h" -#include "nsIInterfaceRequestorUtils.h" - -#include "sqlite3.h" - -#include "mozilla/ReentrantMonitor.h" - -using mozilla::ReentrantMonitor; -using mozilla::ReentrantMonitorAutoEnter; - -/** - * Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on - * the caller (generally main) thread. We do this by decorating the sqlite - * mutex logic with our own code that checks what thread it is being invoked on - * and sets a flag if it is invoked on the main thread. We are able to easily - * decorate the SQLite mutex logic because SQLite allows us to retrieve the - * current function pointers being used and then provide a new set. - */ - -/* ===== Mutex Watching ===== */ - -sqlite3_mutex_methods orig_mutex_methods; -sqlite3_mutex_methods wrapped_mutex_methods; - -bool mutex_used_on_watched_thread = false; -PRThread *watched_thread = nullptr; -/** - * Ugly hack to let us figure out what a connection's async thread is. If we - * were MOZILLA_INTERNAL_API and linked as such we could just include - * mozStorageConnection.h and just ask Connection directly. But that turns out - * poorly. - * - * When the thread a mutex is invoked on isn't watched_thread we save it to this - * variable. - */ -PRThread *last_non_watched_thread = nullptr; - -/** - * Set a flag if the mutex is used on the thread we are watching, but always - * call the real mutex function. - */ -extern "C" void wrapped_MutexEnter(sqlite3_mutex *mutex) -{ - PRThread *curThread = ::PR_GetCurrentThread(); - if (curThread == watched_thread) - mutex_used_on_watched_thread = true; - else - last_non_watched_thread = curThread; - orig_mutex_methods.xMutexEnter(mutex); -} - -extern "C" int wrapped_MutexTry(sqlite3_mutex *mutex) -{ - if (::PR_GetCurrentThread() == watched_thread) - mutex_used_on_watched_thread = true; - return orig_mutex_methods.xMutexTry(mutex); -} - - -#define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK) - -void hook_sqlite_mutex() -{ - // We need to initialize and teardown SQLite to get it to set up the - // default mutex handlers for us so we can steal them and wrap them. - do_check_ok(sqlite3_initialize()); - do_check_ok(sqlite3_shutdown()); - do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &orig_mutex_methods)); - do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped_mutex_methods)); - wrapped_mutex_methods.xMutexEnter = wrapped_MutexEnter; - wrapped_mutex_methods.xMutexTry = wrapped_MutexTry; - do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &wrapped_mutex_methods)); -} - -/** - * Call to clear the watch state and to set the watching against this thread. - * - * Check |mutex_used_on_watched_thread| to see if the mutex has fired since - * this method was last called. Since we're talking about the current thread, - * there are no race issues to be concerned about - */ -void watch_for_mutex_use_on_this_thread() -{ - watched_thread = ::PR_GetCurrentThread(); - mutex_used_on_watched_thread = false; -} - - -//////////////////////////////////////////////////////////////////////////////// -//// Thread Wedgers - -/** - * A runnable that blocks until code on another thread invokes its unwedge - * method. By dispatching this to a thread you can ensure that no subsequent - * runnables dispatched to the thread will execute until you invoke unwedge. - * - * The wedger is self-dispatching, just construct it with its target. - */ -class ThreadWedger : public nsRunnable -{ -public: - explicit ThreadWedger(nsIEventTarget *aTarget) - : mReentrantMonitor("thread wedger") - , unwedged(false) - { - aTarget->Dispatch(this, aTarget->NS_DISPATCH_NORMAL); - } - - NS_IMETHOD Run() - { - ReentrantMonitorAutoEnter automon(mReentrantMonitor); - - if (!unwedged) - automon.Wait(); - - return NS_OK; - } - - void unwedge() - { - ReentrantMonitorAutoEnter automon(mReentrantMonitor); - unwedged = true; - automon.Notify(); - } - -private: - ReentrantMonitor mReentrantMonitor; - bool unwedged; -}; - -//////////////////////////////////////////////////////////////////////////////// -//// Async Helpers - -/** - * A horrible hack to figure out what the connection's async thread is. By - * creating a statement and async dispatching we can tell from the mutex who - * is the async thread, PRThread style. Then we map that to an nsIThread. - */ -already_AddRefed -get_conn_async_thread(mozIStorageConnection *db) -{ - // Make sure we are tracking the current thread as the watched thread - watch_for_mutex_use_on_this_thread(); - - // - statement with nothing to bind - nsCOMPtr stmt; - db->CreateAsyncStatement( - NS_LITERAL_CSTRING("SELECT 1"), - getter_AddRefs(stmt)); - blocking_async_execute(stmt); - stmt->Finalize(); - - nsCOMPtr threadMan = - do_GetService("@mozilla.org/thread-manager;1"); - nsCOMPtr asyncThread; - threadMan->GetThreadFromPRThread(last_non_watched_thread, - getter_AddRefs(asyncThread)); - - // Additionally, check that the thread we get as the background thread is the - // same one as the one we report from getInterface. - nsCOMPtr target = do_GetInterface(db); - nsCOMPtr allegedAsyncThread = do_QueryInterface(target); - PRThread *allegedPRThread; - (void)allegedAsyncThread->GetPRThread(&allegedPRThread); - do_check_eq(allegedPRThread, last_non_watched_thread); - return asyncThread.forget(); -} - //////////////////////////////////////////////////////////////////////////////// //// Tests diff --git a/storage/test/unit/test_storage_connection.js b/storage/test/unit/test_storage_connection.js index 66de13757ccf..23db1815e6db 100644 --- a/storage/test/unit/test_storage_connection.js +++ b/storage/test/unit/test_storage_connection.js @@ -736,6 +736,7 @@ add_task(function test_clone_copies_pragmas() { name: "journal_size_limit", value: 524288, copied: true }, { name: "synchronous", value: 2, copied: true }, { name: "wal_autocheckpoint", value: 16, copied: true }, + { name: "busy_timeout", value: 50, copied: true }, { name: "ignore_check_constraints", value: 1, copied: false }, ]; @@ -778,6 +779,7 @@ add_task(function test_readonly_clone_copies_pragmas() { name: "journal_size_limit", value: 524288, copied: false }, { name: "synchronous", value: 2, copied: false }, { name: "wal_autocheckpoint", value: 16, copied: false }, + { name: "busy_timeout", value: 50, copied: false }, { name: "ignore_check_constraints", value: 1, copied: false }, ];