tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

mozStorageAsyncStatementExecution.cpp (19673B)


      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/. */
      6 
      7 #include "sqlite3.h"
      8 
      9 #include "mozIStorageStatementCallback.h"
     10 #include "mozStorageBindingParams.h"
     11 #include "mozStorageHelper.h"
     12 #include "mozStorageResultSet.h"
     13 #include "mozStorageRow.h"
     14 #include "mozStorageConnection.h"
     15 #include "mozStorageError.h"
     16 #include "mozStoragePrivateHelpers.h"
     17 #include "mozStorageStatementData.h"
     18 #include "mozStorageAsyncStatementExecution.h"
     19 
     20 #include "mozilla/DebugOnly.h"
     21 
     22 #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
     23 #  include "mozilla/Logging.h"
     24 extern mozilla::LazyLogModule gStorageLog;
     25 #endif
     26 
     27 namespace mozilla {
     28 namespace storage {
     29 
     30 /**
     31 * The following constants help batch rows into result sets.
     32 * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that
     33 * takes less than 200 milliseconds is considered to feel instantaneous to end
     34 * users.  MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of
     35 * dispatches to calling thread, while also providing reasonably-sized sets of
     36 * data for consumers.  Both of these constants are used because we assume that
     37 * consumers are trying to avoid blocking their execution thread for long
     38 * periods of time, and dispatching many small events to the calling thread will
     39 * end up blocking it.
     40 */
     41 #define MAX_MILLISECONDS_BETWEEN_RESULTS 75
     42 #define MAX_ROWS_PER_RESULT 15
     43 
     44 ////////////////////////////////////////////////////////////////////////////////
     45 //// AsyncExecuteStatements
     46 
     47 /* static */
     48 nsresult AsyncExecuteStatements::execute(
     49    StatementDataArray&& aStatements, Connection* aConnection,
     50    sqlite3* aNativeConnection, mozIStorageStatementCallback* aCallback,
     51    mozIStoragePendingStatement** _stmt) {
     52  // Create our event to run in the background
     53  RefPtr<AsyncExecuteStatements> event = new AsyncExecuteStatements(
     54      std::move(aStatements), aConnection, aNativeConnection, aCallback);
     55  NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
     56 
     57  // Dispatch it to the background
     58  nsIEventTarget* target = aConnection->getAsyncExecutionTarget();
     59 
     60  // If we don't have a valid target, this is a bug somewhere else. In the past,
     61  // this assert found cases where a Run method would schedule a new statement
     62  // without checking if asyncClose had been called. The caller must prevent
     63  // that from happening or, if the work is not critical, just avoid creating
     64  // the new statement during shutdown. See bug 718449 for an example.
     65  MOZ_ASSERT(target);
     66  if (!target) {
     67    return NS_ERROR_NOT_AVAILABLE;
     68  }
     69 
     70  nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
     71  NS_ENSURE_SUCCESS(rv, rv);
     72 
     73  // Return it as the pending statement object and track it.
     74  event.forget(_stmt);
     75  return NS_OK;
     76 }
     77 
     78 AsyncExecuteStatements::AsyncExecuteStatements(
     79    StatementDataArray&& aStatements, Connection* aConnection,
     80    sqlite3* aNativeConnection, mozIStorageStatementCallback* aCallback)
     81    : Runnable("AsyncExecuteStatements"),
     82      mStatements(std::move(aStatements)),
     83      mConnection(aConnection),
     84      mNativeConnection(aNativeConnection),
     85      mHasTransaction(false),
     86      mCallback(aCallback),
     87      mCallingThread(::do_GetCurrentThread()),
     88      mMaxWait(
     89          TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS)),
     90      mIntervalStart(TimeStamp::Now()),
     91      mState(PENDING),
     92      mCancelRequested(false),
     93      mMutex(aConnection->sharedAsyncExecutionMutex),
     94      mDBMutex(aConnection->sharedDBMutex) {
     95  NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
     96 }
     97 
     98 AsyncExecuteStatements::~AsyncExecuteStatements() {
     99  MOZ_ASSERT(!mCallback, "Never called the Completion callback!");
    100  MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point");
    101  if (mCallback) {
    102    NS_ProxyRelease("AsyncExecuteStatements::mCallback", mCallingThread,
    103                    mCallback.forget());
    104  }
    105 }
    106 
    107 bool AsyncExecuteStatements::shouldNotify() {
    108 #ifdef DEBUG
    109  mMutex.AssertNotCurrentThreadOwns();
    110 
    111  bool onCallingThread = false;
    112  (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
    113  NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
    114 #endif
    115 
    116  // We do not need to acquire mMutex here because it can only ever be written
    117  // to on the calling thread, and the only thread that can call us is the
    118  // calling thread, so we know that our access is serialized.
    119  return !mCancelRequested;
    120 }
    121 
    122 bool AsyncExecuteStatements::bindExecuteAndProcessStatement(
    123    StatementData& aData, bool aLastStatement) {
    124  mMutex.AssertNotCurrentThreadOwns();
    125 
    126  sqlite3_stmt* aStatement = nullptr;
    127  // This cannot fail; we are only called if it's available.
    128  (void)aData.getSqliteStatement(&aStatement);
    129  MOZ_DIAGNOSTIC_ASSERT(
    130      aStatement,
    131      "bindExecuteAndProcessStatement called without an initialized statement");
    132  BindingParamsArray* paramsArray(aData);
    133 
    134  // Iterate through all of our parameters, bind them, and execute.
    135  bool continueProcessing = true;
    136  BindingParamsArray::iterator itr = paramsArray->begin();
    137  BindingParamsArray::iterator end = paramsArray->end();
    138  while (itr != end && continueProcessing) {
    139    // Bind the data to our statement.
    140    nsCOMPtr<IStorageBindingParamsInternal> bindingInternal =
    141        do_QueryInterface(*itr);
    142    nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement);
    143    if (error) {
    144      // Set our error state.
    145      mState = ERROR;
    146 
    147      // And notify.
    148      (void)notifyError(error);
    149      return false;
    150    }
    151 
    152    // Advance our iterator, execute, and then process the statement.
    153    itr++;
    154    bool lastStatement = aLastStatement && itr == end;
    155    continueProcessing = executeAndProcessStatement(aData, lastStatement);
    156 
    157    // Always reset our statement.
    158    (void)::sqlite3_reset(aStatement);
    159  }
    160 
    161  return continueProcessing;
    162 }
    163 
    164 bool AsyncExecuteStatements::executeAndProcessStatement(StatementData& aData,
    165                                                        bool aLastStatement) {
    166  mMutex.AssertNotCurrentThreadOwns();
    167 
    168  sqlite3_stmt* aStatement = nullptr;
    169  // This cannot fail; we are only called if it's available.
    170  (void)aData.getSqliteStatement(&aStatement);
    171  MOZ_DIAGNOSTIC_ASSERT(
    172      aStatement,
    173      "executeAndProcessStatement called without an initialized statement");
    174 
    175  // Execute our statement
    176  bool hasResults;
    177  do {
    178    hasResults = executeStatement(aData);
    179 
    180    // If we had an error, bail.
    181    if (mState == ERROR || mState == CANCELED) return false;
    182 
    183    // If we have been canceled, there is no point in going on...
    184    {
    185      MutexAutoLock lockedScope(mMutex);
    186      if (mCancelRequested) {
    187        mState = CANCELED;
    188        return false;
    189      }
    190    }
    191 
    192    // Build our result set and notify if we got anything back and have a
    193    // callback to notify.
    194    if (mCallback && hasResults &&
    195        NS_FAILED(buildAndNotifyResults(aStatement))) {
    196      // We had an error notifying, so we notify on error and stop processing.
    197      mState = ERROR;
    198 
    199      // Notify, and stop processing statements.
    200      (void)notifyError(mozIStorageError::ERROR,
    201                        "An error occurred while notifying about results");
    202 
    203      return false;
    204    }
    205  } while (hasResults);
    206 
    207 #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
    208  if (MOZ_LOG_TEST(gStorageLog, LogLevel::Warning))
    209 #endif
    210  {
    211    // Check to make sure that this statement was smart about what it did.
    212    checkAndLogStatementPerformance(aStatement);
    213  }
    214 
    215  // If we are done, we need to set our state accordingly while we still hold
    216  // our mutex.  We would have already returned if we were canceled or had
    217  // an error at this point.
    218  if (aLastStatement) mState = COMPLETED;
    219 
    220  return true;
    221 }
    222 
    223 bool AsyncExecuteStatements::executeStatement(StatementData& aData) {
    224  mMutex.AssertNotCurrentThreadOwns();
    225 
    226  sqlite3_stmt* aStatement = nullptr;
    227  // This cannot fail; we are only called if it's available.
    228  (void)aData.getSqliteStatement(&aStatement);
    229  MOZ_DIAGNOSTIC_ASSERT(
    230      aStatement, "executeStatement called without an initialized statement");
    231 
    232  bool busyRetry = false;
    233  while (true) {
    234    if (busyRetry) {
    235      busyRetry = false;
    236 
    237      // Yield, and try again
    238      (void)PR_Sleep(PR_INTERVAL_NO_WAIT);
    239 
    240      // Check for cancellation before retrying
    241      {
    242        MutexAutoLock lockedScope(mMutex);
    243        if (mCancelRequested) {
    244          mState = CANCELED;
    245          return false;
    246        }
    247      }
    248    }
    249 
    250    // lock the sqlite mutex so sqlite3_errmsg cannot change
    251    SQLiteMutexAutoLock lockedScope(mDBMutex);
    252 
    253    int rc = mConnection->stepStatement(mNativeConnection, aStatement);
    254 
    255    // Some errors are not fatal, and we can handle them and continue.
    256    if (rc == SQLITE_BUSY) {
    257      ::sqlite3_reset(aStatement);
    258      busyRetry = true;
    259      continue;
    260    }
    261 
    262    aData.MaybeRecordQueryStatus(rc);
    263 
    264    // Stop if we have no more results.
    265    if (rc == SQLITE_DONE) {
    266      return false;
    267    }
    268 
    269    // If we got results, we can return now.
    270    if (rc == SQLITE_ROW) {
    271      return true;
    272    }
    273 
    274    if (rc == SQLITE_INTERRUPT) {
    275      mState = CANCELED;
    276      return false;
    277    }
    278 
    279    // Set an error state.
    280    mState = ERROR;
    281 
    282    // Construct the error message before giving up the mutex (which we cannot
    283    // hold during the call to notifyError).
    284    nsCOMPtr<mozIStorageError> errorObj(
    285        new Error(rc, ::sqlite3_errmsg(mNativeConnection)));
    286    // We cannot hold the DB mutex while calling notifyError.
    287    SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
    288    (void)notifyError(errorObj);
    289 
    290    // Finally, indicate that we should stop processing.
    291    return false;
    292  }
    293 }
    294 
    295 nsresult AsyncExecuteStatements::buildAndNotifyResults(
    296    sqlite3_stmt* aStatement) {
    297  NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
    298  mMutex.AssertNotCurrentThreadOwns();
    299 
    300  // Build result object if we need it.
    301  if (!mResultSet) mResultSet = new ResultSet();
    302  NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
    303 
    304  RefPtr<Row> row(new Row());
    305  NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
    306 
    307  nsresult rv = row->initialize(aStatement);
    308  NS_ENSURE_SUCCESS(rv, rv);
    309 
    310  rv = mResultSet->add(row);
    311  NS_ENSURE_SUCCESS(rv, rv);
    312 
    313  // If we have hit our maximum number of allowed results, or if we have hit
    314  // the maximum amount of time we want to wait for results, notify the
    315  // calling thread about it.
    316  TimeStamp now = TimeStamp::Now();
    317  TimeDuration delta = now - mIntervalStart;
    318  if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) {
    319    // Notify the caller
    320    rv = notifyResults();
    321    if (NS_FAILED(rv)) return NS_OK;  // we'll try again with the next result
    322 
    323    // Reset our start time
    324    mIntervalStart = now;
    325  }
    326 
    327  return NS_OK;
    328 }
    329 
    330 nsresult AsyncExecuteStatements::notifyComplete() {
    331  mMutex.AssertNotCurrentThreadOwns();
    332  NS_ASSERTION(mState != PENDING,
    333               "Still in a pending state when calling Complete!");
    334 
    335  // Reset our statements before we try to commit or rollback.  If we are
    336  // canceling and have statements that think they have pending work, the
    337  // rollback will fail.
    338  for (uint32_t i = 0; i < mStatements.Length(); i++) mStatements[i].reset();
    339 
    340  // Release references to the statement data as soon as possible. If this
    341  // is the last reference, statements will be finalized immediately on the
    342  // async thread, hence avoiding several bounces between threads and possible
    343  // race conditions with AsyncClose().
    344  mStatements.Clear();
    345 
    346  // Handle our transaction, if we have one
    347  if (mHasTransaction) {
    348    SQLiteMutexAutoLock lockedScope(mDBMutex);
    349    if (mState == COMPLETED) {
    350      nsresult rv = mConnection->commitTransactionInternal(lockedScope,
    351                                                           mNativeConnection);
    352      if (NS_FAILED(rv)) {
    353        mState = ERROR;
    354        // We cannot hold the DB mutex while calling notifyError.
    355        SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
    356        (void)notifyError(mozIStorageError::ERROR,
    357                          "Transaction failed to commit");
    358      }
    359    } else {
    360      DebugOnly<nsresult> rv = mConnection->rollbackTransactionInternal(
    361          lockedScope, mNativeConnection);
    362      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Transaction failed to rollback");
    363    }
    364    mHasTransaction = false;
    365  }
    366 
    367  // This will take ownership of mCallback and make sure its destruction will
    368  // happen on the owner thread.
    369  (void)mCallingThread->Dispatch(
    370      NewRunnableMethod("AsyncExecuteStatements::notifyCompleteOnCallingThread",
    371                        this,
    372                        &AsyncExecuteStatements::notifyCompleteOnCallingThread),
    373      NS_DISPATCH_NORMAL);
    374 
    375  return NS_OK;
    376 }
    377 
    378 nsresult AsyncExecuteStatements::notifyCompleteOnCallingThread() {
    379  MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
    380  // Take ownership of mCallback and responsibility for freeing it when we
    381  // release it.  Any notifyResultsOnCallingThread and
    382  // notifyErrorOnCallingThread calls on the stack spinning the event loop have
    383  // guaranteed their safety by creating their own strong reference before
    384  // invoking the callback.
    385  nsCOMPtr<mozIStorageStatementCallback> callback = std::move(mCallback);
    386  if (callback) {
    387    (void)callback->HandleCompletion(mState);
    388  }
    389  return NS_OK;
    390 }
    391 
    392 nsresult AsyncExecuteStatements::notifyError(int32_t aErrorCode,
    393                                             const char* aMessage) {
    394  mMutex.AssertNotCurrentThreadOwns();
    395  mDBMutex.assertNotCurrentThreadOwns();
    396 
    397  if (!mCallback) return NS_OK;
    398 
    399  nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
    400  NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
    401 
    402  return notifyError(errorObj);
    403 }
    404 
    405 nsresult AsyncExecuteStatements::notifyError(mozIStorageError* aError) {
    406  mMutex.AssertNotCurrentThreadOwns();
    407  mDBMutex.assertNotCurrentThreadOwns();
    408 
    409  if (!mCallback) return NS_OK;
    410 
    411  (void)mCallingThread->Dispatch(
    412      NewRunnableMethod<nsCOMPtr<mozIStorageError>>(
    413          "AsyncExecuteStatements::notifyErrorOnCallingThread", this,
    414          &AsyncExecuteStatements::notifyErrorOnCallingThread, aError),
    415      NS_DISPATCH_NORMAL);
    416 
    417  return NS_OK;
    418 }
    419 
    420 nsresult AsyncExecuteStatements::notifyErrorOnCallingThread(
    421    mozIStorageError* aError) {
    422  MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
    423  // Acquire our own strong reference so that if the callback spins a nested
    424  // event loop and notifyCompleteOnCallingThread is executed, forgetting
    425  // mCallback, we still have a valid/strong reference that won't be freed until
    426  // we exit.
    427  nsCOMPtr<mozIStorageStatementCallback> callback = mCallback;
    428  if (shouldNotify() && callback) {
    429    (void)callback->HandleError(aError);
    430  }
    431  return NS_OK;
    432 }
    433 
    434 nsresult AsyncExecuteStatements::notifyResults() {
    435  mMutex.AssertNotCurrentThreadOwns();
    436  MOZ_ASSERT(mCallback, "notifyResults called without a callback!");
    437 
    438  // This takes ownership of mResultSet, a new one will be generated in
    439  // buildAndNotifyResults() when further results will arrive.
    440  (void)mCallingThread->Dispatch(
    441      NewRunnableMethod<RefPtr<ResultSet>>(
    442          "AsyncExecuteStatements::notifyResultsOnCallingThread", this,
    443          &AsyncExecuteStatements::notifyResultsOnCallingThread,
    444          mResultSet.forget()),
    445      NS_DISPATCH_NORMAL);
    446 
    447  return NS_OK;
    448 }
    449 
    450 nsresult AsyncExecuteStatements::notifyResultsOnCallingThread(
    451    ResultSet* aResultSet) {
    452  MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
    453  // Acquire our own strong reference so that if the callback spins a nested
    454  // event loop and notifyCompleteOnCallingThread is executed, forgetting
    455  // mCallback, we still have a valid/strong reference that won't be freed until
    456  // we exit.
    457  nsCOMPtr<mozIStorageStatementCallback> callback = mCallback;
    458  if (shouldNotify() && callback) {
    459    (void)callback->HandleResult(aResultSet);
    460  }
    461  return NS_OK;
    462 }
    463 
    464 NS_IMPL_ISUPPORTS_INHERITED(AsyncExecuteStatements, Runnable,
    465                            mozIStoragePendingStatement)
    466 
    467 bool AsyncExecuteStatements::statementsNeedTransaction() {
    468  // If there is more than one write statement, run in a transaction.
    469  // Additionally, if we have only one statement but it needs a transaction, due
    470  // to multiple BindingParams, we will wrap it in one.
    471  for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) {
    472    transactionsCount += mStatements[i].needsTransaction();
    473    if (transactionsCount > 1) {
    474      return true;
    475    }
    476  }
    477  return false;
    478 }
    479 
    480 ////////////////////////////////////////////////////////////////////////////////
    481 //// mozIStoragePendingStatement
    482 
    483 NS_IMETHODIMP
    484 AsyncExecuteStatements::Cancel() {
    485 #ifdef DEBUG
    486  bool onCallingThread = false;
    487  (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
    488  NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!");
    489 #endif
    490 
    491  // If we have already canceled, we have an error, but always indicate that
    492  // we are trying to cancel.
    493  NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED);
    494 
    495  {
    496    MutexAutoLock lockedScope(mMutex);
    497 
    498    // We need to indicate that we want to try and cancel now.
    499    mCancelRequested = true;
    500  }
    501 
    502  return NS_OK;
    503 }
    504 
    505 ////////////////////////////////////////////////////////////////////////////////
    506 //// nsIRunnable
    507 
    508 NS_IMETHODIMP
    509 AsyncExecuteStatements::Run() {
    510  MOZ_ASSERT(mConnection->isConnectionReadyOnThisThread());
    511 
    512  // Do not run if we have been canceled.
    513  {
    514    MutexAutoLock lockedScope(mMutex);
    515    if (mCancelRequested) mState = CANCELED;
    516  }
    517  if (mState == CANCELED) return notifyComplete();
    518 
    519  if (statementsNeedTransaction()) {
    520    SQLiteMutexAutoLock lockedScope(mDBMutex);
    521    if (!mConnection->transactionInProgress(lockedScope)) {
    522      if (NS_SUCCEEDED(mConnection->beginTransactionInternal(
    523              lockedScope, mNativeConnection,
    524              mozIStorageConnection::TRANSACTION_IMMEDIATE))) {
    525        mHasTransaction = true;
    526      }
    527 #ifdef DEBUG
    528      else {
    529        NS_WARNING("Unable to create a transaction for async execution.");
    530      }
    531 #endif
    532    }
    533  }
    534 
    535  // Execute each statement, giving the callback results if it returns any.
    536  for (uint32_t i = 0; i < mStatements.Length(); i++) {
    537    bool finished = (i == (mStatements.Length() - 1));
    538 
    539    sqlite3_stmt* stmt;
    540    {  // lock the sqlite mutex so sqlite3_errmsg cannot change
    541      SQLiteMutexAutoLock lockedScope(mDBMutex);
    542 
    543      int rc = mStatements[i].getSqliteStatement(&stmt);
    544      if (rc != SQLITE_OK) {
    545        // Set our error state.
    546        mState = ERROR;
    547 
    548        // Build the error object; can't call notifyError with the lock held
    549        nsCOMPtr<mozIStorageError> errorObj(
    550            new Error(rc, ::sqlite3_errmsg(mNativeConnection)));
    551        {
    552          // We cannot hold the DB mutex and call notifyError.
    553          SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
    554          (void)notifyError(errorObj);
    555        }
    556        break;
    557      }
    558    }
    559 
    560    // If we have parameters to bind, bind them, execute, and process.
    561    if (mStatements[i].hasParametersToBeBound()) {
    562      if (!bindExecuteAndProcessStatement(mStatements[i], finished)) break;
    563    }
    564    // Otherwise, just execute and process the statement.
    565    else if (!executeAndProcessStatement(mStatements[i], finished)) {
    566      break;
    567    }
    568  }
    569 
    570  // If we still have results that we haven't notified about, take care of
    571  // them now.
    572  if (mResultSet) (void)notifyResults();
    573 
    574  // Notify about completion
    575  return notifyComplete();
    576 }
    577 
    578 }  // namespace storage
    579 }  // namespace mozilla