Backed out changeset 1a76a45e1895 (bug 1934145) for causing perma browser-time failur...
[gecko.git] / storage / mozStorageHelper.h
blob35ee54faa3779dd8e20cf34d7f90ad227ecb2363
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/. */
6 #ifndef MOZSTORAGEHELPER_H
7 #define MOZSTORAGEHELPER_H
9 #include "nsCOMPtr.h"
10 #include "nsString.h"
11 #include "mozilla/DebugOnly.h"
12 #include "mozilla/ScopeExit.h"
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"
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.
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.
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.
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.
50 * WARNING: YOU SHOULD _NOT_ WRITE NEW MAIN-THREAD CODE USING THIS!
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.
59 * For all of the above reasons, this should only be used as an interim
60 * solution and avoided completely if possible.
62 class mozStorageTransaction {
63 using SQLiteMutexAutoLock = mozilla::storage::SQLiteMutexAutoLock;
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) {}
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");
92 /**
93 * Starts the transaction.
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);
102 // XXX We should probably stop supporting null mConnection.
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;
110 SQLiteMutexAutoLock lock(mConnection->GetSharedDBMutex());
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);
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); });
122 nsAutoCString query;
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));
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");
143 } else {
144 query.Assign("SAVEPOINT sp"_ns + IntToCString(mNestingLevel));
147 nsresult rv = mConnection->ExecuteSimpleSQL(query);
148 NS_ENSURE_SUCCESS(rv, rv);
150 autoFinishTransaction.release();
152 return NS_OK;
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.
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;
165 SQLiteMutexAutoLock lock(mConnection->GetSharedDBMutex());
167 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
168 MOZ_DIAGNOSTIC_ASSERT(CurrentTransaction(lock));
169 #else
170 if (!CurrentTransaction(lock)) {
171 return NS_ERROR_NOT_AVAILABLE;
173 #endif
175 mCompleted = true;
177 nsresult rv;
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);
189 } else {
190 rv = mConnection->ExecuteSimpleSQL("RELEASE sp"_ns +
191 IntToCString(mNestingLevel));
194 NS_ENSURE_SUCCESS(rv, rv);
196 TransactionFinished(lock);
198 return NS_OK;
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.
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;
211 SQLiteMutexAutoLock lock(mConnection->GetSharedDBMutex());
213 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
214 MOZ_DIAGNOSTIC_ASSERT(CurrentTransaction(lock));
215 #else
216 if (!CurrentTransaction(lock)) {
217 return NS_ERROR_NOT_AVAILABLE;
219 #endif
221 mCompleted = true;
223 nsresult rv;
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);
239 NS_ENSURE_SUCCESS(rv, rv);
241 TransactionFinished(lock);
243 return NS_OK;
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);
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);
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;
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;
281 nsCOMPtr<mozIStorageConnection> mConnection;
282 int32_t mType;
283 uint32_t mNestingLevel;
284 bool mHasTransaction;
285 bool mCommitOnComplete;
286 bool mCompleted;
287 bool mAsyncCommit;
291 * This class wraps a statement so that it is guaraneed to be reset when
292 * this object goes out of scope.
294 * Note that this always just resets the statement. If the statement doesn't
295 * need resetting, the reset operation is inexpensive.
297 class MOZ_STACK_CLASS mozStorageStatementScoper {
298 public:
299 explicit mozStorageStatementScoper(mozIStorageStatement* aStatement)
300 : mStatement(aStatement) {}
301 ~mozStorageStatementScoper() {
302 if (mStatement) mStatement->Reset();
305 mozStorageStatementScoper(mozStorageStatementScoper&&) = default;
306 mozStorageStatementScoper& operator=(mozStorageStatementScoper&&) = default;
307 mozStorageStatementScoper(const mozStorageStatementScoper&) = delete;
308 mozStorageStatementScoper& operator=(const mozStorageStatementScoper&) =
309 delete;
312 * Call this to make the statement not reset. You might do this if you know
313 * that the statement has been reset.
315 void Abandon() { mStatement = nullptr; }
317 protected:
318 nsCOMPtr<mozIStorageStatement> mStatement;
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__ " */ "
326 #endif /* MOZSTORAGEHELPER_H */