forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			579 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			579 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 | |
|  * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
 | |
|  * This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "sqlite3.h"
 | |
| 
 | |
| #include "mozIStorageStatementCallback.h"
 | |
| #include "mozStorageBindingParams.h"
 | |
| #include "mozStorageHelper.h"
 | |
| #include "mozStorageResultSet.h"
 | |
| #include "mozStorageRow.h"
 | |
| #include "mozStorageConnection.h"
 | |
| #include "mozStorageError.h"
 | |
| #include "mozStoragePrivateHelpers.h"
 | |
| #include "mozStorageStatementData.h"
 | |
| #include "mozStorageAsyncStatementExecution.h"
 | |
| 
 | |
| #include "mozilla/DebugOnly.h"
 | |
| #include "mozilla/Telemetry.h"
 | |
| 
 | |
| #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
 | |
| extern mozilla::LazyLogModule gStorageLog;
 | |
| #endif
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace storage {
 | |
| 
 | |
| /**
 | |
|  * The following constants help batch rows into result sets.
 | |
|  * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that
 | |
|  * takes less than 200 milliseconds is considered to feel instantaneous to end
 | |
|  * users.  MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of
 | |
|  * dispatches to calling thread, while also providing reasonably-sized sets of
 | |
|  * data for consumers.  Both of these constants are used because we assume that
 | |
|  * consumers are trying to avoid blocking their execution thread for long
 | |
|  * periods of time, and dispatching many small events to the calling thread will
 | |
|  * end up blocking it.
 | |
|  */
 | |
| #define MAX_MILLISECONDS_BETWEEN_RESULTS 75
 | |
| #define MAX_ROWS_PER_RESULT 15
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //// AsyncExecuteStatements
 | |
| 
 | |
| /* static */
 | |
| nsresult AsyncExecuteStatements::execute(
 | |
|     StatementDataArray&& aStatements, Connection* aConnection,
 | |
|     sqlite3* aNativeConnection, mozIStorageStatementCallback* aCallback,
 | |
|     mozIStoragePendingStatement** _stmt) {
 | |
|   // Create our event to run in the background
 | |
|   RefPtr<AsyncExecuteStatements> event = new AsyncExecuteStatements(
 | |
|       std::move(aStatements), aConnection, aNativeConnection, aCallback);
 | |
|   NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
 | |
| 
 | |
|   // Dispatch it to the background
 | |
|   nsIEventTarget* target = aConnection->getAsyncExecutionTarget();
 | |
| 
 | |
|   // If we don't have a valid target, this is a bug somewhere else. In the past,
 | |
|   // this assert found cases where a Run method would schedule a new statement
 | |
|   // without checking if asyncClose had been called. The caller must prevent
 | |
|   // that from happening or, if the work is not critical, just avoid creating
 | |
|   // the new statement during shutdown. See bug 718449 for an example.
 | |
|   MOZ_ASSERT(target);
 | |
|   if (!target) {
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Return it as the pending statement object and track it.
 | |
|   event.forget(_stmt);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| AsyncExecuteStatements::AsyncExecuteStatements(
 | |
|     StatementDataArray&& aStatements, Connection* aConnection,
 | |
|     sqlite3* aNativeConnection, mozIStorageStatementCallback* aCallback)
 | |
|     : Runnable("AsyncExecuteStatements"),
 | |
|       mStatements(std::move(aStatements)),
 | |
|       mConnection(aConnection),
 | |
|       mNativeConnection(aNativeConnection),
 | |
|       mHasTransaction(false),
 | |
|       mCallback(aCallback),
 | |
|       mCallingThread(::do_GetCurrentThread()),
 | |
|       mMaxWait(
 | |
|           TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS)),
 | |
|       mIntervalStart(TimeStamp::Now()),
 | |
|       mState(PENDING),
 | |
|       mCancelRequested(false),
 | |
|       mMutex(aConnection->sharedAsyncExecutionMutex),
 | |
|       mDBMutex(aConnection->sharedDBMutex) {
 | |
|   NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
 | |
| }
 | |
| 
 | |
| AsyncExecuteStatements::~AsyncExecuteStatements() {
 | |
|   MOZ_ASSERT(!mCallback, "Never called the Completion callback!");
 | |
|   MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point");
 | |
|   if (mCallback) {
 | |
|     NS_ProxyRelease("AsyncExecuteStatements::mCallback", mCallingThread,
 | |
|                     mCallback.forget());
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool AsyncExecuteStatements::shouldNotify() {
 | |
| #ifdef DEBUG
 | |
|   mMutex.AssertNotCurrentThreadOwns();
 | |
| 
 | |
|   bool onCallingThread = false;
 | |
|   (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
 | |
|   NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
 | |
| #endif
 | |
| 
 | |
|   // We do not need to acquire mMutex here because it can only ever be written
 | |
|   // to on the calling thread, and the only thread that can call us is the
 | |
|   // calling thread, so we know that our access is serialized.
 | |
|   return !mCancelRequested;
 | |
| }
 | |
| 
 | |
| bool AsyncExecuteStatements::bindExecuteAndProcessStatement(
 | |
|     StatementData& aData, bool aLastStatement) {
 | |
|   mMutex.AssertNotCurrentThreadOwns();
 | |
| 
 | |
|   sqlite3_stmt* aStatement = nullptr;
 | |
|   // This cannot fail; we are only called if it's available.
 | |
|   Unused << aData.getSqliteStatement(&aStatement);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(
 | |
|       aStatement,
 | |
|       "bindExecuteAndProcessStatement called without an initialized statement");
 | |
|   BindingParamsArray* paramsArray(aData);
 | |
| 
 | |
|   // Iterate through all of our parameters, bind them, and execute.
 | |
|   bool continueProcessing = true;
 | |
|   BindingParamsArray::iterator itr = paramsArray->begin();
 | |
|   BindingParamsArray::iterator end = paramsArray->end();
 | |
|   while (itr != end && continueProcessing) {
 | |
|     // Bind the data to our statement.
 | |
|     nsCOMPtr<IStorageBindingParamsInternal> bindingInternal =
 | |
|         do_QueryInterface(*itr);
 | |
|     nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement);
 | |
|     if (error) {
 | |
|       // Set our error state.
 | |
|       mState = ERROR;
 | |
| 
 | |
|       // And notify.
 | |
|       (void)notifyError(error);
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // Advance our iterator, execute, and then process the statement.
 | |
|     itr++;
 | |
|     bool lastStatement = aLastStatement && itr == end;
 | |
|     continueProcessing = executeAndProcessStatement(aData, lastStatement);
 | |
| 
 | |
|     // Always reset our statement.
 | |
|     (void)::sqlite3_reset(aStatement);
 | |
|   }
 | |
| 
 | |
|   return continueProcessing;
 | |
| }
 | |
| 
 | |
| bool AsyncExecuteStatements::executeAndProcessStatement(StatementData& aData,
 | |
|                                                         bool aLastStatement) {
 | |
|   mMutex.AssertNotCurrentThreadOwns();
 | |
| 
 | |
|   sqlite3_stmt* aStatement = nullptr;
 | |
|   // This cannot fail; we are only called if it's available.
 | |
|   Unused << aData.getSqliteStatement(&aStatement);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(
 | |
|       aStatement,
 | |
|       "executeAndProcessStatement called without an initialized statement");
 | |
| 
 | |
|   // Execute our statement
 | |
|   bool hasResults;
 | |
|   do {
 | |
|     hasResults = executeStatement(aData);
 | |
| 
 | |
|     // If we had an error, bail.
 | |
|     if (mState == ERROR || mState == CANCELED) return false;
 | |
| 
 | |
|     // If we have been canceled, there is no point in going on...
 | |
|     {
 | |
|       MutexAutoLock lockedScope(mMutex);
 | |
|       if (mCancelRequested) {
 | |
|         mState = CANCELED;
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Build our result set and notify if we got anything back and have a
 | |
|     // callback to notify.
 | |
|     if (mCallback && hasResults &&
 | |
|         NS_FAILED(buildAndNotifyResults(aStatement))) {
 | |
|       // We had an error notifying, so we notify on error and stop processing.
 | |
|       mState = ERROR;
 | |
| 
 | |
|       // Notify, and stop processing statements.
 | |
|       (void)notifyError(mozIStorageError::ERROR,
 | |
|                         "An error occurred while notifying about results");
 | |
| 
 | |
|       return false;
 | |
|     }
 | |
|   } while (hasResults);
 | |
| 
 | |
| #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
 | |
|   if (MOZ_LOG_TEST(gStorageLog, LogLevel::Warning))
 | |
| #endif
 | |
|   {
 | |
|     // Check to make sure that this statement was smart about what it did.
 | |
|     checkAndLogStatementPerformance(aStatement);
 | |
|   }
 | |
| 
 | |
|   // If we are done, we need to set our state accordingly while we still hold
 | |
|   // our mutex.  We would have already returned if we were canceled or had
 | |
|   // an error at this point.
 | |
|   if (aLastStatement) mState = COMPLETED;
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool AsyncExecuteStatements::executeStatement(StatementData& aData) {
 | |
|   mMutex.AssertNotCurrentThreadOwns();
 | |
| 
 | |
|   sqlite3_stmt* aStatement = nullptr;
 | |
|   // This cannot fail; we are only called if it's available.
 | |
|   Unused << aData.getSqliteStatement(&aStatement);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(
 | |
|       aStatement, "executeStatement called without an initialized statement");
 | |
| 
 | |
|   bool busyRetry = false;
 | |
|   while (true) {
 | |
|     if (busyRetry) {
 | |
|       busyRetry = false;
 | |
| 
 | |
|       // Yield, and try again
 | |
|       Unused << PR_Sleep(PR_INTERVAL_NO_WAIT);
 | |
| 
 | |
|       // Check for cancellation before retrying
 | |
|       {
 | |
|         MutexAutoLock lockedScope(mMutex);
 | |
|         if (mCancelRequested) {
 | |
|           mState = CANCELED;
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // lock the sqlite mutex so sqlite3_errmsg cannot change
 | |
|     SQLiteMutexAutoLock lockedScope(mDBMutex);
 | |
| 
 | |
|     int rc = mConnection->stepStatement(mNativeConnection, aStatement);
 | |
| 
 | |
|     // Some errors are not fatal, and we can handle them and continue.
 | |
|     if (rc == SQLITE_BUSY) {
 | |
|       ::sqlite3_reset(aStatement);
 | |
|       busyRetry = true;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     aData.MaybeRecordQueryStatus(rc);
 | |
| 
 | |
|     // Stop if we have no more results.
 | |
|     if (rc == SQLITE_DONE) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // If we got results, we can return now.
 | |
|     if (rc == SQLITE_ROW) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (rc == SQLITE_INTERRUPT) {
 | |
|       mState = CANCELED;
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // Set an error state.
 | |
|     mState = ERROR;
 | |
| 
 | |
|     // Construct the error message before giving up the mutex (which we cannot
 | |
|     // hold during the call to notifyError).
 | |
|     nsCOMPtr<mozIStorageError> errorObj(
 | |
|         new Error(rc, ::sqlite3_errmsg(mNativeConnection)));
 | |
|     // We cannot hold the DB mutex while calling notifyError.
 | |
|     SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
 | |
|     (void)notifyError(errorObj);
 | |
| 
 | |
|     // Finally, indicate that we should stop processing.
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult AsyncExecuteStatements::buildAndNotifyResults(
 | |
|     sqlite3_stmt* aStatement) {
 | |
|   NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
 | |
|   mMutex.AssertNotCurrentThreadOwns();
 | |
| 
 | |
|   // Build result object if we need it.
 | |
|   if (!mResultSet) mResultSet = new ResultSet();
 | |
|   NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
 | |
| 
 | |
|   RefPtr<Row> row(new Row());
 | |
|   NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
 | |
| 
 | |
|   nsresult rv = row->initialize(aStatement);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   rv = mResultSet->add(row);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // If we have hit our maximum number of allowed results, or if we have hit
 | |
|   // the maximum amount of time we want to wait for results, notify the
 | |
|   // calling thread about it.
 | |
|   TimeStamp now = TimeStamp::Now();
 | |
|   TimeDuration delta = now - mIntervalStart;
 | |
|   if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) {
 | |
|     // Notify the caller
 | |
|     rv = notifyResults();
 | |
|     if (NS_FAILED(rv)) return NS_OK;  // we'll try again with the next result
 | |
| 
 | |
|     // Reset our start time
 | |
|     mIntervalStart = now;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult AsyncExecuteStatements::notifyComplete() {
 | |
|   mMutex.AssertNotCurrentThreadOwns();
 | |
|   NS_ASSERTION(mState != PENDING,
 | |
|                "Still in a pending state when calling Complete!");
 | |
| 
 | |
|   // 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].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 (mHasTransaction) {
 | |
|     SQLiteMutexAutoLock lockedScope(mDBMutex);
 | |
|     if (mState == COMPLETED) {
 | |
|       nsresult rv = mConnection->commitTransactionInternal(lockedScope,
 | |
|                                                            mNativeConnection);
 | |
|       if (NS_FAILED(rv)) {
 | |
|         mState = ERROR;
 | |
|         // We cannot hold the DB mutex while calling notifyError.
 | |
|         SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
 | |
|         (void)notifyError(mozIStorageError::ERROR,
 | |
|                           "Transaction failed to commit");
 | |
|       }
 | |
|     } else {
 | |
|       DebugOnly<nsresult> rv = mConnection->rollbackTransactionInternal(
 | |
|           lockedScope, mNativeConnection);
 | |
|       NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Transaction failed to rollback");
 | |
|     }
 | |
|     mHasTransaction = false;
 | |
|   }
 | |
| 
 | |
|   // This will take ownership of mCallback and make sure its destruction will
 | |
|   // happen on the owner thread.
 | |
|   Unused << mCallingThread->Dispatch(
 | |
|       NewRunnableMethod("AsyncExecuteStatements::notifyCompleteOnCallingThread",
 | |
|                         this,
 | |
|                         &AsyncExecuteStatements::notifyCompleteOnCallingThread),
 | |
|       NS_DISPATCH_NORMAL);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult AsyncExecuteStatements::notifyCompleteOnCallingThread() {
 | |
|   MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
 | |
|   // Take ownership of mCallback and responsibility for freeing it when we
 | |
|   // release it.  Any notifyResultsOnCallingThread and
 | |
|   // notifyErrorOnCallingThread calls on the stack spinning the event loop have
 | |
|   // guaranteed their safety by creating their own strong reference before
 | |
|   // invoking the callback.
 | |
|   nsCOMPtr<mozIStorageStatementCallback> callback = std::move(mCallback);
 | |
|   if (callback) {
 | |
|     Unused << callback->HandleCompletion(mState);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult AsyncExecuteStatements::notifyError(int32_t aErrorCode,
 | |
|                                              const char* aMessage) {
 | |
|   mMutex.AssertNotCurrentThreadOwns();
 | |
|   mDBMutex.assertNotCurrentThreadOwns();
 | |
| 
 | |
|   if (!mCallback) return NS_OK;
 | |
| 
 | |
|   nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
 | |
|   NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
 | |
| 
 | |
|   return notifyError(errorObj);
 | |
| }
 | |
| 
 | |
| nsresult AsyncExecuteStatements::notifyError(mozIStorageError* aError) {
 | |
|   mMutex.AssertNotCurrentThreadOwns();
 | |
|   mDBMutex.assertNotCurrentThreadOwns();
 | |
| 
 | |
|   if (!mCallback) return NS_OK;
 | |
| 
 | |
|   Unused << mCallingThread->Dispatch(
 | |
|       NewRunnableMethod<nsCOMPtr<mozIStorageError>>(
 | |
|           "AsyncExecuteStatements::notifyErrorOnCallingThread", this,
 | |
|           &AsyncExecuteStatements::notifyErrorOnCallingThread, aError),
 | |
|       NS_DISPATCH_NORMAL);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult AsyncExecuteStatements::notifyErrorOnCallingThread(
 | |
|     mozIStorageError* aError) {
 | |
|   MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
 | |
|   // Acquire our own strong reference so that if the callback spins a nested
 | |
|   // event loop and notifyCompleteOnCallingThread is executed, forgetting
 | |
|   // mCallback, we still have a valid/strong reference that won't be freed until
 | |
|   // we exit.
 | |
|   nsCOMPtr<mozIStorageStatementCallback> callback = mCallback;
 | |
|   if (shouldNotify() && callback) {
 | |
|     Unused << callback->HandleError(aError);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult AsyncExecuteStatements::notifyResults() {
 | |
|   mMutex.AssertNotCurrentThreadOwns();
 | |
|   MOZ_ASSERT(mCallback, "notifyResults called without a callback!");
 | |
| 
 | |
|   // This takes ownership of mResultSet, a new one will be generated in
 | |
|   // buildAndNotifyResults() when further results will arrive.
 | |
|   Unused << mCallingThread->Dispatch(
 | |
|       NewRunnableMethod<RefPtr<ResultSet>>(
 | |
|           "AsyncExecuteStatements::notifyResultsOnCallingThread", this,
 | |
|           &AsyncExecuteStatements::notifyResultsOnCallingThread,
 | |
|           mResultSet.forget()),
 | |
|       NS_DISPATCH_NORMAL);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult AsyncExecuteStatements::notifyResultsOnCallingThread(
 | |
|     ResultSet* aResultSet) {
 | |
|   MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
 | |
|   // Acquire our own strong reference so that if the callback spins a nested
 | |
|   // event loop and notifyCompleteOnCallingThread is executed, forgetting
 | |
|   // mCallback, we still have a valid/strong reference that won't be freed until
 | |
|   // we exit.
 | |
|   nsCOMPtr<mozIStorageStatementCallback> callback = mCallback;
 | |
|   if (shouldNotify() && callback) {
 | |
|     Unused << callback->HandleResult(aResultSet);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS_INHERITED(AsyncExecuteStatements, Runnable,
 | |
|                             mozIStoragePendingStatement)
 | |
| 
 | |
| bool AsyncExecuteStatements::statementsNeedTransaction() {
 | |
|   // If there is more than one write statement, run in a transaction.
 | |
|   // Additionally, if we have only one statement but it needs a transaction, due
 | |
|   // to multiple BindingParams, we will wrap it in one.
 | |
|   for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) {
 | |
|     transactionsCount += mStatements[i].needsTransaction();
 | |
|     if (transactionsCount > 1) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //// mozIStoragePendingStatement
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| AsyncExecuteStatements::Cancel() {
 | |
| #ifdef DEBUG
 | |
|   bool onCallingThread = false;
 | |
|   (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
 | |
|   NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!");
 | |
| #endif
 | |
| 
 | |
|   // If we have already canceled, we have an error, but always indicate that
 | |
|   // we are trying to cancel.
 | |
|   NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED);
 | |
| 
 | |
|   {
 | |
|     MutexAutoLock lockedScope(mMutex);
 | |
| 
 | |
|     // We need to indicate that we want to try and cancel now.
 | |
|     mCancelRequested = true;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //// nsIRunnable
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| AsyncExecuteStatements::Run() {
 | |
|   MOZ_ASSERT(mConnection->isConnectionReadyOnThisThread());
 | |
| 
 | |
|   // Do not run if we have been canceled.
 | |
|   {
 | |
|     MutexAutoLock lockedScope(mMutex);
 | |
|     if (mCancelRequested) mState = CANCELED;
 | |
|   }
 | |
|   if (mState == CANCELED) return notifyComplete();
 | |
| 
 | |
|   if (statementsNeedTransaction()) {
 | |
|     SQLiteMutexAutoLock lockedScope(mDBMutex);
 | |
|     if (!mConnection->transactionInProgress(lockedScope)) {
 | |
|       if (NS_SUCCEEDED(mConnection->beginTransactionInternal(
 | |
|               lockedScope, mNativeConnection,
 | |
|               mozIStorageConnection::TRANSACTION_IMMEDIATE))) {
 | |
|         mHasTransaction = true;
 | |
|       }
 | |
| #ifdef DEBUG
 | |
|       else {
 | |
|         NS_WARNING("Unable to create a transaction for async execution.");
 | |
|       }
 | |
| #endif
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Execute each statement, giving the callback results if it returns any.
 | |
|   for (uint32_t i = 0; i < mStatements.Length(); i++) {
 | |
|     bool finished = (i == (mStatements.Length() - 1));
 | |
| 
 | |
|     sqlite3_stmt* stmt;
 | |
|     {  // lock the sqlite mutex so sqlite3_errmsg cannot change
 | |
|       SQLiteMutexAutoLock lockedScope(mDBMutex);
 | |
| 
 | |
|       int rc = mStatements[i].getSqliteStatement(&stmt);
 | |
|       if (rc != SQLITE_OK) {
 | |
|         // Set our error state.
 | |
|         mState = ERROR;
 | |
| 
 | |
|         // Build the error object; can't call notifyError with the lock held
 | |
|         nsCOMPtr<mozIStorageError> errorObj(
 | |
|             new Error(rc, ::sqlite3_errmsg(mNativeConnection)));
 | |
|         {
 | |
|           // We cannot hold the DB mutex and call notifyError.
 | |
|           SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
 | |
|           (void)notifyError(errorObj);
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // If we have parameters to bind, bind them, execute, and process.
 | |
|     if (mStatements[i].hasParametersToBeBound()) {
 | |
|       if (!bindExecuteAndProcessStatement(mStatements[i], finished)) break;
 | |
|     }
 | |
|     // Otherwise, just execute and process the statement.
 | |
|     else if (!executeAndProcessStatement(mStatements[i], finished)) {
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If we still have results that we haven't notified about, take care of
 | |
|   // them now.
 | |
|   if (mResultSet) (void)notifyResults();
 | |
| 
 | |
|   // Notify about completion
 | |
|   return notifyComplete();
 | |
| }
 | |
| 
 | |
| }  // namespace storage
 | |
| }  // namespace mozilla
 | 
