зеркало из https://github.com/mozilla/gecko-dev.git
Bug 874814 - Close AsyncClose() off the main thread and loop until all statements are finalized. r=mak
This commit is contained in:
Родитель
546e845cf4
Коммит
007c70de56
|
@ -117,21 +117,20 @@ void
|
|||
StorageBaseStatementInternal::asyncFinalize()
|
||||
{
|
||||
nsIEventTarget *target = mDBConnection->getAsyncExecutionTarget();
|
||||
if (!target) {
|
||||
// If we cannot get the background thread, we have to assume it has been
|
||||
// shutdown (or is in the process of doing so). As a result, we should
|
||||
// just finalize it here and now.
|
||||
destructorAsyncFinalize();
|
||||
}
|
||||
else {
|
||||
if (target) {
|
||||
// Attempt to finalize asynchronously
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
new AsyncStatementFinalizer(this, mDBConnection);
|
||||
|
||||
// If the dispatching did not go as planned, finalize now.
|
||||
if (NS_FAILED(target->Dispatch(event, NS_DISPATCH_NORMAL))) {
|
||||
destructorAsyncFinalize();
|
||||
}
|
||||
// Dispatch. Note that dispatching can fail, typically if
|
||||
// we have a race condition with asyncClose(). It's ok,
|
||||
// let asyncClose() win.
|
||||
(void)target->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
// If we cannot get the background thread,
|
||||
// mozStorageConnection::AsyncClose() has already been called and
|
||||
// the statement either has been or will be cleaned up by
|
||||
// internalClose().
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -140,17 +139,28 @@ StorageBaseStatementInternal::destructorAsyncFinalize()
|
|||
if (!mAsyncStatement)
|
||||
return;
|
||||
|
||||
// If we reach this point, our owner has not finalized this
|
||||
// statement, yet we are being destructed. If possible, we want to
|
||||
// auto-finalize it early, to release the resources early.
|
||||
nsIEventTarget *target = mDBConnection->getAsyncExecutionTarget();
|
||||
if (target) {
|
||||
// If we can get the async execution target, we can indeed finalize
|
||||
// the statement, as the connection is still open.
|
||||
bool isAsyncThread = false;
|
||||
(void)target->IsOnCurrentThread(&isAsyncThread);
|
||||
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
new LastDitchSqliteStatementFinalizer(mDBConnection, mAsyncStatement);
|
||||
if (NS_SUCCEEDED(target->Dispatch(event, NS_DISPATCH_NORMAL))) {
|
||||
mAsyncStatement = nullptr;
|
||||
return;
|
||||
if (isAsyncThread) {
|
||||
(void)event->Run();
|
||||
} else {
|
||||
(void)target->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
}
|
||||
// (no async thread remains or we could not dispatch to it)
|
||||
(void)::sqlite3_finalize(mAsyncStatement);
|
||||
|
||||
// We might not be able to dispatch to the background thread,
|
||||
// presumably because it is being shutdown. Since said shutdown will
|
||||
// finalize the statement, we just need to clean-up around here.
|
||||
mAsyncStatement = nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -133,12 +133,10 @@ public:
|
|||
* calling thread).
|
||||
*/
|
||||
CompletionNotifier(mozIStorageStatementCallback *aCallback,
|
||||
ExecutionState aReason,
|
||||
StatementDataArray &aStatementData)
|
||||
ExecutionState aReason)
|
||||
: mCallback(aCallback)
|
||||
, mReason(aReason)
|
||||
{
|
||||
mStatementData.SwapElements(aStatementData);
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
|
@ -148,16 +146,10 @@ public:
|
|||
NS_RELEASE(mCallback);
|
||||
}
|
||||
|
||||
// The async thread could still hold onto a reference to us, so we need to
|
||||
// make sure we release our reference to the StatementData now in case our
|
||||
// destructor happens in a different thread.
|
||||
mStatementData.Clear();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
StatementDataArray mStatementData;
|
||||
mozIStorageStatementCallback *mCallback;
|
||||
ExecutionState mReason;
|
||||
};
|
||||
|
@ -428,11 +420,17 @@ AsyncExecuteStatements::notifyComplete()
|
|||
NS_ASSERTION(mState != PENDING,
|
||||
"Still in a pending state when calling Complete!");
|
||||
|
||||
// Finalize our statements before we try to commit or rollback. If we are
|
||||
// Reset our statements before we try to commit or rollback. If we are
|
||||
// canceling and have statements that think they have pending work, the
|
||||
// rollback will fail.
|
||||
for (uint32_t i = 0; i < mStatements.Length(); i++)
|
||||
mStatements[i].finalize();
|
||||
mStatements[i].reset();
|
||||
|
||||
// Release references to the statement data as soon as possible. If this
|
||||
// is the last reference, statements will be finalized immediately on the
|
||||
// async thread, hence avoiding several bounces between threads and possible
|
||||
// race conditions with AsyncClose().
|
||||
mStatements.Clear();
|
||||
|
||||
// Handle our transaction, if we have one
|
||||
if (mTransactionManager) {
|
||||
|
@ -454,9 +452,7 @@ AsyncExecuteStatements::notifyComplete()
|
|||
// Always generate a completion notification; it is what guarantees that our
|
||||
// destruction does not happen here on the async thread.
|
||||
nsRefPtr<CompletionNotifier> completionEvent =
|
||||
new CompletionNotifier(mCallback, mState, mStatements);
|
||||
NS_ASSERTION(mStatements.IsEmpty(),
|
||||
"Should have given up ownership of mStatements!");
|
||||
new CompletionNotifier(mCallback, mState);
|
||||
|
||||
// We no longer own mCallback (the CompletionNotifier takes ownership).
|
||||
mCallback = nullptr;
|
||||
|
|
|
@ -329,15 +329,13 @@ WaitForUnlockNotify(sqlite3* aDatabase)
|
|||
|
||||
namespace {
|
||||
|
||||
class AsyncCloseConnection : public nsRunnable
|
||||
class AsyncCloseConnection MOZ_FINAL: public nsRunnable
|
||||
{
|
||||
public:
|
||||
AsyncCloseConnection(Connection *aConnection,
|
||||
nsIEventTarget *aCallingThread,
|
||||
nsIRunnable *aCallbackEvent,
|
||||
already_AddRefed<nsIThread> aAsyncExecutionThread)
|
||||
: mConnection(aConnection)
|
||||
, mCallingThread(aCallingThread)
|
||||
, mCallbackEvent(aCallbackEvent)
|
||||
, mAsyncExecutionThread(aAsyncExecutionThread)
|
||||
{
|
||||
|
@ -345,46 +343,39 @@ public:
|
|||
|
||||
NS_METHOD Run()
|
||||
{
|
||||
// This event is first dispatched to the background thread to ensure that
|
||||
// all pending asynchronous events are completed, and then back to the
|
||||
// calling thread to actually close and notify.
|
||||
bool onCallingThread = false;
|
||||
(void)mCallingThread->IsOnCurrentThread(&onCallingThread);
|
||||
if (!onCallingThread) {
|
||||
#ifdef DEBUG
|
||||
{
|
||||
bool onAsyncThread = false;
|
||||
(void)mAsyncExecutionThread->IsOnCurrentThread(&onAsyncThread);
|
||||
MOZ_ASSERT(onAsyncThread);
|
||||
}
|
||||
#endif
|
||||
(void)mCallingThread->Dispatch(this, NS_DISPATCH_NORMAL);
|
||||
return NS_OK;
|
||||
}
|
||||
// This code is executed on the background thread
|
||||
bool onAsyncThread = false;
|
||||
(void)mAsyncExecutionThread->IsOnCurrentThread(&onAsyncThread);
|
||||
MOZ_ASSERT(onAsyncThread);
|
||||
#endif // DEBUG
|
||||
|
||||
// Internal close.
|
||||
(void)mConnection->internalClose();
|
||||
if (mCallbackEvent)
|
||||
(void)mCallingThread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL);
|
||||
|
||||
(void)mAsyncExecutionThread->Shutdown();
|
||||
|
||||
// Because we have no guarantee that the invocation of this method on the
|
||||
// asynchronous thread has fully completed (including the Release of the
|
||||
// reference to this object held by that event loop), we need to explicitly
|
||||
// null out our pointers here. It is possible this object will be destroyed
|
||||
// on the asynchronous thread and if the references are still alive we will
|
||||
// release them on that thread. We definitely do not want that for
|
||||
// mConnection and it's nice to avoid for mCallbackEvent too. We do not
|
||||
// null out mCallingThread because it is conceivable the async thread might
|
||||
// still be 'in' the object.
|
||||
mConnection = nullptr;
|
||||
mCallbackEvent = nullptr;
|
||||
// Callback
|
||||
if (mCallbackEvent) {
|
||||
nsCOMPtr<nsIThread> thread;
|
||||
(void)NS_GetMainThread(getter_AddRefs(thread));
|
||||
(void)thread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
~AsyncCloseConnection() {
|
||||
nsCOMPtr<nsIThread> thread;
|
||||
(void)NS_GetMainThread(getter_AddRefs(thread));
|
||||
// Handle ambiguous nsISupports inheritance.
|
||||
Connection *rawConnection = nullptr;
|
||||
mConnection.swap(rawConnection);
|
||||
(void)NS_ProxyRelease(thread,
|
||||
NS_ISUPPORTS_CAST(mozIStorageConnection *,
|
||||
rawConnection));
|
||||
(void)NS_ProxyRelease(thread, mCallbackEvent);
|
||||
}
|
||||
private:
|
||||
nsRefPtr<Connection> mConnection;
|
||||
nsCOMPtr<nsIEventTarget> mCallingThread;
|
||||
nsCOMPtr<nsIRunnable> mCallbackEvent;
|
||||
nsCOMPtr<nsIThread> mAsyncExecutionThread;
|
||||
};
|
||||
|
@ -819,13 +810,9 @@ Connection::internalClose()
|
|||
"Did not call setClosedState!");
|
||||
}
|
||||
|
||||
{ // Ensure that we are being called on the thread we were opened with.
|
||||
bool onOpenedThread = false;
|
||||
(void)threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
|
||||
NS_ASSERTION(onOpenedThread,
|
||||
"Not called on the thread the database was opened on!");
|
||||
}
|
||||
#endif
|
||||
bool onOpeningThread = false;
|
||||
(void)threadOpenedOn->IsOnCurrentThread(&onOpeningThread);
|
||||
#endif // DEBUG
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
nsAutoCString leafName(":memory");
|
||||
|
@ -835,20 +822,62 @@ Connection::internalClose()
|
|||
leafName.get()));
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
// Notify about any non-finalized statements.
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
while ((stmt = ::sqlite3_next_stmt(mDBConn, stmt))) {
|
||||
char *msg = ::PR_smprintf("SQL statement '%s' was not finalized",
|
||||
::sqlite3_sql(stmt));
|
||||
NS_WARNING(msg);
|
||||
::PR_smprintf_free(msg);
|
||||
}
|
||||
#endif
|
||||
// At this stage, we may still have statements that need to be
|
||||
// finalized. Attempt to close the database connection. This will
|
||||
// always disconnect any virtual tables and cleanly finalize their
|
||||
// internal statements. Once this is done, closing may fail due to
|
||||
// unfinalized client statements, in which case we need to finalize
|
||||
// these statements and close again.
|
||||
|
||||
int srv = ::sqlite3_close(mDBConn);
|
||||
NS_ASSERTION(srv == SQLITE_OK,
|
||||
int srv = sqlite3_close(mDBConn);
|
||||
|
||||
if (srv == SQLITE_BUSY) {
|
||||
// We still have non-finalized statements. Finalize them.
|
||||
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
while ((stmt = ::sqlite3_next_stmt(mDBConn, stmt))) {
|
||||
PR_LOG(gStorageLog, PR_LOG_NOTICE,
|
||||
("Auto-finalizing SQL statement '%s' (%x)",
|
||||
::sqlite3_sql(stmt),
|
||||
stmt));
|
||||
|
||||
#ifdef DEBUG
|
||||
char *msg = ::PR_smprintf("SQL statement '%s' (%x) should have been finalized",
|
||||
::sqlite3_sql(stmt),
|
||||
stmt);
|
||||
NS_WARNING(msg);
|
||||
::PR_smprintf_free(msg);
|
||||
#endif // DEBUG
|
||||
|
||||
srv = ::sqlite3_finalize(stmt);
|
||||
|
||||
#ifdef DEBUG
|
||||
if (srv != SQLITE_OK) {
|
||||
char *msg = ::PR_smprintf("Could not finalize SQL statement '%s' (%x)",
|
||||
::sqlite3_sql(stmt),
|
||||
stmt);
|
||||
NS_WARNING(msg);
|
||||
::PR_smprintf_free(msg);
|
||||
}
|
||||
#endif // DEBUG
|
||||
|
||||
// Ensure that the loop continues properly, whether closing has succeeded
|
||||
// or not.
|
||||
if (srv == SQLITE_OK) {
|
||||
stmt = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Now that all statements have been finalized, we
|
||||
// shoudl be able to close.
|
||||
srv = ::sqlite3_close(mDBConn);
|
||||
|
||||
}
|
||||
|
||||
if (srv != SQLITE_OK) {
|
||||
MOZ_ASSERT(srv == SQLITE_OK,
|
||||
"sqlite3_close failed. There are probably outstanding statements that are listed above!");
|
||||
}
|
||||
|
||||
mDBConn = nullptr;
|
||||
return convertResultCode(srv);
|
||||
|
@ -1047,7 +1076,7 @@ Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
|
|||
return NS_ERROR_NOT_INITIALIZED;
|
||||
|
||||
nsIEventTarget *asyncThread = getAsyncExecutionTarget();
|
||||
NS_ENSURE_TRUE(asyncThread, NS_ERROR_UNEXPECTED);
|
||||
NS_ENSURE_TRUE(asyncThread, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
nsresult rv = setClosedState();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -1063,7 +1092,7 @@ Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
|
|||
{
|
||||
// We need to lock because we're modifying mAsyncExecutionThread
|
||||
MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
|
||||
closeEvent = new AsyncCloseConnection(this, NS_GetCurrentThread(),
|
||||
closeEvent = new AsyncCloseConnection(this,
|
||||
completeEvent,
|
||||
mAsyncExecutionThread.forget());
|
||||
}
|
||||
|
|
|
@ -47,6 +47,14 @@ public:
|
|||
StatementData()
|
||||
{
|
||||
}
|
||||
~StatementData()
|
||||
{
|
||||
// We need to ensure that mParamsArray is released on the main thread,
|
||||
// as the binding arguments may be XPConnect values, which are safe
|
||||
// to release only on the main thread.
|
||||
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
|
||||
(void)NS_ProxyRelease(mainThread, mParamsArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sqlite statement, fetching it from the storage statement. In
|
||||
|
@ -77,7 +85,7 @@ public:
|
|||
* NULLs out our sqlite3_stmt (it is held by the owner) after reseting it and
|
||||
* clear all bindings to it. This is expected to occur on the async thread.
|
||||
*/
|
||||
inline void finalize()
|
||||
inline void reset()
|
||||
{
|
||||
NS_PRECONDITION(mStatementOwner, "Must have a statement owner!");
|
||||
#ifdef DEBUG
|
||||
|
|
|
@ -228,9 +228,13 @@ function test_double_asyncClose_throws()
|
|||
try {
|
||||
conn.asyncClose();
|
||||
do_throw("should have thrown");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_eq(e.result, Cr.NS_ERROR_UNEXPECTED);
|
||||
// There is a small race condition here, which can cause either of
|
||||
// Cr.NS_ERROR_NOT_INITIALIZED or Cr.NS_ERROR_UNEXPECTED to be thrown.
|
||||
} catch (e if "result" in e && e.result == Cr.NS_ERROR_NOT_INITIALIZED) {
|
||||
do_print("NS_ERROR_NOT_INITIALIZED");
|
||||
} catch (e if "result" in e && e.result == Cr.NS_ERROR_UNEXPECTED) {
|
||||
do_print("NS_ERROR_UNEXPECTED");
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
// Reset gDBConn so that later tests will get a new connection object.
|
||||
|
|
|
@ -289,7 +289,6 @@ add_task(function test_asyncClose_succeeds_with_finalized_async_statement()
|
|||
stmt.executeAsync();
|
||||
stmt.finalize();
|
||||
|
||||
let deferred = Promise.defer();
|
||||
yield asyncClose(getOpenedDatabase());
|
||||
// Reset gDBConn so that later tests will get a new connection object.
|
||||
gDBConn = null;
|
||||
|
@ -359,7 +358,7 @@ function standardAsyncTest(promisedDB, name, shouldInit = false) {
|
|||
}
|
||||
|
||||
// Generate a name to insert and fetch back
|
||||
let name = "worker bee " + Math.random() + " (" + name + ")";
|
||||
name = "worker bee " + Math.random() + " (" + name + ")";
|
||||
|
||||
let stmt = adb.createAsyncStatement("INSERT INTO test (name) VALUES (:name)");
|
||||
stmt.params.name = name;
|
||||
|
|
Загрузка…
Ссылка в новой задаче