tor-browser

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

mozStorageHelper.h (11288B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #ifndef MOZSTORAGEHELPER_H
      7 #define MOZSTORAGEHELPER_H
      8 
      9 #include "nsCOMPtr.h"
     10 #include "nsString.h"
     11 #include "mozilla/DebugOnly.h"
     12 #include "mozilla/ScopeExit.h"
     13 
     14 #include "mozilla/storage/SQLiteMutex.h"
     15 #include "mozIStorageConnection.h"
     16 #include "mozIStorageStatement.h"
     17 #include "mozIStoragePendingStatement.h"
     18 #include "mozilla/DebugOnly.h"
     19 #include "nsCOMPtr.h"
     20 #include "nsError.h"
     21 
     22 /**
     23 * This class wraps a transaction inside a given C++ scope, guaranteeing that
     24 * the transaction will be completed even if you have an exception or
     25 * return early.
     26 *
     27 * A common use is to create an instance with aCommitOnComplete = false
     28 * (rollback), then call Commit() on this object manually when your function
     29 * completes successfully.
     30 *
     31 * @note nested transactions are not supported by Sqlite, only nested
     32 * savepoints, so if a transaction is already in progress, this object creates
     33 * a nested savepoint to the existing transaction which is considered as
     34 * anonymous savepoint itself. However, aType and aAsyncCommit are ignored
     35 * in the case of nested savepoints.
     36 *
     37 * @param aConnection
     38 *        The connection to create the transaction on.
     39 * @param aCommitOnComplete
     40 *        Controls whether the transaction is committed or rolled back when
     41 *        this object goes out of scope.
     42 * @param aType [optional]
     43 *        The transaction type, as defined in mozIStorageConnection. Uses the
     44 *        default transaction behavior for the connection if unspecified.
     45 * @param aAsyncCommit [optional]
     46 *        Whether commit should be executed asynchronously on the helper thread.
     47 *        This is a special option introduced as an interim solution to reduce
     48 *        main-thread fsyncs in Places.  Can only be used on main-thread.
     49 *
     50 *        WARNING: YOU SHOULD _NOT_ WRITE NEW MAIN-THREAD CODE USING THIS!
     51 *
     52 *        Notice that async commit might cause synchronous statements to fail
     53 *        with SQLITE_BUSY.  A possible mitigation strategy is to use
     54 *        PRAGMA busy_timeout, but notice that might cause main-thread jank.
     55 *        Finally, if the database is using WAL journaling mode, other
     56 *        connections won't see the changes done in async committed transactions
     57 *        until commit is complete.
     58 *
     59 *        For all of the above reasons, this should only be used as an interim
     60 *        solution and avoided completely if possible.
     61 */
     62 class mozStorageTransaction {
     63  using SQLiteMutexAutoLock = mozilla::storage::SQLiteMutexAutoLock;
     64 
     65 public:
     66  mozStorageTransaction(
     67      mozIStorageConnection* aConnection, bool aCommitOnComplete,
     68      int32_t aType = mozIStorageConnection::TRANSACTION_DEFAULT,
     69      bool aAsyncCommit = false)
     70      : mConnection(aConnection),
     71        mType(aType),
     72        mNestingLevel(0),
     73        mHasTransaction(false),
     74        mCommitOnComplete(aCommitOnComplete),
     75        mCompleted(false),
     76        mAsyncCommit(aAsyncCommit) {}
     77 
     78  ~mozStorageTransaction() {
     79    if (mConnection && mHasTransaction && !mCompleted) {
     80      if (mCommitOnComplete) {
     81        mozilla::DebugOnly<nsresult> rv = Commit();
     82        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
     83                             "A transaction didn't commit correctly");
     84      } else {
     85        mozilla::DebugOnly<nsresult> rv = Rollback();
     86        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
     87                             "A transaction didn't rollback correctly");
     88      }
     89    }
     90  }
     91 
     92  /**
     93   * Starts the transaction.
     94   */
     95  nsresult Start() {
     96    // XXX We should probably get rid of mHasTransaction and use mConnection
     97    // for checking if a transaction has been started. However, we need to
     98    // first stop supporting null mConnection and also move aConnection from
     99    // the constructor to Start.
    100    MOZ_DIAGNOSTIC_ASSERT(!mHasTransaction);
    101 
    102    // XXX We should probably stop supporting null mConnection.
    103 
    104    // XXX We should probably get rid of mCompleted and allow to start the
    105    // transaction again if it was already committed or rolled back.
    106    if (!mConnection || mCompleted) {
    107      return NS_OK;
    108    }
    109 
    110    SQLiteMutexAutoLock lock(mConnection->GetSharedDBMutex());
    111 
    112    // We nee to speculatively set the nesting level to be able to decide
    113    // if this is a top level transaction and to be able to generate the
    114    // savepoint name.
    115    TransactionStarted(lock);
    116 
    117    // If there's a failure we need to revert the speculatively set nesting
    118    // level on the connection.
    119    auto autoFinishTransaction =
    120        mozilla::MakeScopeExit([&] { TransactionFinished(lock); });
    121 
    122    nsAutoCString query;
    123 
    124    if (TopLevelTransaction(lock)) {
    125      query.Assign("BEGIN");
    126      int32_t type = mType;
    127      if (type == mozIStorageConnection::TRANSACTION_DEFAULT) {
    128        MOZ_ALWAYS_SUCCEEDS(mConnection->GetDefaultTransactionType(&type));
    129      }
    130      switch (type) {
    131        case mozIStorageConnection::TRANSACTION_IMMEDIATE:
    132          query.AppendLiteral(" IMMEDIATE");
    133          break;
    134        case mozIStorageConnection::TRANSACTION_EXCLUSIVE:
    135          query.AppendLiteral(" EXCLUSIVE");
    136          break;
    137        case mozIStorageConnection::TRANSACTION_DEFERRED:
    138          query.AppendLiteral(" DEFERRED");
    139          break;
    140        default:
    141          MOZ_ASSERT(false, "Unknown transaction type");
    142      }
    143    } else {
    144      query.Assign("SAVEPOINT sp"_ns + IntToCString(mNestingLevel));
    145    }
    146 
    147    nsresult rv = mConnection->ExecuteSimpleSQL(query);
    148    NS_ENSURE_SUCCESS(rv, rv);
    149 
    150    autoFinishTransaction.release();
    151 
    152    return NS_OK;
    153  }
    154 
    155  /**
    156   * Commits the transaction if one is in progress. If one is not in progress,
    157   * this is a NOP since the actual owner of the transaction outside of our
    158   * scope is in charge of finally committing or rolling back the transaction.
    159   */
    160  nsresult Commit() {
    161    // XXX Assert instead of returning NS_OK if the transaction hasn't been
    162    // started.
    163    if (!mConnection || mCompleted || !mHasTransaction) return NS_OK;
    164 
    165    SQLiteMutexAutoLock lock(mConnection->GetSharedDBMutex());
    166 
    167 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    168    MOZ_DIAGNOSTIC_ASSERT(CurrentTransaction(lock));
    169 #else
    170    if (!CurrentTransaction(lock)) {
    171      return NS_ERROR_NOT_AVAILABLE;
    172    }
    173 #endif
    174 
    175    mCompleted = true;
    176 
    177    nsresult rv;
    178 
    179    if (TopLevelTransaction(lock)) {
    180      // TODO (bug 559659): this might fail with SQLITE_BUSY, but we don't
    181      // handle it, thus the transaction might stay open until the next COMMIT.
    182      if (mAsyncCommit) {
    183        nsCOMPtr<mozIStoragePendingStatement> ps;
    184        rv = mConnection->ExecuteSimpleSQLAsync("COMMIT"_ns, nullptr,
    185                                                getter_AddRefs(ps));
    186      } else {
    187        rv = mConnection->ExecuteSimpleSQL("COMMIT"_ns);
    188      }
    189    } else {
    190      rv = mConnection->ExecuteSimpleSQL("RELEASE sp"_ns +
    191                                         IntToCString(mNestingLevel));
    192    }
    193 
    194    NS_ENSURE_SUCCESS(rv, rv);
    195 
    196    TransactionFinished(lock);
    197 
    198    return NS_OK;
    199  }
    200 
    201  /**
    202   * Rolls back the transaction if one is in progress. If one is not in
    203   * progress, this is a NOP since the actual owner of the transaction outside
    204   * of our scope is in charge of finally rolling back the transaction.
    205   */
    206  nsresult Rollback() {
    207    // XXX Assert instead of returning NS_OK if the transaction hasn't been
    208    // started.
    209    if (!mConnection || mCompleted || !mHasTransaction) return NS_OK;
    210 
    211    SQLiteMutexAutoLock lock(mConnection->GetSharedDBMutex());
    212 
    213 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    214    MOZ_DIAGNOSTIC_ASSERT(CurrentTransaction(lock));
    215 #else
    216    if (!CurrentTransaction(lock)) {
    217      return NS_ERROR_NOT_AVAILABLE;
    218    }
    219 #endif
    220 
    221    mCompleted = true;
    222 
    223    nsresult rv;
    224 
    225    if (TopLevelTransaction(lock)) {
    226      // TODO (bug 1062823): from Sqlite 3.7.11 on, rollback won't ever return
    227      // a busy error, so this handling can be removed.
    228      do {
    229        rv = mConnection->ExecuteSimpleSQL("ROLLBACK"_ns);
    230        if (rv == NS_ERROR_STORAGE_BUSY) (void)PR_Sleep(PR_INTERVAL_NO_WAIT);
    231      } while (rv == NS_ERROR_STORAGE_BUSY);
    232    } else {
    233      const auto nestingLevelCString = IntToCString(mNestingLevel);
    234      rv = mConnection->ExecuteSimpleSQL(
    235          "ROLLBACK TO sp"_ns + nestingLevelCString + "; RELEASE sp"_ns +
    236          nestingLevelCString);
    237    }
    238 
    239    NS_ENSURE_SUCCESS(rv, rv);
    240 
    241    TransactionFinished(lock);
    242 
    243    return NS_OK;
    244  }
    245 
    246 protected:
    247  void TransactionStarted(const SQLiteMutexAutoLock& aProofOfLock) {
    248    MOZ_ASSERT(mConnection);
    249    MOZ_ASSERT(!mHasTransaction);
    250    MOZ_ASSERT(mNestingLevel == 0);
    251    mHasTransaction = true;
    252    mNestingLevel = mConnection->IncreaseTransactionNestingLevel(aProofOfLock);
    253  }
    254 
    255  bool CurrentTransaction(const SQLiteMutexAutoLock& aProofOfLock) const {
    256    MOZ_ASSERT(mConnection);
    257    MOZ_ASSERT(mHasTransaction);
    258    MOZ_ASSERT(mNestingLevel > 0);
    259    return mNestingLevel ==
    260           mConnection->GetTransactionNestingLevel(aProofOfLock);
    261  }
    262 
    263  bool TopLevelTransaction(const SQLiteMutexAutoLock& aProofOfLock) const {
    264    MOZ_ASSERT(mConnection);
    265    MOZ_ASSERT(mHasTransaction);
    266    MOZ_ASSERT(mNestingLevel > 0);
    267    MOZ_ASSERT(CurrentTransaction(aProofOfLock));
    268    return mNestingLevel == 1;
    269  }
    270 
    271  void TransactionFinished(const SQLiteMutexAutoLock& aProofOfLock) {
    272    MOZ_ASSERT(mConnection);
    273    MOZ_ASSERT(mHasTransaction);
    274    MOZ_ASSERT(mNestingLevel > 0);
    275    MOZ_ASSERT(CurrentTransaction(aProofOfLock));
    276    mConnection->DecreaseTransactionNestingLevel(aProofOfLock);
    277    mNestingLevel = 0;
    278    mHasTransaction = false;
    279  }
    280 
    281  nsCOMPtr<mozIStorageConnection> mConnection;
    282  int32_t mType;
    283  uint32_t mNestingLevel;
    284  bool mHasTransaction;
    285  bool mCommitOnComplete;
    286  bool mCompleted;
    287  bool mAsyncCommit;
    288 };
    289 
    290 /**
    291 * This class wraps a statement so that it is guaraneed to be reset when
    292 * this object goes out of scope.
    293 *
    294 * Note that this always just resets the statement. If the statement doesn't
    295 * need resetting, the reset operation is inexpensive.
    296 */
    297 class MOZ_STACK_CLASS mozStorageStatementScoper {
    298 public:
    299  explicit mozStorageStatementScoper(mozIStorageStatement* aStatement)
    300      : mStatement(aStatement) {}
    301  ~mozStorageStatementScoper() {
    302    if (mStatement) mStatement->Reset();
    303  }
    304 
    305  mozStorageStatementScoper(mozStorageStatementScoper&&) = default;
    306  mozStorageStatementScoper& operator=(mozStorageStatementScoper&&) = default;
    307  mozStorageStatementScoper(const mozStorageStatementScoper&) = delete;
    308  mozStorageStatementScoper& operator=(const mozStorageStatementScoper&) =
    309      delete;
    310 
    311  /**
    312   * Call this to make the statement not reset. You might do this if you know
    313   * that the statement has been reset.
    314   */
    315  void Abandon() { mStatement = nullptr; }
    316 
    317 protected:
    318  nsCOMPtr<mozIStorageStatement> mStatement;
    319 };
    320 
    321 // Use this to make queries uniquely identifiable in telemetry
    322 // statistics, especially PRAGMAs.  We don't include __LINE__ so that
    323 // queries are stable in the face of source code changes.
    324 #define MOZ_STORAGE_UNIQUIFY_QUERY_STR "/* " __FILE__ " */ "
    325 
    326 #endif /* MOZSTORAGEHELPER_H */