Bug 489257 - Rename and update mozStorageEvents.* to mozStorageAsyncStatementExecution.*
authorShawn Wilsher <sdwilsh@shawnwilsher.com>
Tue, 21 Apr 2009 11:27:03 -0400
changeset 27569 54a5878ac36828732bdb5af4497afccd6ebef376
parent 27568 7b213ee861164549b4d099423afd3d9d436d38c7
child 27570 92791a422f0a4cb0adfd054f1fe90c866d6be949
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs489257
milestone1.9.2a1pre
Bug 489257 - Rename and update mozStorageEvents.* to mozStorageAsyncStatementExecution.* Renames the files to be more representative to what they actually contain. This also updates the files to follow the storage style guidelines. r=asuth
storage/src/Makefile.in
storage/src/mozStorageAsyncStatementExecution.cpp
storage/src/mozStorageAsyncStatementExecution.h
storage/src/mozStorageConnection.cpp
storage/src/mozStorageEvents.cpp
storage/src/mozStorageEvents.h
storage/src/mozStorageStatement.cpp
--- a/storage/src/Makefile.in
+++ b/storage/src/Makefile.in
@@ -68,17 +68,17 @@ CPPSRCS = \
   mozStorageStatementWrapper.cpp \
   mozStorageStatementParams.cpp \
   mozStorageStatementRow.cpp \
   mozStorageArgValueArray.cpp \
   mozStorageSQLFunctions.cpp \
   mozStorageRow.cpp \
   mozStorageResultSet.cpp \
   mozStorageError.cpp \
-  mozStorageEvents.cpp \
+  mozStorageAsyncStatementExecution.cpp \
   mozStorageStatementJSHelper.cpp \
   mozStoragePrivateHelpers.cpp \
   mozStorageVariant.cpp \
   $(NULL)
 
 LOCAL_INCLUDES = \
 	$(SQLITE_CFLAGS)
 
rename from storage/src/mozStorageEvents.cpp
rename to storage/src/mozStorageAsyncStatementExecution.cpp
--- a/storage/src/mozStorageEvents.cpp
+++ b/storage/src/mozStorageAsyncStatementExecution.cpp
@@ -32,156 +32,121 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-#include "nsThreadUtils.h"
+#include "nsAutoLock.h"
 #include "nsAutoPtr.h"
-#include "nsAutoLock.h"
-#include "nsCOMArray.h"
 #include "prtime.h"
 
 #include "sqlite3.h"
 
 #include "mozIStorageStatementCallback.h"
-#include "mozIStoragePendingStatement.h"
 #include "mozStorageHelper.h"
 #include "mozStorageResultSet.h"
 #include "mozStorageRow.h"
 #include "mozStorageConnection.h"
 #include "mozStorageError.h"
 #include "mozStoragePrivateHelpers.h"
-#include "mozStorageEvents.h"
+#include "mozStorageAsyncStatementExecution.h"
 
-using namespace mozilla::storage;
+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 100
 #define MAX_ROWS_PER_RESULT 15
 
 ////////////////////////////////////////////////////////////////////////////////
-//// Asynchronous Statement Execution
+//// Local Classes
 
-/**
- * Enum used to describe the state of execution.
- */
-enum ExecutionState {
-    PENDING = -1
-  , COMPLETED = mozIStorageStatementCallback::REASON_FINISHED
-  , CANCELED = mozIStorageStatementCallback::REASON_CANCELED
-  , ERROR = mozIStorageStatementCallback::REASON_ERROR
-};
+namespace {
 
-/**
- * Interface used to check if an event should run.
- */
-class iEventStatus : public nsISupports
-{
-public:
-  virtual PRBool runEvent() = 0;
-};
+typedef AsyncExecuteStatements::ExecutionState ExecutionState;
 
 /**
  * Notifies a callback with a result set.
  */
-class CallbackResultNotifier : public nsIRunnable
+class CallbackResultNotifier : public nsRunnable
 {
 public:
-  NS_DECL_ISUPPORTS
-
   CallbackResultNotifier(mozIStorageStatementCallback *aCallback,
                          mozIStorageResultSet *aResults,
-                         iEventStatus *aEventStatus) :
+                         AsyncExecuteStatements *aEventStatus) :
       mCallback(aCallback)
     , mResults(aResults)
     , mEventStatus(aEventStatus)
   {
   }
 
   NS_IMETHOD Run()
   {
     NS_ASSERTION(mCallback, "Trying to notify about results without a callback!");
 
-    if (mEventStatus->runEvent())
+    if (mEventStatus->shouldNotify())
       (void)mCallback->HandleResult(mResults);
 
     return NS_OK;
   }
 
 private:
-  CallbackResultNotifier() { }
-
   mozIStorageStatementCallback *mCallback;
   nsCOMPtr<mozIStorageResultSet> mResults;
-  nsRefPtr<iEventStatus> mEventStatus;
+  nsRefPtr<AsyncExecuteStatements> mEventStatus;
 };
