Bug 1588538 - Add missing file.
[gecko.git] / storage / mozStorageAsyncStatementExecution.cpp
blob99ccac5b60ed9baed6740208be3f5d7d24bfe39b
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 "nsAutoPtr.h"
9 #include "sqlite3.h"
11 #include "mozIStorageStatementCallback.h"
12 #include "mozStorageBindingParams.h"
13 #include "mozStorageHelper.h"
14 #include "mozStorageResultSet.h"
15 #include "mozStorageRow.h"
16 #include "mozStorageConnection.h"
17 #include "mozStorageError.h"
18 #include "mozStoragePrivateHelpers.h"
19 #include "mozStorageStatementData.h"
20 #include "mozStorageAsyncStatementExecution.h"
22 #include "mozilla/DebugOnly.h"
23 #include "mozilla/Telemetry.h"
25 namespace mozilla {
26 namespace storage {
28 /**
29 * The following constants help batch rows into result sets.
30 * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that
31 * takes less than 200 milliseconds is considered to feel instantaneous to end
32 * users. MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of
33 * dispatches to calling thread, while also providing reasonably-sized sets of
34 * data for consumers. Both of these constants are used because we assume that
35 * consumers are trying to avoid blocking their execution thread for long
36 * periods of time, and dispatching many small events to the calling thread will
37 * end up blocking it.
39 #define MAX_MILLISECONDS_BETWEEN_RESULTS 75
40 #define MAX_ROWS_PER_RESULT 15
42 ////////////////////////////////////////////////////////////////////////////////
43 //// AsyncExecuteStatements
45 /* static */
46 nsresult AsyncExecuteStatements::execute(
47 StatementDataArray& aStatements, Connection* aConnection,
48 sqlite3* aNativeConnection, mozIStorageStatementCallback* aCallback,
49 mozIStoragePendingStatement** _stmt) {
50 // Create our event to run in the background
51 RefPtr<AsyncExecuteStatements> event = new AsyncExecuteStatements(
52 aStatements, aConnection, aNativeConnection, aCallback);
53 NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
55 // Dispatch it to the background
56 nsIEventTarget* target = aConnection->getAsyncExecutionTarget();
58 // If we don't have a valid target, this is a bug somewhere else. In the past,
59 // this assert found cases where a Run method would schedule a new statement
60 // without checking if asyncClose had been called. The caller must prevent
61 // that from happening or, if the work is not critical, just avoid creating
62 // the new statement during shutdown. See bug 718449 for an example.
63 MOZ_ASSERT(target);
64 if (!target) {
65 return NS_ERROR_NOT_AVAILABLE;
68 nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
69 NS_ENSURE_SUCCESS(rv, rv);
71 // Return it as the pending statement object and track it.
72 event.forget(_stmt);
73 return NS_OK;
76 AsyncExecuteStatements::AsyncExecuteStatements(
77 StatementDataArray& aStatements, Connection* aConnection,
78 sqlite3* aNativeConnection, mozIStorageStatementCallback* aCallback)
79 : mConnection(aConnection),
80 mNativeConnection(aNativeConnection),
81 mHasTransaction(false),
82 mCallback(aCallback),
83 mCallingThread(::do_GetCurrentThread()),
84 mMaxWait(
85 TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS)),
86 mIntervalStart(TimeStamp::Now()),
87 mState(PENDING),
88 mCancelRequested(false),
89 mMutex(aConnection->sharedAsyncExecutionMutex),
90 mDBMutex(aConnection->sharedDBMutex),
91 mRequestStartDate(TimeStamp::Now()) {
92 (void)mStatements.SwapElements(aStatements);
93 NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
96 AsyncExecuteStatements::~AsyncExecuteStatements() {
97 MOZ_ASSERT(!mCallback, "Never called the Completion callback!");
98 MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point");
99 if (mCallback) {
100 NS_ProxyRelease("AsyncExecuteStatements::mCallback", mCallingThread,
101 mCallback.forget());
105 bool AsyncExecuteStatements::shouldNotify() {
106 #ifdef DEBUG
107 mMutex.AssertNotCurrentThreadOwns();
109 bool onCallingThread = false;
110 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
111 NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
112 #endif
114 // We do not need to acquire mMutex here because it can only ever be written
115 // to on the calling thread, and the only thread that can call us is the
116 // calling thread, so we know that our access is serialized.
117 return !mCancelRequested;
120 bool AsyncExecuteStatements::bindExecuteAndProcessStatement(
121 StatementData& aData, bool aLastStatement) {
122 mMutex.AssertNotCurrentThreadOwns();
124 sqlite3_stmt* aStatement = nullptr;
125 // This cannot fail; we are only called if it's available.
126 (void)aData.getSqliteStatement(&aStatement);
127 NS_ASSERTION(aStatement, "You broke the code; do not call here like that!");
128 BindingParamsArray* paramsArray(aData);
130 // Iterate through all of our parameters, bind them, and execute.
131 bool continueProcessing = true;
132 BindingParamsArray::iterator itr = paramsArray->begin();
133 BindingParamsArray::iterator end = paramsArray->end();
134 while (itr != end && continueProcessing) {
135 // Bind the data to our statement.
136 nsCOMPtr<IStorageBindingParamsInternal> bindingInternal =
137 do_QueryInterface(*itr);
138 nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement);
139 if (error) {
140 // Set our error state.
141 mState = ERROR;
143 // And notify.
144 (void)notifyError(error);
145 return false;
148 // Advance our iterator, execute, and then process the statement.
149 itr++;
150 bool lastStatement = aLastStatement && itr == end;
151 continueProcessing = executeAndProcessStatement(aStatement, lastStatement);
153 // Always reset our statement.
154 (void)::sqlite3_reset(aStatement);
157 return continueProcessing;
160 bool AsyncExecuteStatements::executeAndProcessStatement(
161 sqlite3_stmt* aStatement, bool aLastStatement) {
162 mMutex.AssertNotCurrentThreadOwns();
164 // Execute our statement
165 bool hasResults;
166 do {
167 hasResults = executeStatement(aStatement);
169 // If we had an error, bail.
170 if (mState == ERROR || mState == CANCELED) return false;
172 // If we have been canceled, there is no point in going on...
174 MutexAutoLock lockedScope(mMutex);
175 if (mCancelRequested) {
176 mState = CANCELED;
177 return false;
181 // Build our result set and notify if we got anything back and have a
182 // callback to notify.
183 if (mCallback && hasResults &&
184 NS_FAILED(buildAndNotifyResults(aStatement))) {
185 // We had an error notifying, so we notify on error and stop processing.
186 mState = ERROR;
188 // Notify, and stop processing statements.
189 (void)notifyError(mozIStorageError::ERROR,
190 "An error occurred while notifying about results");
192 return false;
194 } while (hasResults);
196 #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
197 if (MOZ_LOG_TEST(gStorageLog, LogLevel::Warning))
198 #endif
200 // Check to make sure that this statement was smart about what it did.
201 checkAndLogStatementPerformance(aStatement);
204 // If we are done, we need to set our state accordingly while we still hold
205 // our mutex. We would have already returned if we were canceled or had
206 // an error at this point.
207 if (aLastStatement) mState = COMPLETED;
209 return true;
212 bool AsyncExecuteStatements::executeStatement(sqlite3_stmt* aStatement) {
213 mMutex.AssertNotCurrentThreadOwns();
214 Telemetry::AutoTimer<Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_MS>
215 finallySendExecutionDuration(mRequestStartDate);
216 while (true) {
217 // lock the sqlite mutex so sqlite3_errmsg cannot change
218 SQLiteMutexAutoLock lockedScope(mDBMutex);
220 int rc = mConnection->stepStatement(mNativeConnection, aStatement);
221 // Stop if we have no more results.
222 if (rc == SQLITE_DONE) {
223 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS,
224 true);
225 return false;
228 // If we got results, we can return now.
229 if (rc == SQLITE_ROW) {
230 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS,
231 true);
232 return true;
235 // Some errors are not fatal, and we can handle them and continue.
236 if (rc == SQLITE_BUSY) {
238 // Don't hold the lock while we call outside our module.
239 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
240 // Yield, and try again
241 (void)::PR_Sleep(PR_INTERVAL_NO_WAIT);
243 ::sqlite3_reset(aStatement);
244 continue;
247 if (rc == SQLITE_INTERRUPT) {
248 mState = CANCELED;
249 return false;
252 // Set an error state.
253 mState = ERROR;
254 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, false);
256 // Construct the error message before giving up the mutex (which we cannot
257 // hold during the call to notifyError).
258 nsCOMPtr<mozIStorageError> errorObj(
259 new Error(rc, ::sqlite3_errmsg(mNativeConnection)));
260 // We cannot hold the DB mutex while calling notifyError.
261 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
262 (void)notifyError(errorObj);
264 // Finally, indicate that we should stop processing.
265 return false;
269 nsresult AsyncExecuteStatements::buildAndNotifyResults(
270 sqlite3_stmt* aStatement) {
271 NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
272 mMutex.AssertNotCurrentThreadOwns();
274 // Build result object if we need it.
275 if (!mResultSet) mResultSet = new ResultSet();
276 NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
278 RefPtr<Row> row(new Row());
279 NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
281 nsresult rv = row->initialize(aStatement);
282 NS_ENSURE_SUCCESS(rv, rv);
284 rv = mResultSet->add(row);
285 NS_ENSURE_SUCCESS(rv, rv);
287 // If we have hit our maximum number of allowed results, or if we have hit
288 // the maximum amount of time we want to wait for results, notify the
289 // calling thread about it.
290 TimeStamp now = TimeStamp::Now();
291 TimeDuration delta = now - mIntervalStart;
292 if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) {
293 // Notify the caller
294 rv = notifyResults();
295 if (NS_FAILED(rv)) return NS_OK; // we'll try again with the next result
297 // Reset our start time
298 mIntervalStart = now;
301 return NS_OK;
304 nsresult AsyncExecuteStatements::notifyComplete() {
305 mMutex.AssertNotCurrentThreadOwns();
306 NS_ASSERTION(mState != PENDING,
307 "Still in a pending state when calling Complete!");
309 // Reset our statements before we try to commit or rollback. If we are
310 // canceling and have statements that think they have pending work, the
311 // rollback will fail.
312 for (uint32_t i = 0; i < mStatements.Length(); i++) mStatements[i].reset();
314 // Release references to the statement data as soon as possible. If this
315 // is the last reference, statements will be finalized immediately on the
316 // async thread, hence avoiding several bounces between threads and possible
317 // race conditions with AsyncClose().
318 mStatements.Clear();
320 // Handle our transaction, if we have one
321 if (mHasTransaction) {
322 if (mState == COMPLETED) {
323 nsresult rv = mConnection->commitTransactionInternal(mNativeConnection);
324 if (NS_FAILED(rv)) {
325 mState = ERROR;
326 (void)notifyError(mozIStorageError::ERROR,
327 "Transaction failed to commit");
329 } else {
330 DebugOnly<nsresult> rv =
331 mConnection->rollbackTransactionInternal(mNativeConnection);
332 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Transaction failed to rollback");
334 mHasTransaction = false;
337 // This will take ownership of mCallback and make sure its destruction will
338 // happen on the owner thread.
339 Unused << mCallingThread->Dispatch(
340 NewRunnableMethod("AsyncExecuteStatements::notifyCompleteOnCallingThread",
341 this,
342 &AsyncExecuteStatements::notifyCompleteOnCallingThread),
343 NS_DISPATCH_NORMAL);
345 return NS_OK;
348 nsresult AsyncExecuteStatements::notifyCompleteOnCallingThread() {
349 MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
350 // Take ownership of mCallback and responsibility for freeing it when we
351 // release it. Any notifyResultsOnCallingThread and
352 // notifyErrorOnCallingThread calls on the stack spinning the event loop have
353 // guaranteed their safety by creating their own strong reference before
354 // invoking the callback.
355 nsCOMPtr<mozIStorageStatementCallback> callback = mCallback.forget();
356 if (callback) {
357 Unused << callback->HandleCompletion(mState);
359 return NS_OK;
362 nsresult AsyncExecuteStatements::notifyError(int32_t aErrorCode,
363 const char* aMessage) {
364 mMutex.AssertNotCurrentThreadOwns();
365 mDBMutex.assertNotCurrentThreadOwns();
367 if (!mCallback) return NS_OK;
369 nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
370 NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
372 return notifyError(errorObj);
375 nsresult AsyncExecuteStatements::notifyError(mozIStorageError* aError) {
376 mMutex.AssertNotCurrentThreadOwns();
377 mDBMutex.assertNotCurrentThreadOwns();
379 if (!mCallback) return NS_OK;
381 Unused << mCallingThread->Dispatch(
382 NewRunnableMethod<nsCOMPtr<mozIStorageError>>(
383 "AsyncExecuteStatements::notifyErrorOnCallingThread", this,
384 &AsyncExecuteStatements::notifyErrorOnCallingThread, aError),
385 NS_DISPATCH_NORMAL);
387 return NS_OK;
390 nsresult AsyncExecuteStatements::notifyErrorOnCallingThread(
391 mozIStorageError* aError) {
392 MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
393 // Acquire our own strong reference so that if the callback spins a nested
394 // event loop and notifyCompleteOnCallingThread is executed, forgetting
395 // mCallback, we still have a valid/strong reference that won't be freed until
396 // we exit.
397 nsCOMPtr<mozIStorageStatementCallback> callback = mCallback;
398 if (shouldNotify() && callback) {
399 Unused << callback->HandleError(aError);
401 return NS_OK;
404 nsresult AsyncExecuteStatements::notifyResults() {
405 mMutex.AssertNotCurrentThreadOwns();
406 MOZ_ASSERT(mCallback, "notifyResults called without a callback!");
408 // This takes ownership of mResultSet, a new one will be generated in
409 // buildAndNotifyResults() when further results will arrive.
410 Unused << mCallingThread->Dispatch(
411 NewRunnableMethod<RefPtr<ResultSet>>(
412 "AsyncExecuteStatements::notifyResultsOnCallingThread", this,
413 &AsyncExecuteStatements::notifyResultsOnCallingThread,
414 mResultSet.forget()),
415 NS_DISPATCH_NORMAL);
417 return NS_OK;
420 nsresult AsyncExecuteStatements::notifyResultsOnCallingThread(
421 ResultSet* aResultSet) {
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->HandleResult(aResultSet);
431 return NS_OK;
434 NS_IMPL_ISUPPORTS(AsyncExecuteStatements, nsIRunnable,
435 mozIStoragePendingStatement)
437 bool AsyncExecuteStatements::statementsNeedTransaction() {
438 // If there is more than one write statement, run in a transaction.
439 // Additionally, if we have only one statement but it needs a transaction, due
440 // to multiple BindingParams, we will wrap it in one.
441 for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) {
442 transactionsCount += mStatements[i].needsTransaction();
443 if (transactionsCount > 1) {
444 return true;
447 return false;
450 ////////////////////////////////////////////////////////////////////////////////
451 //// mozIStoragePendingStatement
453 NS_IMETHODIMP
454 AsyncExecuteStatements::Cancel() {
455 #ifdef DEBUG
456 bool onCallingThread = false;
457 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
458 NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!");
459 #endif
461 // If we have already canceled, we have an error, but always indicate that
462 // we are trying to cancel.
463 NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED);
466 MutexAutoLock lockedScope(mMutex);
468 // We need to indicate that we want to try and cancel now.
469 mCancelRequested = true;
472 return NS_OK;
475 ////////////////////////////////////////////////////////////////////////////////
476 //// nsIRunnable
478 NS_IMETHODIMP
479 AsyncExecuteStatements::Run() {
480 MOZ_ASSERT(mConnection->isConnectionReadyOnThisThread());
482 // Do not run if we have been canceled.
484 MutexAutoLock lockedScope(mMutex);
485 if (mCancelRequested) mState = CANCELED;
487 if (mState == CANCELED) return notifyComplete();
489 if (statementsNeedTransaction() && mConnection->getAutocommit()) {
490 if (NS_SUCCEEDED(mConnection->beginTransactionInternal(
491 mNativeConnection, mozIStorageConnection::TRANSACTION_IMMEDIATE))) {
492 mHasTransaction = true;
494 #ifdef DEBUG
495 else {
496 NS_WARNING("Unable to create a transaction for async execution.");
498 #endif
501 // Execute each statement, giving the callback results if it returns any.
502 for (uint32_t i = 0; i < mStatements.Length(); i++) {
503 bool finished = (i == (mStatements.Length() - 1));
505 sqlite3_stmt* stmt;
506 { // lock the sqlite mutex so sqlite3_errmsg cannot change
507 SQLiteMutexAutoLock lockedScope(mDBMutex);
509 int rc = mStatements[i].getSqliteStatement(&stmt);
510 if (rc != SQLITE_OK) {
511 // Set our error state.
512 mState = ERROR;
514 // Build the error object; can't call notifyError with the lock held
515 nsCOMPtr<mozIStorageError> errorObj(
516 new Error(rc, ::sqlite3_errmsg(mNativeConnection)));
518 // We cannot hold the DB mutex and call notifyError.
519 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
520 (void)notifyError(errorObj);
522 break;
526 // If we have parameters to bind, bind them, execute, and process.
527 if (mStatements[i].hasParametersToBeBound()) {
528 if (!bindExecuteAndProcessStatement(mStatements[i], finished)) break;
530 // Otherwise, just execute and process the statement.
531 else if (!executeAndProcessStatement(stmt, finished)) {
532 break;
536 // If we still have results that we haven't notified about, take care of
537 // them now.
538 if (mResultSet) (void)notifyResults();
540 // Notify about completion
541 return notifyComplete();
544 } // namespace storage
545 } // namespace mozilla