Bug 1708519 [wpt PR 28744] - Fix #28743: make the status checkboxes on the th.js...
[gecko.git] / storage / mozStorageAsyncStatementExecution.cpp
blob16954a15b7c94875c3fe56c8bfb9f6be0f548ada
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 "sqlite3.h"
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"
20 #include "mozilla/DebugOnly.h"
21 #include "mozilla/Telemetry.h"
23 #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
24 extern mozilla::LazyLogModule gStorageLog;
25 #endif
27 namespace mozilla {
28 namespace storage {
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.
41 #define MAX_MILLISECONDS_BETWEEN_RESULTS 75
42 #define MAX_ROWS_PER_RESULT 15
44 ////////////////////////////////////////////////////////////////////////////////
45 //// AsyncExecuteStatements
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);
57 // Dispatch it to the background
58 nsIEventTarget* target = aConnection->getAsyncExecutionTarget();
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;
70 nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
71 NS_ENSURE_SUCCESS(rv, rv);
73 // Return it as the pending statement object and track it.
74 event.forget(_stmt);
75 return NS_OK;
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!");
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());
107 bool AsyncExecuteStatements::shouldNotify() {
108 #ifdef DEBUG
109 mMutex.AssertNotCurrentThreadOwns();
111 bool onCallingThread = false;
112 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
113 NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
114 #endif
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;
122 bool AsyncExecuteStatements::bindExecuteAndProcessStatement(
123 StatementData& aData, bool aLastStatement) {
124 mMutex.AssertNotCurrentThreadOwns();
126 sqlite3_stmt* aStatement = nullptr;
127 // This cannot fail; we are only called if it's available.
128 Unused << aData.getSqliteStatement(&aStatement);
129 MOZ_DIAGNOSTIC_ASSERT(
130 aStatement,
131 "bindExecuteAndProcessStatement called without an initialized statement");
132 BindingParamsArray* paramsArray(aData);
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;
147 // And notify.
148 (void)notifyError(error);
149 return false;
152 // Advance our iterator, execute, and then process the statement.
153 itr++;
154 bool lastStatement = aLastStatement && itr == end;
155 continueProcessing = executeAndProcessStatement(aData, lastStatement);
157 // Always reset our statement.
158 (void)::sqlite3_reset(aStatement);
161 return continueProcessing;
164 bool AsyncExecuteStatements::executeAndProcessStatement(StatementData& aData,
165 bool aLastStatement) {
166 mMutex.AssertNotCurrentThreadOwns();
168 sqlite3_stmt* aStatement = nullptr;
169 // This cannot fail; we are only called if it's available.
170 Unused << aData.getSqliteStatement(&aStatement);
171 MOZ_DIAGNOSTIC_ASSERT(
172 aStatement,
173 "executeAndProcessStatement called without an initialized statement");
175 // Execute our statement
176 bool hasResults;
177 do {
178 hasResults = executeStatement(aData);
180 // If we had an error, bail.
181 if (mState == ERROR || mState == CANCELED) return false;
183 // If we have been canceled, there is no point in going on...
185 MutexAutoLock lockedScope(mMutex);
186 if (mCancelRequested) {
187 mState = CANCELED;
188 return false;
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;
199 // Notify, and stop processing statements.
200 (void)notifyError(mozIStorageError::ERROR,
201 "An error occurred while notifying about results");
203 return false;
205 } while (hasResults);
207 #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
208 if (MOZ_LOG_TEST(gStorageLog, LogLevel::Warning))
209 #endif
211 // Check to make sure that this statement was smart about what it did.
212 checkAndLogStatementPerformance(aStatement);
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;
220 return true;
223 bool AsyncExecuteStatements::executeStatement(StatementData& aData) {
224 mMutex.AssertNotCurrentThreadOwns();
226 sqlite3_stmt* aStatement = nullptr;
227 // This cannot fail; we are only called if it's available.
228 Unused << aData.getSqliteStatement(&aStatement);
229 MOZ_DIAGNOSTIC_ASSERT(
230 aStatement, "executeStatement called without an initialized statement");
232 bool busyRetry = false;
233 while (true) {
234 if (busyRetry) {
235 busyRetry = false;
237 // Yield, and try again
238 Unused << PR_Sleep(PR_INTERVAL_NO_WAIT);
240 // Check for cancellation before retrying
242 MutexAutoLock lockedScope(mMutex);
243 if (mCancelRequested) {
244 mState = CANCELED;
245 return false;
250 // lock the sqlite mutex so sqlite3_errmsg cannot change
251 SQLiteMutexAutoLock lockedScope(mDBMutex);
253 int rc = mConnection->stepStatement(mNativeConnection, aStatement);
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;
262 aData.MaybeRecordQueryStatus(rc);
264 // Stop if we have no more results.
265 if (rc == SQLITE_DONE) {
266 return false;
269 // If we got results, we can return now.
270 if (rc == SQLITE_ROW) {
271 return true;
274 if (rc == SQLITE_INTERRUPT) {
275 mState = CANCELED;
276 return false;
279 // Set an error state.
280 mState = ERROR;
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);
290 // Finally, indicate that we should stop processing.
291 return false;
295 nsresult AsyncExecuteStatements::buildAndNotifyResults(
296 sqlite3_stmt* aStatement) {
297 NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
298 mMutex.AssertNotCurrentThreadOwns();
300 // Build result object if we need it.
301 if (!mResultSet) mResultSet = new ResultSet();
302 NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
304 RefPtr<Row> row(new Row());
305 NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
307 nsresult rv = row->initialize(aStatement);
308 NS_ENSURE_SUCCESS(rv, rv);
310 rv = mResultSet->add(row);
311 NS_ENSURE_SUCCESS(rv, rv);
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
323 // Reset our start time
324 mIntervalStart = now;
327 return NS_OK;
330 nsresult AsyncExecuteStatements::notifyComplete() {
331 mMutex.AssertNotCurrentThreadOwns();
332 NS_ASSERTION(mState != PENDING,
333 "Still in a pending state when calling Complete!");
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();
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();
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");
359 } else {
360 DebugOnly<nsresult> rv = mConnection->rollbackTransactionInternal(
361 lockedScope, mNativeConnection);
362 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Transaction failed to rollback");
364 mHasTransaction = false;
367 // This will take ownership of mCallback and make sure its destruction will
368 // happen on the owner thread.
369 Unused << mCallingThread->Dispatch(
370 NewRunnableMethod("AsyncExecuteStatements::notifyCompleteOnCallingThread",
371 this,
372 &AsyncExecuteStatements::notifyCompleteOnCallingThread),
373 NS_DISPATCH_NORMAL);
375 return NS_OK;
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 Unused << callback->HandleCompletion(mState);
389 return NS_OK;
392 nsresult AsyncExecuteStatements::notifyError(int32_t aErrorCode,
393 const char* aMessage) {
394 mMutex.AssertNotCurrentThreadOwns();
395 mDBMutex.assertNotCurrentThreadOwns();
397 if (!mCallback) return NS_OK;
399 nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
400 NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
402 return notifyError(errorObj);
405 nsresult AsyncExecuteStatements::notifyError(mozIStorageError* aError) {
406 mMutex.AssertNotCurrentThreadOwns();
407 mDBMutex.assertNotCurrentThreadOwns();
409 if (!mCallback) return NS_OK;
411 Unused << mCallingThread->Dispatch(
412 NewRunnableMethod<nsCOMPtr<mozIStorageError>>(
413 "AsyncExecuteStatements::notifyErrorOnCallingThread", this,
414 &AsyncExecuteStatements::notifyErrorOnCallingThread, aError),
415 NS_DISPATCH_NORMAL);
417 return NS_OK;
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 Unused << callback->HandleError(aError);
431 return NS_OK;
434 nsresult AsyncExecuteStatements::notifyResults() {
435 mMutex.AssertNotCurrentThreadOwns();
436 MOZ_ASSERT(mCallback, "notifyResults called without a callback!");
438 // This takes ownership of mResultSet, a new one will be generated in
439 // buildAndNotifyResults() when further results will arrive.
440 Unused << mCallingThread->Dispatch(
441 NewRunnableMethod<RefPtr<ResultSet>>(
442 "AsyncExecuteStatements::notifyResultsOnCallingThread", this,
443 &AsyncExecuteStatements::notifyResultsOnCallingThread,
444 mResultSet.forget()),
445 NS_DISPATCH_NORMAL);
447 return NS_OK;
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 Unused << callback->HandleResult(aResultSet);
461 return NS_OK;
464 NS_IMPL_ISUPPORTS_INHERITED(AsyncExecuteStatements, Runnable,
465 mozIStoragePendingStatement)
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;
477 return false;
480 ////////////////////////////////////////////////////////////////////////////////
481 //// mozIStoragePendingStatement
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
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);
496 MutexAutoLock lockedScope(mMutex);
498 // We need to indicate that we want to try and cancel now.
499 mCancelRequested = true;
502 return NS_OK;
505 ////////////////////////////////////////////////////////////////////////////////
506 //// nsIRunnable
508 NS_IMETHODIMP
509 AsyncExecuteStatements::Run() {
510 MOZ_ASSERT(mConnection->isConnectionReadyOnThisThread());
512 // Do not run if we have been canceled.
514 MutexAutoLock lockedScope(mMutex);
515 if (mCancelRequested) mState = CANCELED;
517 if (mState == CANCELED) return notifyComplete();
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;
527 #ifdef DEBUG
528 else {
529 NS_WARNING("Unable to create a transaction for async execution.");
531 #endif
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));
539 sqlite3_stmt* stmt;
540 { // lock the sqlite mutex so sqlite3_errmsg cannot change
541 SQLiteMutexAutoLock lockedScope(mDBMutex);
543 int rc = mStatements[i].getSqliteStatement(&stmt);
544 if (rc != SQLITE_OK) {
545 // Set our error state.
546 mState = ERROR;
548 // Build the error object; can't call notifyError with the lock held
549 nsCOMPtr<mozIStorageError> errorObj(
550 new Error(rc, ::sqlite3_errmsg(mNativeConnection)));
552 // We cannot hold the DB mutex and call notifyError.
553 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
554 (void)notifyError(errorObj);
556 break;
560 // If we have parameters to bind, bind them, execute, and process.
561 if (mStatements[i].hasParametersToBeBound()) {
562 if (!bindExecuteAndProcessStatement(mStatements[i], finished)) break;
564 // Otherwise, just execute and process the statement.
565 else if (!executeAndProcessStatement(mStatements[i], finished)) {
566 break;
570 // If we still have results that we haven't notified about, take care of
571 // them now.
572 if (mResultSet) (void)notifyResults();
574 // Notify about completion
575 return notifyComplete();
578 } // namespace storage
579 } // namespace mozilla