-NS_IMPL_THREADSAFE_ISUPPORTS1(
-  CallbackResultNotifier,
-  nsIRunnable
-)
 
 /**
  * Notifies the calling thread that an error has occurred.
  */
-class ErrorNotifier : public nsIRunnable
+class ErrorNotifier : public nsRunnable
 {
 public:
-  NS_DECL_ISUPPORTS
-
   ErrorNotifier(mozIStorageStatementCallback *aCallback,
                 mozIStorageError *aErrorObj,
-                iEventStatus *aEventStatus) :
+                AsyncExecuteStatements *aEventStatus) :
       mCallback(aCallback)
     , mErrorObj(aErrorObj)
     , mEventStatus(aEventStatus)
   {
   }
 
   NS_IMETHOD Run()
   {
-    if (mEventStatus->runEvent() && mCallback)
+    if (mEventStatus->shouldNotify() && mCallback)
       (void)mCallback->HandleError(mErrorObj);
 
     return NS_OK;
   }
 
 private:
-  ErrorNotifier() { }
-
   mozIStorageStatementCallback *mCallback;
   nsCOMPtr<mozIStorageError> mErrorObj;
-  nsRefPtr<iEventStatus> mEventStatus;
+  nsRefPtr<AsyncExecuteStatements> mEventStatus;
 };
-NS_IMPL_THREADSAFE_ISUPPORTS1(
-  ErrorNotifier,
-  nsIRunnable
-)
 
 /**
  * Notifies the calling thread that the statement has finished executing.
  */
-class CompletionNotifier : public nsIRunnable
+class CompletionNotifier : public nsRunnable
 {
 public:
-  NS_DECL_ISUPPORTS
-
   /**
    * This takes ownership of the callback.  It is released on the thread this is
    * dispatched to (which should always be the calling thread).
    */
   CompletionNotifier(mozIStorageStatementCallback *aCallback,
                      ExecutionState aReason) :
       mCallback(aCallback)
     , mReason(aReason)
@@ -192,449 +157,31 @@ public:
   {
     (void)mCallback->HandleCompletion(mReason);
     NS_RELEASE(mCallback);
 
     return NS_OK;
   }
 
 private:
-  CompletionNotifier() { }
-
   mozIStorageStatementCallback *mCallback;
   ExecutionState mReason;
 };
-NS_IMPL_THREADSAFE_ISUPPORTS1(
-  CompletionNotifier,
-  nsIRunnable
-)
 
-/**
- * Executes a statement asynchronously in the background.
- */
-class AsyncExecuteStatements : public nsIRunnable
-                             , public mozIStoragePendingStatement
-                             , public iEventStatus
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  /**
-   * This takes ownership of both the statement and the callback.
-   */
-  AsyncExecuteStatements(nsTArray<sqlite3_stmt *> &aStatements,
-                         mozIStorageConnection *aConnection,
-                         mozIStorageStatementCallback *aCallback) :
-      mConnection(aConnection)
-    , mTransactionManager(nsnull)
-    , mCallback(aCallback)
-    , mCallingThread(do_GetCurrentThread())
-    , mMaxIntervalWait(PR_MicrosecondsToInterval(MAX_MILLISECONDS_BETWEEN_RESULTS))
-    , mIntervalStart(PR_IntervalNow())
-    , mState(PENDING)
-    , mCancelRequested(PR_FALSE)
-    , mLock(nsAutoLock::NewLock("AsyncExecuteStatements::mLock"))
-  {
-    (void)mStatements.SwapElements(aStatements);
-    NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
-  }
-
-  nsresult initialize()
-  {
-    NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
-    NS_IF_ADDREF(mCallback);
-    return NS_OK;
-  }
-
-  NS_IMETHOD Run()
-  {
-    // do not run if we have been canceled
-    {
-      nsAutoLock mutex(mLock);
-      if (mCancelRequested) {
-        mState = CANCELED;
-        mutex.unlock();
-        return NotifyComplete();
-      }
-    }
-
-    // If there is more than one statement, run it in a transaction.  We assume
-    // that we have been given write statements since getting a batch of read
-    // statements doesn't make a whole lot of sense.
-    if (mStatements.Length() > 1) {
-      // We don't error if this failed because it's not terrible if it does.
-      mTransactionManager = new mozStorageTransaction(mConnection, PR_FALSE,
-                                                      mozIStorageConnection::TRANSACTION_IMMEDIATE);
-    }
-
-    // Execute each statement, giving the callback results if it returns any.
-    for (PRUint32 i = 0; i < mStatements.Length(); i++) {
-      PRBool finished = (i == (mStatements.Length() - 1));
-      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();
-  }
-
-  NS_IMETHOD Cancel(PRBool *_successful)
-  {
-#ifdef DEBUG
-    PRBool onCallingThread = PR_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);
-
-    {
-      nsAutoLock mutex(mLock);
-
-      // We need to indicate that we want to try and cancel now.
-      mCancelRequested = PR_TRUE;
-
-      // Establish if we can cancel
-      *_successful = (mState == PENDING);
-    }
-
-    // Note, it is possible for us to return false here, and end up canceling
-    // events that have been dispatched to the calling thread.  This is OK,
-    // however, because only read statements (such as SELECT) are going to be
-    // posting events to the calling thread that actually check if they should
-    // run or not.
+} // anonymous namespace
 
