mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1047811 - Part 1 - Allow to commit a main-thread Storage transaction asynchronously. r=asuth
This commit is contained in:
parent
8e98559e06
commit
87f3e90cdc
@ -7,10 +7,13 @@
|
|||||||
#define MOZSTORAGEHELPER_H
|
#define MOZSTORAGEHELPER_H
|
||||||
|
|
||||||
#include "nsAutoPtr.h"
|
#include "nsAutoPtr.h"
|
||||||
|
#include "nsStringGlue.h"
|
||||||
|
#include "mozilla/DebugOnly.h"
|
||||||
|
|
||||||
#include "mozIStorageAsyncConnection.h"
|
#include "mozIStorageAsyncConnection.h"
|
||||||
#include "mozIStorageConnection.h"
|
#include "mozIStorageConnection.h"
|
||||||
#include "mozIStorageStatement.h"
|
#include "mozIStorageStatement.h"
|
||||||
|
#include "mozIStoragePendingStatement.h"
|
||||||
#include "nsError.h"
|
#include "nsError.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,59 +21,113 @@
|
|||||||
* the transaction will be completed even if you have an exception or
|
* the transaction will be completed even if you have an exception or
|
||||||
* return early.
|
* return early.
|
||||||
*
|
*
|
||||||
* aCommitOnComplete controls whether the transaction is committed or rolled
|
* A common use is to create an instance with aCommitOnComplete = false (rollback),
|
||||||
* back when it goes out of scope. A common use is to create an instance with
|
* then call Commit() on this object manually when your function completes
|
||||||
* commitOnComplete = FALSE (rollback), then call Commit on this object manually
|
* successfully.
|
||||||
* when your function completes successfully.
|
|
||||||
*
|
*
|
||||||
* Note that nested transactions are not supported by sqlite, so if a transaction
|
* @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,
|
* 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
|
* you may not get the transaction type you asked for, and you won't be able
|
||||||
* to rollback.
|
* to rollback.
|
||||||
*
|
*
|
||||||
* Note: This class is templatized to be also usable with internal data
|
* @param aConnection
|
||||||
* structures. External users of this class should generally use
|
* The connection to create the transaction on.
|
||||||
* |mozStorageTransaction| instead.
|
* @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<typename T, typename U>
|
class mozStorageTransaction
|
||||||
class mozStorageTransactionBase
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
mozStorageTransactionBase(T* aConnection,
|
mozStorageTransaction(mozIStorageConnection* aConnection,
|
||||||
bool aCommitOnComplete,
|
bool aCommitOnComplete,
|
||||||
int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED)
|
int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED,
|
||||||
|
bool aAsyncCommit = false)
|
||||||
: mConnection(aConnection),
|
: mConnection(aConnection),
|
||||||
mHasTransaction(false),
|
mHasTransaction(false),
|
||||||
mCommitOnComplete(aCommitOnComplete),
|
mCommitOnComplete(aCommitOnComplete),
|
||||||
mCompleted(false)
|
mCompleted(false),
|
||||||
|
mAsyncCommit(aAsyncCommit)
|
||||||
{
|
{
|
||||||
// We won't try to get a transaction if one is already in progress.
|
if (mConnection) {
|
||||||
if (mConnection)
|
nsAutoCString query("BEGIN");
|
||||||
mHasTransaction = NS_SUCCEEDED(mConnection->BeginTransactionAs(aType));
|
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 (mConnection && mHasTransaction && !mCompleted) {
|
||||||
if (mCommitOnComplete)
|
if (mCommitOnComplete) {
|
||||||
mConnection->CommitTransaction();
|
mozilla::DebugOnly<nsresult> rv = Commit();
|
||||||
else
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
|
||||||
mConnection->RollbackTransaction();
|
"A transaction didn't commit correctly");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mozilla::DebugOnly<nsresult> 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,
|
* 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
|
* 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()
|
nsresult Commit()
|
||||||
{
|
{
|
||||||
if (!mConnection || mCompleted)
|
if (!mConnection || mCompleted || !mHasTransaction)
|
||||||
return NS_OK; // no connection, or already done
|
return NS_OK;
|
||||||
mCompleted = true;
|
mCompleted = true;
|
||||||
if (! mHasTransaction)
|
|
||||||
return NS_OK; // transaction not ours, ignore
|
// TODO (bug 559659): this might fail with SQLITE_BUSY, but we don't handle
|
||||||
nsresult rv = mConnection->CommitTransaction();
|
// it, thus the transaction might stay open until the next COMMIT.
|
||||||
|
nsresult rv;
|
||||||
|
if (mAsyncCommit) {
|
||||||
|
nsCOMPtr<mozIStoragePendingStatement> ps;
|
||||||
|
rv = mConnection->ExecuteSimpleSQLAsync(NS_LITERAL_CSTRING("COMMIT"),
|
||||||
|
nullptr, getter_AddRefs(ps));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("COMMIT"));
|
||||||
|
}
|
||||||
|
|
||||||
if (NS_SUCCEEDED(rv))
|
if (NS_SUCCEEDED(rv))
|
||||||
mHasTransaction = false;
|
mHasTransaction = false;
|
||||||
|
|
||||||
@ -78,22 +135,21 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rolls back the transaction in progress. You should only call this function
|
* Rolls back the transaction if one is in progress. If one is not in progress,
|
||||||
* if this object has a real transaction (HasTransaction() = true) because
|
* this is a NOP since the actual owner of the transaction outside of our
|
||||||
* otherwise, there is no transaction to roll back.
|
* scope is in charge of finally rolling back the transaction.
|
||||||
*/
|
*/
|
||||||
nsresult Rollback()
|
nsresult Rollback()
|
||||||
{
|
{
|
||||||
if (!mConnection || mCompleted)
|
if (!mConnection || mCompleted || !mHasTransaction)
|
||||||
return NS_OK; // no connection, or already done
|
return NS_OK;
|
||||||
mCompleted = true;
|
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;
|
nsresult rv = NS_OK;
|
||||||
do {
|
do {
|
||||||
rv = mConnection->RollbackTransaction();
|
rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ROLLBACK"));
|
||||||
if (rv == NS_ERROR_STORAGE_BUSY)
|
if (rv == NS_ERROR_STORAGE_BUSY)
|
||||||
(void)PR_Sleep(PR_INTERVAL_NO_WAIT);
|
(void)PR_Sleep(PR_INTERVAL_NO_WAIT);
|
||||||
} while (rv == NS_ERROR_STORAGE_BUSY);
|
} while (rv == NS_ERROR_STORAGE_BUSY);
|
||||||
@ -104,42 +160,14 @@ public:
|
|||||||
return rv;
|
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:
|
protected:
|
||||||
U mConnection;
|
nsCOMPtr<mozIStorageConnection> mConnection;
|
||||||
bool mHasTransaction;
|
bool mHasTransaction;
|
||||||
bool mCommitOnComplete;
|
bool mCommitOnComplete;
|
||||||
bool mCompleted;
|
bool mCompleted;
|
||||||
|
bool mAsyncCommit;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* An instance of the mozStorageTransaction<> family dedicated
|
|
||||||
* to |mozIStorageConnection|.
|
|
||||||
*/
|
|
||||||
typedef mozStorageTransactionBase<mozIStorageConnection,
|
|
||||||
nsCOMPtr<mozIStorageConnection> >
|
|
||||||
mozStorageTransaction;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class wraps a statement so that it is guaraneed to be reset when
|
* This class wraps a statement so that it is guaraneed to be reset when
|
||||||
* this object goes out of scope.
|
* this object goes out of scope.
|
||||||
|
@ -1218,6 +1218,7 @@ Connection::initializeClone(Connection* aClone, bool aReadOnly)
|
|||||||
"journal_size_limit",
|
"journal_size_limit",
|
||||||
"synchronous",
|
"synchronous",
|
||||||
"wal_autocheckpoint",
|
"wal_autocheckpoint",
|
||||||
|
"busy_timeout"
|
||||||
};
|
};
|
||||||
for (uint32_t i = 0; i < ArrayLength(pragmas); ++i) {
|
for (uint32_t i = 0; i < ArrayLength(pragmas); ++i) {
|
||||||
// Read-only connections just need cache_size and temp_store pragmas.
|
// Read-only connections just need cache_size and temp_store pragmas.
|
||||||
|
@ -120,6 +120,14 @@ public:
|
|||||||
::sqlite3_commit_hook(mDBConn, aCallbackFn, aData);
|
::sqlite3_commit_hook(mDBConn, aCallbackFn, aData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets autocommit status.
|
||||||
|
*/
|
||||||
|
bool getAutocommit() {
|
||||||
|
MOZ_ASSERT(mDBConn, "A connection must exist at this point");
|
||||||
|
return static_cast<bool>(::sqlite3_get_autocommit(mDBConn));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lazily creates and returns a background execution thread. In the future,
|
* 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
|
* the thread may be re-claimed if left idle, so you should call this
|
||||||
|
@ -5,9 +5,13 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "TestHarness.h"
|
#include "TestHarness.h"
|
||||||
|
|
||||||
#include "nsMemory.h"
|
#include "nsMemory.h"
|
||||||
|
#include "prthread.h"
|
||||||
#include "nsThreadUtils.h"
|
#include "nsThreadUtils.h"
|
||||||
#include "nsDirectoryServiceDefs.h"
|
#include "nsDirectoryServiceDefs.h"
|
||||||
|
#include "mozilla/ReentrantMonitor.h"
|
||||||
|
|
||||||
#include "mozIStorageService.h"
|
#include "mozIStorageService.h"
|
||||||
#include "mozIStorageConnection.h"
|
#include "mozIStorageConnection.h"
|
||||||
#include "mozIStorageStatementCallback.h"
|
#include "mozIStorageStatementCallback.h"
|
||||||
@ -18,7 +22,10 @@
|
|||||||
#include "mozIStorageStatement.h"
|
#include "mozIStorageStatement.h"
|
||||||
#include "mozIStoragePendingStatement.h"
|
#include "mozIStoragePendingStatement.h"
|
||||||
#include "mozIStorageError.h"
|
#include "mozIStorageError.h"
|
||||||
#include "nsThreadUtils.h"
|
#include "nsIInterfaceRequestorUtils.h"
|
||||||
|
#include "nsIEventTarget.h"
|
||||||
|
|
||||||
|
#include "sqlite3.h"
|
||||||
|
|
||||||
static int gTotalTests = 0;
|
static int gTotalTests = 0;
|
||||||
static int gPassedTests = 0;
|
static int gPassedTests = 0;
|
||||||
@ -53,13 +60,11 @@ static int gPassedTests = 0;
|
|||||||
do_check_true(aExpected == aActual)
|
do_check_true(aExpected == aActual)
|
||||||
#else
|
#else
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
// Print nsresult as uint32_t
|
// Print nsresult as uint32_t
|
||||||
std::ostream& operator<<(std::ostream& aStream, const nsresult aInput)
|
std::ostream& operator<<(std::ostream& aStream, const nsresult aInput)
|
||||||
{
|
{
|
||||||
return aStream << static_cast<uint32_t>(aInput);
|
return aStream << static_cast<uint32_t>(aInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define do_check_eq(aExpected, aActual) \
|
#define do_check_eq(aExpected, aActual) \
|
||||||
PR_BEGIN_MACRO \
|
PR_BEGIN_MACRO \
|
||||||
gTotalTests++; \
|
gTotalTests++; \
|
||||||
@ -74,6 +79,8 @@ std::ostream& operator<<(std::ostream& aStream, const nsresult aInput)
|
|||||||
PR_END_MACRO
|
PR_END_MACRO
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK)
|
||||||
|
|
||||||
already_AddRefed<mozIStorageService>
|
already_AddRefed<mozIStorageService>
|
||||||
getService()
|
getService()
|
||||||
{
|
{
|
||||||
@ -224,3 +231,159 @@ blocking_async_close(mozIStorageConnection *db)
|
|||||||
db->AsyncClose(spinner);
|
db->AsyncClose(spinner);
|
||||||
spinner->SpinUntilCompleted();
|
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<nsIThread>
|
||||||
|
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<mozIStorageAsyncStatement> stmt;
|
||||||
|
db->CreateAsyncStatement(
|
||||||
|
NS_LITERAL_CSTRING("SELECT 1"),
|
||||||
|
getter_AddRefs(stmt));
|
||||||
|
blocking_async_execute(stmt);
|
||||||
|
stmt->Finalize();
|
||||||
|
|
||||||
|
nsCOMPtr<nsIThreadManager> threadMan =
|
||||||
|
do_GetService("@mozilla.org/thread-manager;1");
|
||||||
|
nsCOMPtr<nsIThread> 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<nsIEventTarget> target = do_GetInterface(db);
|
||||||
|
nsCOMPtr<nsIThread> allegedAsyncThread = do_QueryInterface(target);
|
||||||
|
PRThread *allegedPRThread;
|
||||||
|
(void)allegedAsyncThread->GetPRThread(&allegedPRThread);
|
||||||
|
do_check_eq(allegedPRThread, last_non_watched_thread);
|
||||||
|
return asyncThread.forget();
|
||||||
|
}
|
||||||
|
@ -7,42 +7,19 @@
|
|||||||
#include "storage_test_harness.h"
|
#include "storage_test_harness.h"
|
||||||
|
|
||||||
#include "mozStorageHelper.h"
|
#include "mozStorageHelper.h"
|
||||||
|
#include "mozStorageConnection.h"
|
||||||
|
|
||||||
|
using namespace mozilla;
|
||||||
|
using namespace mozilla::storage;
|
||||||
|
|
||||||
|
bool has_transaction(mozIStorageConnection* aDB) {
|
||||||
|
return !(static_cast<Connection *>(aDB)->getAutocommit());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file test our Transaction helper in mozStorageHelper.h.
|
* This file test our Transaction helper in mozStorageHelper.h.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void
|
|
||||||
test_HasTransaction()
|
|
||||||
{
|
|
||||||
nsCOMPtr<mozIStorageConnection> 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
|
void
|
||||||
test_Commit()
|
test_Commit()
|
||||||
{
|
{
|
||||||
@ -52,11 +29,13 @@ test_Commit()
|
|||||||
// exists after the transaction falls out of scope.
|
// exists after the transaction falls out of scope.
|
||||||
{
|
{
|
||||||
mozStorageTransaction transaction(db, false);
|
mozStorageTransaction transaction(db, false);
|
||||||
|
do_check_true(has_transaction(db));
|
||||||
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||||
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
|
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
|
||||||
));
|
));
|
||||||
(void)transaction.Commit();
|
(void)transaction.Commit();
|
||||||
}
|
}
|
||||||
|
do_check_false(has_transaction(db));
|
||||||
|
|
||||||
bool exists = false;
|
bool exists = false;
|
||||||
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
|
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
|
||||||
@ -72,11 +51,13 @@ test_Rollback()
|
|||||||
// not exists after the transaction falls out of scope.
|
// not exists after the transaction falls out of scope.
|
||||||
{
|
{
|
||||||
mozStorageTransaction transaction(db, true);
|
mozStorageTransaction transaction(db, true);
|
||||||
|
do_check_true(has_transaction(db));
|
||||||
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||||
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
|
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
|
||||||
));
|
));
|
||||||
(void)transaction.Rollback();
|
(void)transaction.Rollback();
|
||||||
}
|
}
|
||||||
|
do_check_false(has_transaction(db));
|
||||||
|
|
||||||
bool exists = true;
|
bool exists = true;
|
||||||
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
|
(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.
|
// transaction falls out of scope. This means the Commit was successful.
|
||||||
{
|
{
|
||||||
mozStorageTransaction transaction(db, true);
|
mozStorageTransaction transaction(db, true);
|
||||||
|
do_check_true(has_transaction(db));
|
||||||
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||||
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
|
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
do_check_false(has_transaction(db));
|
||||||
|
|
||||||
bool exists = false;
|
bool exists = false;
|
||||||
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
|
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
|
||||||
@ -112,68 +95,78 @@ test_AutoRollback()
|
|||||||
// successful.
|
// successful.
|
||||||
{
|
{
|
||||||
mozStorageTransaction transaction(db, false);
|
mozStorageTransaction transaction(db, false);
|
||||||
|
do_check_true(has_transaction(db));
|
||||||
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||||
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
|
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
do_check_false(has_transaction(db));
|
||||||
|
|
||||||
bool exists = true;
|
bool exists = true;
|
||||||
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
|
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
|
||||||
do_check_false(exists);
|
do_check_false(exists);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
test_SetDefaultAction()
|
|
||||||
{
|
|
||||||
nsCOMPtr<mozIStorageConnection> 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
|
void
|
||||||
test_null_database_connection()
|
test_null_database_connection()
|
||||||
{
|
{
|
||||||
// We permit the use of the Transaction helper when passing a null database
|
// 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.
|
// in, so we need to make sure this still works without crashing.
|
||||||
mozStorageTransaction transaction(nullptr, false);
|
mozStorageTransaction transaction(nullptr, false);
|
||||||
|
|
||||||
do_check_false(transaction.HasTransaction());
|
|
||||||
do_check_true(NS_SUCCEEDED(transaction.Commit()));
|
do_check_true(NS_SUCCEEDED(transaction.Commit()));
|
||||||
do_check_true(NS_SUCCEEDED(transaction.Rollback()));
|
do_check_true(NS_SUCCEEDED(transaction.Rollback()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
test_async_Commit()
|
||||||
|
{
|
||||||
|
// note this will be active for any following test.
|
||||||
|
hook_sqlite_mutex();
|
||||||
|
|
||||||
|
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
|
||||||
|
|
||||||
|
// -- wedge the thread
|
||||||
|
nsCOMPtr<nsIThread> target(get_conn_async_thread(db));
|
||||||
|
do_check_true(target);
|
||||||
|
nsRefPtr<ThreadWedger> 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<mozIStorageAsyncStatement> 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) = {
|
void (*gTests[])(void) = {
|
||||||
test_HasTransaction,
|
|
||||||
test_Commit,
|
test_Commit,
|
||||||
test_Rollback,
|
test_Rollback,
|
||||||
test_AutoCommit,
|
test_AutoCommit,
|
||||||
test_AutoRollback,
|
test_AutoRollback,
|
||||||
test_SetDefaultAction,
|
|
||||||
test_null_database_connection,
|
test_null_database_connection,
|
||||||
|
test_async_Commit,
|
||||||
};
|
};
|
||||||
|
|
||||||
const char *file = __FILE__;
|
const char *file = __FILE__;
|
||||||
|
@ -5,175 +5,6 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "storage_test_harness.h"
|
#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<nsIThread>
|
|
||||||
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<mozIStorageAsyncStatement> stmt;
|
|
||||||
db->CreateAsyncStatement(
|
|
||||||
NS_LITERAL_CSTRING("SELECT 1"),
|
|
||||||
getter_AddRefs(stmt));
|
|
||||||
blocking_async_execute(stmt);
|
|
||||||
stmt->Finalize();
|
|
||||||
|
|
||||||
nsCOMPtr<nsIThreadManager> threadMan =
|
|
||||||
do_GetService("@mozilla.org/thread-manager;1");
|
|
||||||
nsCOMPtr<nsIThread> 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<nsIEventTarget> target = do_GetInterface(db);
|
|
||||||
nsCOMPtr<nsIThread> allegedAsyncThread = do_QueryInterface(target);
|
|
||||||
PRThread *allegedPRThread;
|
|
||||||
(void)allegedAsyncThread->GetPRThread(&allegedPRThread);
|
|
||||||
do_check_eq(allegedPRThread, last_non_watched_thread);
|
|
||||||
return asyncThread.forget();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//// Tests
|
//// Tests
|
||||||
|
@ -736,6 +736,7 @@ add_task(function test_clone_copies_pragmas()
|
|||||||
{ name: "journal_size_limit", value: 524288, copied: true },
|
{ name: "journal_size_limit", value: 524288, copied: true },
|
||||||
{ name: "synchronous", value: 2, copied: true },
|
{ name: "synchronous", value: 2, copied: true },
|
||||||
{ name: "wal_autocheckpoint", value: 16, copied: true },
|
{ name: "wal_autocheckpoint", value: 16, copied: true },
|
||||||
|
{ name: "busy_timeout", value: 50, copied: true },
|
||||||
{ name: "ignore_check_constraints", value: 1, copied: false },
|
{ 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: "journal_size_limit", value: 524288, copied: false },
|
||||||
{ name: "synchronous", value: 2, copied: false },
|
{ name: "synchronous", value: 2, copied: false },
|
||||||
{ name: "wal_autocheckpoint", value: 16, copied: false },
|
{ name: "wal_autocheckpoint", value: 16, copied: false },
|
||||||
|
{ name: "busy_timeout", value: 50, copied: false },
|
||||||
{ name: "ignore_check_constraints", value: 1, copied: false },
|
{ name: "ignore_check_constraints", value: 1, copied: false },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user