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