-    return NS_OK;
-  }
-
-  /**
-   * This is part of iEventStatus.  It indicates if an event should be ran based
-   * on if we are trying to cancel or not.
-   */
-  PRBool runEvent()
-  {
-#ifdef DEBUG
-    PRBool onCallingThread = PR_FALSE;
-    (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
-    NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
-#endif
-
-    // We do not need to acquire mLock 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;
-  }
-
-private:
-  ~AsyncExecuteStatements()
-  {
-    nsAutoLock::DestroyLock(mLock);
-  }
-
-  /**
-   * Executes a given statement until completion, an error occurs, or we are
-   * canceled.  If aFinished is true, we know that we are the last statement,
-   * and should set mState accordingly.
-   *
-   * @pre mLock is not held
-   *
-   * @param aStatement
-   *        The statement to execute and then process.
-   * @param aFinished
-   *        Indicates if this is the last statement or not.  If it is, we have
-   *        to set the proper state.
-   * @returns true if we should continue to process statements, false otherwise.
-   */
-  PRBool ExecuteAndProcessStatement(sqlite3_stmt *aStatement, PRBool aFinished)
-  {
-    // We need to hold a lock for statement execution so we can properly
-    // reflect state in case we are canceled.  We unlock in a few areas in
-    // order to allow for cancelation to occur.
-    nsAutoLock mutex(mLock);
-
-    nsresult rv = NS_OK;
-    while (PR_TRUE) {
-      int rc = sqlite3_step(aStatement);
-      // Break out if we have no more results
-      if (rc == SQLITE_DONE)
-        break;
-
-      // Some errors are not fatal, and we can handle them and continue.
-      if (rc != SQLITE_OK && rc != SQLITE_ROW) {
-        if (rc == SQLITE_BUSY) {
-          // We do not want to hold our lock while we yield.
-          nsAutoUnlock cancelationScope(mLock);
-
-          // Yield, and try again
-          (void)PR_Sleep(PR_INTERVAL_NO_WAIT);
-          continue;
-        }
-
-        // Set error state
-        mState = ERROR;
-
-        // Drop our mutex - NotifyError doesn't want it held
-        mutex.unlock();
-
-        // Notify
-        sqlite3 *db = sqlite3_db_handle(aStatement);
-        (void)NotifyError(rc, sqlite3_errmsg(db));
-
-        // And stop processing statements
-        return PR_FALSE;
-      }
-
-      // If we do not have a callback, there's no point in executing this
-      // statement anymore, but we wish to continue to execute statements.  We
-      // also need to update our state if we are finished, so break out of the
-      // while loop.
-      if (!mCallback)
-        break;
-
-      // If we have been canceled, there is no point in going on...
-      if (mCancelRequested) {
-        mState = CANCELED;
-        return PR_FALSE;
-      }
-
-      // Build our results and notify if it's time.
-      rv = BuildAndNotifyResults(aStatement);
-      if (NS_FAILED(rv))
-        break;
-    }
-
-    // If we have an error that we have not already notified about, set our
-    // state accordingly, and notify.
-    if (NS_FAILED(rv)) {
-      mState = ERROR;
-
-      // Drop our mutex - NotifyError doesn't want it held
-      mutex.unlock();
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncExecuteStatements
 
-      // Notify, and stop processing statements.
-      (void)NotifyError(mozIStorageError::ERROR, "");
-      return PR_FALSE;
-    }
-
-#ifdef DEBUG
-    // Check to make sure that this statement was smart about what it did.
-    CheckAndLogStatementPerformance(aStatement);
-#endif
-
-    // If we are done, we need to set our state accordingly while we still
-    // hold our lock.  We would have already returned if we were canceled or had
-    // an error at this point.
-    if (aFinished)
-      mState = COMPLETED;
-
-    return PR_TRUE;
-  }
-
-  /**
-   * Builds a result set up with a row from a given statement.  If we meet the
-   * right criteria, go ahead and notify about this results too.
-   *
-   * @pre mLock is held
-   *
-   * @param aStatement
-   *        The statement to get the row data from.
-   */
-  nsresult BuildAndNotifyResults(sqlite3_stmt *aStatement)
-  {
-    NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
-    PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mLock);
-
-    // At this point, it is safe to not hold the lock and allow for cancelation.
-    // We may add an event to the calling thread, but that thread will not end
-    // up running when it checks back with us to see if it should run.
-    nsAutoUnlock cancelationScope(mLock);
-
-    // Build result object if we need it.
-    if (!mResultSet)
-      mResultSet = new mozStorageResultSet();
-    NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
-
-    nsRefPtr<mozStorageRow> row(new mozStorageRow());
-    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.
-    PRIntervalTime now = PR_IntervalNow();
-    PRIntervalTime delta = now - mIntervalStart;
-    if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxIntervalWait) {
-      // 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;
-  }
-
-  /**
-   * Notifies callback about completion, and does any necessary cleanup.
-   *
-   * @pre mLock is not held
-   */
-  nsresult 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
-    // canceling and have statements that think they have pending work, the
-    // rollback will fail.
-    for (PRUint32 i = 0; i < mStatements.Length(); i++) {
-      (void)sqlite3_finalize(mStatements[i]);
-      mStatements[i] = NULL;
-    }
-
-    // Handle our transaction, if we have one
-    if (mTransactionManager) {
-      if (mState == COMPLETED) {
-        nsresult rv = mTransactionManager->Commit();
-        if (NS_FAILED(rv)) {
-          mState = ERROR;
-          (void)NotifyError(mozIStorageError::ERROR,
-                            "Transaction failed to commit");
-        }
-      }
-      else {
-        (void)mTransactionManager->Rollback();
-      }
-      delete mTransactionManager;
-      mTransactionManager = nsnull;
-    }
-
-    // Notify about completion iff we have a callback.
-    if (mCallback) {
-      nsRefPtr<CompletionNotifier> completionEvent =
-        new CompletionNotifier(mCallback, mState);
-      NS_ENSURE_TRUE(completionEvent, NS_ERROR_OUT_OF_MEMORY);
-
-      // We no longer own mCallback (the CompletionNotifier takes ownership).
-      mCallback = nsnull;
-
-      (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
-    }
-
-    return NS_OK;
-  }
-
-  /**
-   * Notifies callback about an error.
-   *
-   * @pre mLock is not held
-   *
-   * @param aErrorCode
-   *        The error code defined in mozIStorageError for the error.
-   * @param aMessage
-   *        The error string, if any.
-   */
-  nsresult NotifyError(PRInt32 aErrorCode, const char *aMessage)
-  {
-    if (!mCallback)
-      return NS_OK;
-
-    nsCOMPtr<mozIStorageError> errorObj =
-      new mozStorageError(aErrorCode, aMessage);
-    NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
-
-    nsRefPtr<ErrorNotifier> notifier =
-      new ErrorNotifier(mCallback, errorObj, this);
-    NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
-
-    return mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
-  }
-
-  /**
-   * Notifies the callback about a result set.
-   *
-   * @pre mLock is not held
-   */
-  nsresult NotifyResults()
-  {
-    NS_ASSERTION(mCallback, "NotifyResults called without a callback!");
-
-    nsRefPtr<CallbackResultNotifier> notifier =
-      new CallbackResultNotifier(mCallback, mResultSet, this);
-    NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
-
-    nsresult rv = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
-    if (NS_SUCCEEDED(rv))
-      mResultSet = nsnull; // we no longer own it on success
-    return rv;
-  };
-
-  nsTArray<sqlite3_stmt *> mStatements;
-  mozIStorageConnection *mConnection;
-  mozStorageTransaction *mTransactionManager;
-  mozIStorageStatementCallback *mCallback;
-  nsCOMPtr<nsIThread> mCallingThread;
-  nsRefPtr<mozStorageResultSet> mResultSet;
-
-  /**
-   * The maximum amount of time we want to wait between results.  Defined by
-   * MAX_MILLISECONDS_BETWEEN_RESULTS and set at construction.
-   */
-  const PRIntervalTime mMaxIntervalWait;
-
-  /**
-   * The start time since our last set of results.
-   */
-  PRIntervalTime mIntervalStart;
-
-  /**
-   * Indicates the state the object is currently in.
-   */
-  ExecutionState mState;
-
-  /**
-   * Indicates if we should try to cancel at a cancelation point or not.
-   */
-  PRBool mCancelRequested;
-
-  /**
-   * This is the lock that protects our state from changing.  This includes the
-   * following variables:
-   *   -mState
-   *   -mCancelRequested is only set on the calling thread while the lock is
-   *    held.  It is always read from within the lock on the background thread,
-   *    but not on the calling thread (see runEvent for why).
-   */
-  PRLock *mLock;
-};
-NS_IMPL_THREADSAFE_ISUPPORTS2(
-  AsyncExecuteStatements,
-  nsIRunnable,
-  mozIStoragePendingStatement
-)
-
+/* static */
 nsresult
