storage/src/mozStorageAsyncStatementExecution.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     2  * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "nsAutoPtr.h"
     9 #include "sqlite3.h"
    11 #include "mozIStorageStatementCallback.h"
    12 #include "mozStorageBindingParams.h"
    13 #include "mozStorageHelper.h"
    14 #include "mozStorageResultSet.h"
    15 #include "mozStorageRow.h"
    16 #include "mozStorageConnection.h"
    17 #include "mozStorageError.h"
    18 #include "mozStoragePrivateHelpers.h"
    19 #include "mozStorageStatementData.h"
    20 #include "mozStorageAsyncStatementExecution.h"
    22 #include "mozilla/Telemetry.h"
    24 namespace mozilla {
    25 namespace storage {
    27 /**
    28  * The following constants help batch rows into result sets.
    29  * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that
    30  * takes less than 200 milliseconds is considered to feel instantaneous to end
    31  * users.  MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of
    32  * dispatches to calling thread, while also providing reasonably-sized sets of
    33  * data for consumers.  Both of these constants are used because we assume that
    34  * consumers are trying to avoid blocking their execution thread for long
    35  * periods of time, and dispatching many small events to the calling thread will
    36  * end up blocking it.
    37  */
    38 #define MAX_MILLISECONDS_BETWEEN_RESULTS 75
    39 #define MAX_ROWS_PER_RESULT 15
    41 ////////////////////////////////////////////////////////////////////////////////
    42 //// Local Classes
    44 namespace {
    46 typedef AsyncExecuteStatements::ExecutionState ExecutionState;
    47 typedef AsyncExecuteStatements::StatementDataArray StatementDataArray;
    49 /**
    50  * Notifies a callback with a result set.
    51  */
    52 class CallbackResultNotifier : public nsRunnable
    53 {
    54 public:
    55   CallbackResultNotifier(mozIStorageStatementCallback *aCallback,
    56                          mozIStorageResultSet *aResults,
    57                          AsyncExecuteStatements *aEventStatus) :
    58       mCallback(aCallback)
    59     , mResults(aResults)
    60     , mEventStatus(aEventStatus)
    61   {
    62   }
    64   NS_IMETHOD Run()
    65   {
    66     NS_ASSERTION(mCallback, "Trying to notify about results without a callback!");
    68     if (mEventStatus->shouldNotify()) {
    69       // Hold a strong reference to the callback while notifying it, so that if
    70       // it spins the event loop, the callback won't be released and freed out
    71       // from under us.
    72       nsCOMPtr<mozIStorageStatementCallback> callback =
    73         do_QueryInterface(mCallback);
    75       (void)mCallback->HandleResult(mResults);
    76     }
    78     return NS_OK;
    79   }
    81 private:
    82   mozIStorageStatementCallback *mCallback;
    83   nsCOMPtr<mozIStorageResultSet> mResults;
    84   nsRefPtr<AsyncExecuteStatements> mEventStatus;
    85 };
    87 /**
    88  * Notifies the calling thread that an error has occurred.
    89  */
    90 class ErrorNotifier : public nsRunnable
    91 {
    92 public:
    93   ErrorNotifier(mozIStorageStatementCallback *aCallback,
    94                 mozIStorageError *aErrorObj,
    95                 AsyncExecuteStatements *aEventStatus) :
    96       mCallback(aCallback)
    97     , mErrorObj(aErrorObj)
    98     , mEventStatus(aEventStatus)
    99   {
   100   }
   102   NS_IMETHOD Run()
   103   {
   104     if (mEventStatus->shouldNotify() && mCallback) {
   105       // Hold a strong reference to the callback while notifying it, so that if
   106       // it spins the event loop, the callback won't be released and freed out
   107       // from under us.
   108       nsCOMPtr<mozIStorageStatementCallback> callback =
   109         do_QueryInterface(mCallback);
   111       (void)mCallback->HandleError(mErrorObj);
   112     }
   114     return NS_OK;
   115   }
   117 private:
   118   mozIStorageStatementCallback *mCallback;
   119   nsCOMPtr<mozIStorageError> mErrorObj;
   120   nsRefPtr<AsyncExecuteStatements> mEventStatus;
   121 };
   123 /**
   124  * Notifies the calling thread that the statement has finished executing.  Takes
   125  * ownership of the StatementData so it is released on the proper thread.
   126  */
   127 class CompletionNotifier : public nsRunnable
   128 {
   129 public:
   130   /**
   131    * This takes ownership of the callback and the StatementData.  They are
   132    * released on the thread this is dispatched to (which should always be the
   133    * calling thread).
   134    */
   135   CompletionNotifier(mozIStorageStatementCallback *aCallback,
   136                      ExecutionState aReason)
   137     : mCallback(aCallback)
   138     , mReason(aReason)
   139   {
   140   }
   142   NS_IMETHOD Run()
   143   {
   144     if (mCallback) {
   145       (void)mCallback->HandleCompletion(mReason);
   146       NS_RELEASE(mCallback);
   147     }
   149     return NS_OK;
   150   }
   152 private:
   153   mozIStorageStatementCallback *mCallback;
   154   ExecutionState mReason;
   155 };
   157 } // anonymous namespace
   159 ////////////////////////////////////////////////////////////////////////////////
   160 //// AsyncExecuteStatements
   162 /* static */
   163 nsresult
   164 AsyncExecuteStatements::execute(StatementDataArray &aStatements,
   165                                 Connection *aConnection,
   166                                 sqlite3 *aNativeConnection,
   167                                 mozIStorageStatementCallback *aCallback,
   168                                 mozIStoragePendingStatement **_stmt)
   169 {
   170   // Create our event to run in the background
   171   nsRefPtr<AsyncExecuteStatements> event =
   172     new AsyncExecuteStatements(aStatements, aConnection, aNativeConnection,
   173                                aCallback);
   174   NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
   176   // Dispatch it to the background
   177   nsIEventTarget *target = aConnection->getAsyncExecutionTarget();
   179   // If we don't have a valid target, this is a bug somewhere else. In the past,
   180   // this assert found cases where a Run method would schedule a new statement
   181   // without checking if asyncClose had been called. The caller must prevent
   182   // that from happening or, if the work is not critical, just avoid creating
   183   // the new statement during shutdown. See bug 718449 for an example.
   184   MOZ_ASSERT(target);
   185   if (!target) {
   186     return NS_ERROR_NOT_AVAILABLE;
   187   }
   189   nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
   190   NS_ENSURE_SUCCESS(rv, rv);
   192   // Return it as the pending statement object and track it.
   193   NS_ADDREF(*_stmt = event);
   194   return NS_OK;
   195 }
   197 AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements,
   198                                                Connection *aConnection,
   199                                                sqlite3 *aNativeConnection,
   200                                                mozIStorageStatementCallback *aCallback)
   201 : mConnection(aConnection)
   202 , mNativeConnection(aNativeConnection)
   203 , mHasTransaction(false)
   204 , mCallback(aCallback)
   205 , mCallingThread(::do_GetCurrentThread())
   206 , mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS))
   207 , mIntervalStart(TimeStamp::Now())
   208 , mState(PENDING)
   209 , mCancelRequested(false)
   210 , mMutex(aConnection->sharedAsyncExecutionMutex)
   211 , mDBMutex(aConnection->sharedDBMutex)
   212   , mRequestStartDate(TimeStamp::Now())
   213 {
   214   (void)mStatements.SwapElements(aStatements);
   215   NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
   216   NS_IF_ADDREF(mCallback);
   217 }
   219 AsyncExecuteStatements::~AsyncExecuteStatements()
   220 {
   221   MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point");
   222 }
   224 bool
   225 AsyncExecuteStatements::shouldNotify()
   226 {
   227 #ifdef DEBUG
   228   mMutex.AssertNotCurrentThreadOwns();
   230   bool onCallingThread = false;
   231   (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
   232   NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
   233 #endif
   235   // We do not need to acquire mMutex here because it can only ever be written
   236   // to on the calling thread, and the only thread that can call us is the
   237   // calling thread, so we know that our access is serialized.
   238   return !mCancelRequested;
   239 }
   241 bool
   242 AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData,
   243                                                        bool aLastStatement)
   244 {
   245   mMutex.AssertNotCurrentThreadOwns();
   247   sqlite3_stmt *aStatement = nullptr;
   248   // This cannot fail; we are only called if it's available.
   249   (void)aData.getSqliteStatement(&aStatement);
   250   NS_ASSERTION(aStatement, "You broke the code; do not call here like that!");
   251   BindingParamsArray *paramsArray(aData);
   253   // Iterate through all of our parameters, bind them, and execute.
   254   bool continueProcessing = true;
   255   BindingParamsArray::iterator itr = paramsArray->begin();
   256   BindingParamsArray::iterator end = paramsArray->end();
   257   while (itr != end && continueProcessing) {
   258     // Bind the data to our statement.
   259     nsCOMPtr<IStorageBindingParamsInternal> bindingInternal = 
   260       do_QueryInterface(*itr);
   261     nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement);
   262     if (error) {
   263       // Set our error state.
   264       mState = ERROR;
   266       // And notify.
   267       (void)notifyError(error);
   268       return false;
   269     }
   271     // Advance our iterator, execute, and then process the statement.
   272     itr++;
   273     bool lastStatement = aLastStatement && itr == end;
   274     continueProcessing = executeAndProcessStatement(aStatement, lastStatement);
   276     // Always reset our statement.
   277     (void)::sqlite3_reset(aStatement);
   278   }
   280   return continueProcessing;
   281 }
   283 bool
   284 AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement,
   285                                                    bool aLastStatement)
   286 {
   287   mMutex.AssertNotCurrentThreadOwns();
   289   // Execute our statement
   290   bool hasResults;
   291   do {
   292     hasResults = executeStatement(aStatement);
   294     // If we had an error, bail.
   295     if (mState == ERROR)
   296       return false;
   298     // If we have been canceled, there is no point in going on...
   299     {
   300       MutexAutoLock lockedScope(mMutex);
   301       if (mCancelRequested) {
   302         mState = CANCELED;
   303         return false;
   304       }
   305     }
   307     // Build our result set and notify if we got anything back and have a
   308     // callback to notify.
   309     if (mCallback && hasResults &&
   310         NS_FAILED(buildAndNotifyResults(aStatement))) {
   311       // We had an error notifying, so we notify on error and stop processing.
   312       mState = ERROR;
   314       // Notify, and stop processing statements.
   315       (void)notifyError(mozIStorageError::ERROR,
   316                         "An error occurred while notifying about results");
   318       return false;
   319     }
   320   } while (hasResults);
   322 #ifdef DEBUG
   323   // Check to make sure that this statement was smart about what it did.
   324   checkAndLogStatementPerformance(aStatement);
   325 #endif
   327   // If we are done, we need to set our state accordingly while we still hold
   328   // our mutex.  We would have already returned if we were canceled or had
   329   // an error at this point.
   330   if (aLastStatement)
   331     mState = COMPLETED;
   333   return true;
   334 }
   336 bool
   337 AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement)
   338 {
   339   mMutex.AssertNotCurrentThreadOwns();
   340   Telemetry::AutoTimer<Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_MS> finallySendExecutionDuration(mRequestStartDate);
   341   while (true) {
   342     // lock the sqlite mutex so sqlite3_errmsg cannot change
   343     SQLiteMutexAutoLock lockedScope(mDBMutex);
   345     int rc = mConnection->stepStatement(mNativeConnection, aStatement);
   346     // Stop if we have no more results.
   347     if (rc == SQLITE_DONE)
   348     {
   349       Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
   350       return false;
   351     }
   353     // If we got results, we can return now.
   354     if (rc == SQLITE_ROW)
   355     {
   356       Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
   357       return true;
   358     }
   360     // Some errors are not fatal, and we can handle them and continue.
   361     if (rc == SQLITE_BUSY) {
   362       // Don't hold the lock while we call outside our module.
   363       SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
   365       // Yield, and try again
   366       (void)::PR_Sleep(PR_INTERVAL_NO_WAIT);
   367       continue;
   368     }
   370     // Set an error state.
   371     mState = ERROR;
   372     Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, false);
   374     // Construct the error message before giving up the mutex (which we cannot
   375     // hold during the call to notifyError).
   376     nsCOMPtr<mozIStorageError> errorObj(
   377       new Error(rc, ::sqlite3_errmsg(mNativeConnection))
   378     );
   379     // We cannot hold the DB mutex while calling notifyError.
   380     SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
   381     (void)notifyError(errorObj);
   383     // Finally, indicate that we should stop processing.
   384     return false;
   385   }
   386 }
   388 nsresult
   389 AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement)
   390 {
   391   NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
   392   mMutex.AssertNotCurrentThreadOwns();
   394   // Build result object if we need it.
   395   if (!mResultSet)
   396     mResultSet = new ResultSet();
   397   NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
   399   nsRefPtr<Row> row(new Row());
   400   NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
   402   nsresult rv = row->initialize(aStatement);
   403   NS_ENSURE_SUCCESS(rv, rv);
   405   rv = mResultSet->add(row);
   406   NS_ENSURE_SUCCESS(rv, rv);
   408   // If we have hit our maximum number of allowed results, or if we have hit
   409   // the maximum amount of time we want to wait for results, notify the
   410   // calling thread about it.
   411   TimeStamp now = TimeStamp::Now();
   412   TimeDuration delta = now - mIntervalStart;
   413   if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) {
   414     // Notify the caller
   415     rv = notifyResults();
   416     if (NS_FAILED(rv))
   417       return NS_OK; // we'll try again with the next result
   419     // Reset our start time
   420     mIntervalStart = now;
   421   }
   423   return NS_OK;
   424 }
   426 nsresult
   427 AsyncExecuteStatements::notifyComplete()
   428 {
   429   mMutex.AssertNotCurrentThreadOwns();
   430   NS_ASSERTION(mState != PENDING,
   431                "Still in a pending state when calling Complete!");
   433   // Reset our statements before we try to commit or rollback.  If we are
   434   // canceling and have statements that think they have pending work, the
   435   // rollback will fail.
   436   for (uint32_t i = 0; i < mStatements.Length(); i++)
   437     mStatements[i].reset();
   439   // Release references to the statement data as soon as possible. If this
   440   // is the last reference, statements will be finalized immediately on the
   441   // async thread, hence avoiding several bounces between threads and possible
   442   // race conditions with AsyncClose().
   443   mStatements.Clear();
   445   // Handle our transaction, if we have one
   446   if (mHasTransaction) {
   447     if (mState == COMPLETED) {
   448       nsresult rv = mConnection->commitTransactionInternal(mNativeConnection);
   449       if (NS_FAILED(rv)) {
   450         mState = ERROR;
   451         (void)notifyError(mozIStorageError::ERROR,
   452                           "Transaction failed to commit");
   453       }
   454     }
   455     else {
   456       NS_WARN_IF(NS_FAILED(mConnection->rollbackTransactionInternal(mNativeConnection)));
   457     }
   458     mHasTransaction = false;
   459   }
   461   // Always generate a completion notification; it is what guarantees that our
   462   // destruction does not happen here on the async thread.
   463   nsRefPtr<CompletionNotifier> completionEvent =
   464     new CompletionNotifier(mCallback, mState);
   466   // We no longer own mCallback (the CompletionNotifier takes ownership).
   467   mCallback = nullptr;
   469   (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
   471   return NS_OK;
   472 }
   474 nsresult
   475 AsyncExecuteStatements::notifyError(int32_t aErrorCode,
   476                                     const char *aMessage)
   477 {
   478   mMutex.AssertNotCurrentThreadOwns();
   479   mDBMutex.assertNotCurrentThreadOwns();
   481   if (!mCallback)
   482     return NS_OK;
   484   nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
   485   NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
   487   return notifyError(errorObj);
   488 }
   490 nsresult
   491 AsyncExecuteStatements::notifyError(mozIStorageError *aError)
   492 {
   493   mMutex.AssertNotCurrentThreadOwns();
   494   mDBMutex.assertNotCurrentThreadOwns();
   496   if (!mCallback)
   497     return NS_OK;
   499   nsRefPtr<ErrorNotifier> notifier =
   500     new ErrorNotifier(mCallback, aError, this);
   501   NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
   503   return mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
   504 }
   506 nsresult
   507 AsyncExecuteStatements::notifyResults()
   508 {
   509   mMutex.AssertNotCurrentThreadOwns();
   510   NS_ASSERTION(mCallback, "notifyResults called without a callback!");
   512   nsRefPtr<CallbackResultNotifier> notifier =
   513     new CallbackResultNotifier(mCallback, mResultSet, this);
   514   NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
   516   nsresult rv = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
   517   if (NS_SUCCEEDED(rv))
   518     mResultSet = nullptr; // we no longer own it on success
   519   return rv;
   520 }
   522 NS_IMPL_ISUPPORTS(
   523   AsyncExecuteStatements,
   524   nsIRunnable,
   525   mozIStoragePendingStatement
   526 )
   528 bool
   529 AsyncExecuteStatements::statementsNeedTransaction()
   530 {
   531   // If there is more than one write statement, run in a transaction.
   532   // Additionally, if we have only one statement but it needs a transaction, due
   533   // to multiple BindingParams, we will wrap it in one.
   534   for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) {
   535     transactionsCount += mStatements[i].needsTransaction();
   536     if (transactionsCount > 1) {
   537       return true;
   538     }
   539   }
   540   return false;
   541 }
   543 ////////////////////////////////////////////////////////////////////////////////
   544 //// mozIStoragePendingStatement
   546 NS_IMETHODIMP
   547 AsyncExecuteStatements::Cancel()
   548 {
   549 #ifdef DEBUG
   550   bool onCallingThread = false;
   551   (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
   552   NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!");
   553 #endif
   555   // If we have already canceled, we have an error, but always indicate that
   556   // we are trying to cancel.
   557   NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED);
   559   {
   560     MutexAutoLock lockedScope(mMutex);
   562     // We need to indicate that we want to try and cancel now.
   563     mCancelRequested = true;
   564   }
   566   return NS_OK;
   567 }
   569 ////////////////////////////////////////////////////////////////////////////////
   570 //// nsIRunnable
   572 NS_IMETHODIMP
   573 AsyncExecuteStatements::Run()
   574 {
   575   MOZ_ASSERT(!mConnection->isClosed());
   577   // Do not run if we have been canceled.
   578   {
   579     MutexAutoLock lockedScope(mMutex);
   580     if (mCancelRequested)
   581       mState = CANCELED;
   582   }
   583   if (mState == CANCELED)
   584     return notifyComplete();
   586   if (statementsNeedTransaction()) {
   587     Connection* rawConnection = static_cast<Connection*>(mConnection.get());
   588     if (NS_SUCCEEDED(mConnection->beginTransactionInternal(mNativeConnection,
   589                                                            mozIStorageConnection::TRANSACTION_IMMEDIATE))) {
   590       mHasTransaction = true;
   591     }
   592 #ifdef DEBUG
   593     else {
   594       NS_WARNING("Unable to create a transaction for async execution.");
   595     }
   596 #endif
   597   }
   599   // Execute each statement, giving the callback results if it returns any.
   600   for (uint32_t i = 0; i < mStatements.Length(); i++) {
   601     bool finished = (i == (mStatements.Length() - 1));
   603     sqlite3_stmt *stmt;
   604     { // lock the sqlite mutex so sqlite3_errmsg cannot change
   605       SQLiteMutexAutoLock lockedScope(mDBMutex);
   607       int rc = mStatements[i].getSqliteStatement(&stmt);
   608       if (rc != SQLITE_OK) {
   609         // Set our error state.
   610         mState = ERROR;
   612         // Build the error object; can't call notifyError with the lock held
   613         nsCOMPtr<mozIStorageError> errorObj(
   614           new Error(rc, ::sqlite3_errmsg(mNativeConnection))
   615         );
   616         {
   617           // We cannot hold the DB mutex and call notifyError.
   618           SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
   619           (void)notifyError(errorObj);
   620         }
   621         break;
   622       }
   623     }
   625     // If we have parameters to bind, bind them, execute, and process.
   626     if (mStatements[i].hasParametersToBeBound()) {
   627       if (!bindExecuteAndProcessStatement(mStatements[i], finished))
   628         break;
   629     }
   630     // Otherwise, just execute and process the statement.
   631     else if (!executeAndProcessStatement(stmt, finished)) {
   632       break;
   633     }
   634   }
   636   // If we still have results that we haven't notified about, take care of
   637   // them now.
   638   if (mResultSet)
   639     (void)notifyResults();
   641   // Notify about completion
   642   return notifyComplete();
   643 }
   645 } // namespace storage
   646 } // namespace mozilla

mercurial