-NS_executeAsync(nsTArray<sqlite3_stmt *> &aStatements,
-                Connection *aConnection,
-                mozIStorageStatementCallback *aCallback,
-                mozIStoragePendingStatement **_stmt)
+AsyncExecuteStatements::execute(sqlite3_stmt_array &aStatements,
+                                Connection *aConnection,
+                                mozIStorageStatementCallback *aCallback,
+                                mozIStoragePendingStatement **_stmt)
 {
   // Create our event to run in the background
   nsRefPtr<AsyncExecuteStatements> event =
     new AsyncExecuteStatements(aStatements, aConnection, aCallback);
   NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
 
   nsresult rv = event->initialize();
   NS_ENSURE_SUCCESS(rv, rv);
@@ -644,8 +191,347 @@ NS_executeAsync(nsTArray<sqlite3_stmt *>
   NS_ENSURE_TRUE(target, NS_ERROR_NOT_AVAILABLE);
   rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Return it as the pending statement object
   NS_ADDREF(*_stmt = event);
   return NS_OK;
 }
+
+AsyncExecuteStatements::AsyncExecuteStatements(sqlite3_stmt_array &aStatements,
+                                               mozIStorageConnection *aConnection,
+                                               mozIStorageStatementCallback *aCallback)
+: mConnection(aConnection)
+, mTransactionManager(nsnull)
+, mCallback(aCallback)
+, mCallingThread(::do_GetCurrentThread())
+, mMaxIntervalWait(::PR_MicrosecondsToInterval(MAX_MILLISECONDS_BETWEEN_RESULTS))
+, mIntervalStart(::PR_IntervalNow())
+, mState(PENDING)
+, mCancelRequested(PR_FALSE)
+, mLock(nsAutoLock::NewLock("AsyncExecuteStatements::mLock"))
+{
+  (void)mStatements.SwapElements(aStatements);
+  NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
+}
+
+AsyncExecuteStatements::~AsyncExecuteStatements()
+{
+  nsAutoLock::DestroyLock(mLock);
+}
+
+nsresult
+AsyncExecuteStatements::initialize()
+{
+  NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
+  NS_IF_ADDREF(mCallback);
+  return NS_OK;
+}
+
+bool
+AsyncExecuteStatements::shouldNotify()
+{
+#ifdef DEBUG
+  PRBool onCallingThread = PR_FALSE;
+  (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
+  NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
+#endif
+
+  // We do not need to acquire mLock 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::executeAndProcessStatement(sqlite3_stmt *aStatement,
+                                                   bool aLastStatement)
+{
+  // We need to hold the mutex for statement execution so we can properly
+  // reflect state in case we are canceled.  We release the mutex in a few areas
+  // in order to allow for cancelation to occur.
+  nsAutoLock mutex(mLock);
+
+  nsresult rv = NS_OK;
+  while (true) {
+    int rc = ::sqlite3_step(aStatement);
+    // Break out if we have no more results
+    if (rc == SQLITE_DONE)
+      break;
+
+    // Some errors are not fatal, and we can handle them and continue.
+    if (rc != SQLITE_OK && rc != SQLITE_ROW) {
+      if (rc == SQLITE_BUSY) {
+        // We do not want to hold our mutex while we yield.
+        nsAutoUnlock cancelationScope(mLock);
+
+        // Yield, and try again
+        (void)::PR_Sleep(PR_INTERVAL_NO_WAIT);
+        continue;
+      }
+
+      // Set error state
+      mState = ERROR;
+
+      // Drop our mutex - notifyError doesn't want it held
+      mutex.unlock();
+
+      // Notify
+      sqlite3 *db = ::sqlite3_db_handle(aStatement);
+      (void)notifyError(rc, ::sqlite3_errmsg(db));
+
+      // And stop processing statements
+      return false;
+    }
+
+    // If we do not have a callback, there's no point in executing this
+    // statement anymore, but we wish to continue to execute statements.  We
+    // also need to update our state if we are finished, so break out of the
+    // while loop.
+    if (!mCallback)
+      break;
+
+    // If we have been canceled, there is no point in going on...
+    if (mCancelRequested) {
+      mState = CANCELED;
+      return false;
+    }
+
+    // Build our results and notify if it's time.
+    rv = buildAndNotifyResults(aStatement);
+    if (NS_FAILED(rv))
+      break;
+  }
+
+  // If we have an error that we have not already notified about, set our
+  // state accordingly, and notify.
+  if (NS_FAILED(rv)) {
+    mState = ERROR;
+
+    // Drop our mutex - notifyError doesn't want it held
+    mutex.unlock();
+
+    // Notify, and stop processing statements.
+    (void)notifyError(mozIStorageError::ERROR, "");
+    return false;
+  }
+
+#ifdef DEBUG
+  // Check to make sure that this statement was smart about what it did.
+  CheckAndLogStatementPerformance(aStatement);
+#endif
+
+  // 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;
+}
+
+nsresult
+AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement)
+{
+  NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
+  PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mLock);
+
+  // At this point, it is safe to not hold the mutex and allow for cancelation.
+  // We may add an event to the calling thread, but that thread will not end
+  // up running when it checks back with us to see if it should run.
+  nsAutoUnlock cancelationScope(mLock);
+
+  // Build result object if we need it.
+  if (!mResultSet)
+    mResultSet = new mozStorageResultSet();
+  NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
+
+  nsRefPtr<mozStorageRow> row(new mozStorageRow());
+  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.
+  PRIntervalTime now = ::PR_IntervalNow();
+  PRIntervalTime delta = now - mIntervalStart;
+  if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxIntervalWait) {
+    // 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()
+{
+  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
+  // canceling and have statements that think they have pending work, the
+  // rollback will fail.
+  for (PRUint32 i = 0; i < mStatements.Length(); i++) {
+    (void)::sqlite3_finalize(mStatements[i]);
+    mStatements[i] = NULL;
+  }
+
+  // Handle our transaction, if we have one
+  if (mTransactionManager) {
+    if (mState == COMPLETED) {
+      nsresult rv = mTransactionManager->Commit();
+      if (NS_FAILED(rv)) {
+        mState = ERROR;
+        (void)notifyError(mozIStorageError::ERROR,
+                          "Transaction failed to commit");
+      }
+    }
+    else {
+      (void)mTransactionManager->Rollback();
+    }
+    delete mTransactionManager;
+    mTransactionManager = nsnull;
+  }
+
+  // Notify about completion iff we have a callback.
+  if (mCallback) {
+    nsRefPtr<CompletionNotifier> completionEvent =
+      new CompletionNotifier(mCallback, mState);
+    NS_ENSURE_TRUE(completionEvent, NS_ERROR_OUT_OF_MEMORY);
+
+    // We no longer own mCallback (the CompletionNotifier takes ownership).
+    mCallback = nsnull;
+
+    (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+AsyncExecuteStatements::notifyError(PRInt32 aErrorCode,
+                                    const char *aMessage)
+{
+  if (!mCallback)
+    return NS_OK;
+
+  nsCOMPtr<mozIStorageError> errorObj =
+    new mozStorageError(aErrorCode, aMessage);
+  NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
+
+  nsRefPtr<ErrorNotifier> notifier =
+    new ErrorNotifier(mCallback, errorObj, this);
+  NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
+
+  return mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
+}
+
+nsresult
+AsyncExecuteStatements::notifyResults()
+{
+  NS_ASSERTION(mCallback, "notifyResults called without a callback!");
+
+  nsRefPtr<CallbackResultNotifier> notifier =
+    new CallbackResultNotifier(mCallback, mResultSet, this);
+  NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
+
+  nsresult rv = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
+  if (NS_SUCCEEDED(rv))
+    mResultSet = nsnull; // we no longer own it on success
+  return rv;
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(
+  AsyncExecuteStatements,
+  nsIRunnable,
+  mozIStoragePendingStatement
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStoragePendingStatement
+
+NS_IMETHODIMP
+AsyncExecuteStatements::Cancel(PRBool *_successful)
+{
+#ifdef DEBUG
+  PRBool onCallingThread = PR_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);
+
+  {
+    nsAutoLock mutex(mLock);
+
+    // We need to indicate that we want to try and cancel now.
+    mCancelRequested = true;
+
+    // Establish if we can cancel
+    *_successful = (mState == PENDING);
+  }
+
+  // Note, it is possible for us to return false here, and end up canceling
+  // events that have been dispatched to the calling thread.  This is OK,
+  // however, because only read statements (such as SELECT) are going to be
+  // posting events to the calling thread that actually check if they should
+  // run or not.
+
+  return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIRunnable
+
+NS_IMETHODIMP
+AsyncExecuteStatements::Run()
+{
+  // do not run if we have been canceled
+  {
+    nsAutoLock mutex(mLock);
+    if (mCancelRequested) {
+      mState = CANCELED;
+      mutex.unlock();
+      return notifyComplete();
+    }
+  }
+
+  // If there is more than one statement, run it in a transaction.  We assume
+  // that we have been given write statements since getting a batch of read
+  // statements doesn't make a whole lot of sense.
+  if (mStatements.Length() > 1) {
+    // We don't error if this failed because it's not terrible if it does.
+    mTransactionManager = new mozStorageTransaction(mConnection, PR_FALSE,
+                                                    mozIStorageConnection::TRANSACTION_IMMEDIATE);
+  }
+
+  // Execute each statement, giving the callback results if it returns any.
+  for (PRUint32 i = 0; i < mStatements.Length(); i++) {
+    PRBool finished = (i == (mStatements.Length() - 1));
+    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
rename from storage/src/mozStorageEvents.h
rename to storage/src/mozStorageAsyncStatementExecution.h
--- a/storage/src/mozStorageEvents.h
+++ b/storage/src/mozStorageAsyncStatementExecution.h
@@ -11,17 +11,17 @@
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
  * The Original Code is mozilla.org code.
  *
  * The Initial Developer of the Original Code is
- * Mozilla Corporation. 
+ * Mozilla Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -32,44 +32,184 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-#ifndef _mozStorageEvents_h_
-#define _mozStorageEvents_h_
+#ifndef _mozStorageAsyncStatementExecution_h_
+#define _mozStorageAsyncStatementExecution_h_
 
 #include "nscore.h"
 #include "nsTArray.h"
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+
+#include "mozIStoragePendingStatement.h"
+#include "mozIStorageStatementCallback.h"
+
 struct sqlite3_stmt;
-class mozIStorageStatementCallback;
-class mozIStoragePendingStatement;
+class mozStorageTransaction;
+class mozStorageResultSet;
 
 namespace mozilla {
 namespace storage {
+
 class Connection;
+
+class AsyncExecuteStatements : public nsIRunnable
+                             , public mozIStoragePendingStatement
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIRUNNABLE
+  NS_DECL_MOZISTORAGEPENDINGSTATEMENT
+
+  /**
+   * Describes the state of execution.
+   */
+  enum ExecutionState {
+    PENDING = -1,
+    COMPLETED = mozIStorageStatementCallback::REASON_FINISHED,
+    CANCELED = mozIStorageStatementCallback::REASON_CANCELED,
+    ERROR = mozIStorageStatementCallback::REASON_ERROR
+  };
+
+  typedef nsTArray<sqlite3_stmt *> sqlite3_stmt_array;
+
+  /**
+   * Executes a statement in the background, and passes results back to the
+   * caller.
+   *
+   * @param aStatements
+   *        The SQLite statements to execute in the background.  Ownership is
+   *        transfered from the caller.
+   * @param aConnection
+   *        The connection that created the statements to execute.
+   * @param aCallback
+   *        The callback that is notified of results, completion, and errors.
+   * @param _stmt
+   *        The handle to control the execution of the statements.
+   */
+  static nsresult execute(sqlite3_stmt_array &aStatements,
+                          Connection *aConnection,
+                          mozIStorageStatementCallback *aCallback,
+                          mozIStoragePendingStatement **_stmt);
+
+  /**
+   * Indicates when events on the calling thread should run or not.  Certain
+   * events posted back to the calling thread should call this see if they
+   * should run or not.
+   *
+   * @returns true if the event should notify still, false otherwise.
+   */
+  bool shouldNotify();
+
+private:
+  AsyncExecuteStatements(sqlite3_stmt_array &aStatements,
+                         mozIStorageConnection *aConnection,
+                         mozIStorageStatementCallback *aCallback);
+
+  /**
+   * Initializes the object so it can be run on the background thread.
+   */
+  nsresult initialize();
+
+  ~AsyncExecuteStatements();
+
+  /**
+   * Executes a given statement until completion, an error occurs, or we are
+   * canceled.  If aLastStatement is true, we should set mState accordingly.
+   *
+   * @pre mLock is not held
+   *
+   * @param aStatement
+   *        The statement to execute and then process.
+   * @param aLastStatement
+   *        Indicates if this is the last statement or not.  If it is, we have
+   *        to set the proper state.
+   * @returns true if we should continue to process statements, false otherwise.
+   */
+  bool executeAndProcessStatement(sqlite3_stmt *aStatement,
+                                  bool aLastStatement);
+
+  /**
+   * Builds a result set up with a row from a given statement.  If we meet the
+   * right criteria, go ahead and notify about this results too.
+   *
+   * @pre mLock is held
+   *
+   * @param aStatement
+   *        The statement to get the row data from.
+   */
+  nsresult buildAndNotifyResults(sqlite3_stmt *aStatement);
+
+  /**
+   * Notifies callback about completion, and does any necessary cleanup.
+   *
+   * @pre mLock is not held
+   */
+  nsresult notifyComplete();
+
+  /**
+   * Notifies callback about an error.
+   *
+   * @pre mLock is not held
+   *
+   * @param aErrorCode
+   *        The error code defined in mozIStorageError for the error.
+   * @param aMessage
+   *        The error string, if any.
+   */
+  nsresult notifyError(PRInt32 aErrorCode, const char *aMessage);
+
+  /**
+   * Notifies the callback about a result set.
+   *
+   * @pre mLock is not held
+   */
+  nsresult notifyResults();
+
+  sqlite3_stmt_array mStatements;
+  mozIStorageConnection *mConnection;
+  mozStorageTransaction *mTransactionManager;
+  mozIStorageStatementCallback *mCallback;
+  nsCOMPtr<nsIThread> mCallingThread;
+  nsRefPtr<mozStorageResultSet> mResultSet;
+
+  /**
+   * The maximum amount of time we want to wait between results.  Defined by
+   * MAX_MILLISECONDS_BETWEEN_RESULTS and set at construction.
+   */
+  const PRIntervalTime mMaxIntervalWait;
+
+  /**
+   * The start time since our last set of results.
+   */
+  PRIntervalTime mIntervalStart;
+
+  /**
+   * Indicates our state of execution.
+   */
+  ExecutionState mState;
+
+  /**
+   * Indicates if we should try to cancel at a cancelation point.
+   */
+  bool mCancelRequested;
+
+  /**
+   * This is the mutex tat protects our state from changing between threads.
+   * This includes the following variables:
+   *   - mState
+   *   - mCancelRequested is only set on the calling thread while the lock is
+   *     held.  It is always read from within the lock on the background thread,
+   *     but not on the calling thread (see shouldNotify for why).
+   */
+  PRLock *mLock;
+};
+
 } // namespace storage
 } // namespace mozilla
 
-/**
- * Executes a statement in the background, and passes results back to the
- * caller.
- *
- * @param aStatements
- *        The SQLite statements to execute in the background.
- * @param aConnection
- *        The connection that created the statements to execute.
- * @param aCallback
- *        The callback that is notified of results, completion, and errors.
- * @param _stmt
- *        The handle to control the execution of the statements.
- */
-nsresult NS_executeAsync(
-  nsTArray<sqlite3_stmt *> &aStatements,
-  mozilla::storage::Connection *aConnection,
-  mozIStorageStatementCallback *aCallback,
-  mozIStoragePendingStatement **_stmt
-);
-
-#endif // _mozStorageEvents_h_
+#endif // _mozStorageAsyncStatementExecution_h_
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -50,17 +50,17 @@
 #include "nsIVariant.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsThreadUtils.h"
 
 #include "mozIStorageAggregateFunction.h"
 #include "mozIStorageFunction.h"
 
-#include "mozStorageEvents.h"
+#include "mozStorageAsyncStatementExecution.h"
 #include "mozStorageSQLFunctions.h"
 #include "mozStorageConnection.h"
 #include "mozStorageService.h"
 #include "mozStorageStatement.h"
 #include "mozStorageArgValueArray.h"
 #include "mozStoragePrivateHelpers.h"
 
 #include "prlog.h"
@@ -692,17 +692,17 @@ Connection::ExecuteAsync(mozIStorageStat
       rc = SQLITE_NOMEM;
       break;
     }
   }
 
   // Dispatch to the background
   nsresult rv = NS_OK;
   if (rc == SQLITE_OK)
-    rv = NS_executeAsync(stmts, this, aCallback, _handle);
+    rv = AsyncExecuteStatements::execute(stmts, this, aCallback, _handle);
 
   // We had a failure, so we need to clean up...
   if (rc != SQLITE_OK || NS_FAILED(rv)) {
     for (PRUint32 i = 0; i < stmts.Length(); i++)
       (void)::sqlite3_finalize(stmts[i]);
 
     if (rc != SQLITE_OK)
       rv = ConvertResultCode(rc);
--- a/storage/src/mozStorageStatement.cpp
+++ b/storage/src/mozStorageStatement.cpp
@@ -46,17 +46,16 @@
 #include "nsISimpleEnumerator.h"
 #include "nsMemory.h"
 #include "nsIClassInfoImpl.h"
 #include "nsIProgrammingLanguage.h"
 
 #include "mozStorageConnection.h"
 #include "mozStorageStatementJSHelper.h"
 #include "mozStoragePrivateHelpers.h"
-#include "mozStorageEvents.h"
 #include "mozStorageStatementParams.h"
 #include "mozStorageStatementRow.h"
 #include "mozStorageStatement.h"
 
 #include "prlog.h"
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gStorageLog;