Bumping gaia.json for 2 gaia revision(s) a=gaia-bump
[gecko.git] / storage / src / mozStorageAsyncStatementExecution.cpp
blobee8228accdafae63a8a7a30765f924c556ee1d37
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 //// Local Classes
45 namespace {
47 typedef AsyncExecuteStatements::ExecutionState ExecutionState;
48 typedef AsyncExecuteStatements::StatementDataArray StatementDataArray;
50 /**
51 * Notifies a callback with a result set.
53 class CallbackResultNotifier : public nsRunnable
55 public:
56 CallbackResultNotifier(mozIStorageStatementCallback *aCallback,
57 mozIStorageResultSet *aResults,
58 AsyncExecuteStatements *aEventStatus) :
59 mCallback(aCallback)
60 , mResults(aResults)
61 , mEventStatus(aEventStatus)
65 NS_IMETHOD Run()
67 NS_ASSERTION(mCallback, "Trying to notify about results without a callback!");
69 if (mEventStatus->shouldNotify()) {
70 // Hold a strong reference to the callback while notifying it, so that if
71 // it spins the event loop, the callback won't be released and freed out
72 // from under us.
73 nsCOMPtr<mozIStorageStatementCallback> callback =
74 do_QueryInterface(mCallback);
76 (void)mCallback->HandleResult(mResults);
79 return NS_OK;
82 private:
83 mozIStorageStatementCallback *mCallback;
84 nsCOMPtr<mozIStorageResultSet> mResults;
85 nsRefPtr<AsyncExecuteStatements> mEventStatus;
88 /**
89 * Notifies the calling thread that an error has occurred.
91 class ErrorNotifier : public nsRunnable
93 public:
94 ErrorNotifier(mozIStorageStatementCallback *aCallback,
95 mozIStorageError *aErrorObj,
96 AsyncExecuteStatements *aEventStatus) :
97 mCallback(aCallback)
98 , mErrorObj(aErrorObj)
99 , mEventStatus(aEventStatus)
103 NS_IMETHOD Run()
105 if (mEventStatus->shouldNotify() && mCallback) {
106 // Hold a strong reference to the callback while notifying it, so that if
107 // it spins the event loop, the callback won't be released and freed out
108 // from under us.
109 nsCOMPtr<mozIStorageStatementCallback> callback =
110 do_QueryInterface(mCallback);
112 (void)mCallback->HandleError(mErrorObj);
115 return NS_OK;
118 private:
119 mozIStorageStatementCallback *mCallback;
120 nsCOMPtr<mozIStorageError> mErrorObj;
121 nsRefPtr<AsyncExecuteStatements> mEventStatus;
125 * Notifies the calling thread that the statement has finished executing. Takes
126 * ownership of the StatementData so it is released on the proper thread.
128 class CompletionNotifier : public nsRunnable
130 public:
132 * This takes ownership of the callback and the StatementData. They are
133 * released on the thread this is dispatched to (which should always be the
134 * calling thread).
136 CompletionNotifier(mozIStorageStatementCallback *aCallback,
137 ExecutionState aReason)
138 : mCallback(aCallback)
139 , mReason(aReason)
143 NS_IMETHOD Run()
145 if (mCallback) {
146 (void)mCallback->HandleCompletion(mReason);
147 NS_RELEASE(mCallback);
150 return NS_OK;
153 private:
154 mozIStorageStatementCallback *mCallback;
155 ExecutionState mReason;
158 } // anonymous namespace
160 ////////////////////////////////////////////////////////////////////////////////
161 //// AsyncExecuteStatements
163 /* static */
164 nsresult
165 AsyncExecuteStatements::execute(StatementDataArray &aStatements,
166 Connection *aConnection,
167 sqlite3 *aNativeConnection,
168 mozIStorageStatementCallback *aCallback,
169 mozIStoragePendingStatement **_stmt)
171 // Create our event to run in the background
172 nsRefPtr<AsyncExecuteStatements> event =
173 new AsyncExecuteStatements(aStatements, aConnection, aNativeConnection,
174 aCallback);
175 NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
177 // Dispatch it to the background
178 nsIEventTarget *target = aConnection->getAsyncExecutionTarget();
180 // If we don't have a valid target, this is a bug somewhere else. In the past,
181 // this assert found cases where a Run method would schedule a new statement
182 // without checking if asyncClose had been called. The caller must prevent
183 // that from happening or, if the work is not critical, just avoid creating
184 // the new statement during shutdown. See bug 718449 for an example.
185 MOZ_ASSERT(target);
186 if (!target) {
187 return NS_ERROR_NOT_AVAILABLE;
190 nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
191 NS_ENSURE_SUCCESS(rv, rv);
193 // Return it as the pending statement object and track it.
194 NS_ADDREF(*_stmt = event);
195 return NS_OK;
198 AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements,
199 Connection *aConnection,
200 sqlite3 *aNativeConnection,
201 mozIStorageStatementCallback *aCallback)
202 : mConnection(aConnection)
203 , mNativeConnection(aNativeConnection)
204 , mHasTransaction(false)
205 , mCallback(aCallback)
206 , mCallingThread(::do_GetCurrentThread())
207 , mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS))
208 , mIntervalStart(TimeStamp::Now())
209 , mState(PENDING)
210 , mCancelRequested(false)
211 , mMutex(aConnection->sharedAsyncExecutionMutex)
212 , mDBMutex(aConnection->sharedDBMutex)
213 , mRequestStartDate(TimeStamp::Now())
215 (void)mStatements.SwapElements(aStatements);
216 NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
217 NS_IF_ADDREF(mCallback);
220 AsyncExecuteStatements::~AsyncExecuteStatements()
222 MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point");
225 bool
226 AsyncExecuteStatements::shouldNotify()
228 #ifdef DEBUG
229 mMutex.AssertNotCurrentThreadOwns();
231 bool onCallingThread = false;
232 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
233 NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
234 #endif
236 // We do not need to acquire mMutex here because it can only ever be written
237 // to on the calling thread, and the only thread that can call us is the
238 // calling thread, so we know that our access is serialized.
239 return !mCancelRequested;
242 bool
243 AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData,
244 bool aLastStatement)
246 mMutex.AssertNotCurrentThreadOwns();
248 sqlite3_stmt *aStatement = nullptr;
249 // This cannot fail; we are only called if it's available.
250 (void)aData.getSqliteStatement(&aStatement);
251 NS_ASSERTION(aStatement, "You broke the code; do not call here like that!");
252 BindingParamsArray *paramsArray(aData);
254 // Iterate through all of our parameters, bind them, and execute.
255 bool continueProcessing = true;
256 BindingParamsArray::iterator itr = paramsArray->begin();
257 BindingParamsArray::iterator end = paramsArray->end();
258 while (itr != end && continueProcessing) {
259 // Bind the data to our statement.
260 nsCOMPtr<IStorageBindingParamsInternal> bindingInternal =
261 do_QueryInterface(*itr);
262 nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement);
263 if (error) {
264 // Set our error state.
265 mState = ERROR;
267 // And notify.
268 (void)notifyError(error);
269 return false;
272 // Advance our iterator, execute, and then process the statement.
273 itr++;
274 bool lastStatement = aLastStatement && itr == end;
275 continueProcessing = executeAndProcessStatement(aStatement, lastStatement);
277 // Always reset our statement.
278 (void)::sqlite3_reset(aStatement);
281 return continueProcessing;
284 bool
285 AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement,
286 bool aLastStatement)
288 mMutex.AssertNotCurrentThreadOwns();
290 // Execute our statement
291 bool hasResults;
292 do {
293 hasResults = executeStatement(aStatement);
295 // If we had an error, bail.
296 if (mState == ERROR)
297 return false;
299 // If we have been canceled, there is no point in going on...
301 MutexAutoLock lockedScope(mMutex);
302 if (mCancelRequested) {
303 mState = CANCELED;
304 return false;
308 // Build our result set and notify if we got anything back and have a
309 // callback to notify.
310 if (mCallback && hasResults &&
311 NS_FAILED(buildAndNotifyResults(aStatement))) {
312 // We had an error notifying, so we notify on error and stop processing.
313 mState = ERROR;
315 // Notify, and stop processing statements.
316 (void)notifyError(mozIStorageError::ERROR,
317 "An error occurred while notifying about results");
319 return false;
321 } while (hasResults);
323 #ifdef DEBUG
324 // Check to make sure that this statement was smart about what it did.
325 checkAndLogStatementPerformance(aStatement);
326 #endif
328 // If we are done, we need to set our state accordingly while we still hold
329 // our mutex. We would have already returned if we were canceled or had
330 // an error at this point.
331 if (aLastStatement)
332 mState = COMPLETED;
334 return true;
337 bool
338 AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement)
340 mMutex.AssertNotCurrentThreadOwns();
341 Telemetry::AutoTimer<Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_MS> finallySendExecutionDuration(mRequestStartDate);
342 while (true) {
343 // lock the sqlite mutex so sqlite3_errmsg cannot change
344 SQLiteMutexAutoLock lockedScope(mDBMutex);
346 int rc = mConnection->stepStatement(mNativeConnection, aStatement);
347 // Stop if we have no more results.
348 if (rc == SQLITE_DONE)
350 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
351 return false;
354 // If we got results, we can return now.
355 if (rc == SQLITE_ROW)
357 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
358 return true;
361 // Some errors are not fatal, and we can handle them and continue.
362 if (rc == SQLITE_BUSY) {
363 // Don't hold the lock while we call outside our module.
364 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
366 // Yield, and try again
367 (void)::PR_Sleep(PR_INTERVAL_NO_WAIT);
368 continue;
371 // Set an error state.
372 mState = ERROR;
373 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, false);
375 // Construct the error message before giving up the mutex (which we cannot
376 // hold during the call to notifyError).
377 nsCOMPtr<mozIStorageError> errorObj(
378 new Error(rc, ::sqlite3_errmsg(mNativeConnection))
380 // We cannot hold the DB mutex while calling notifyError.
381 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
382 (void)notifyError(errorObj);
384 // Finally, indicate that we should stop processing.
385 return false;
389 nsresult
390 AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement)
392 NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
393 mMutex.AssertNotCurrentThreadOwns();
395 // Build result object if we need it.
396 if (!mResultSet)
397 mResultSet = new ResultSet();
398 NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
400 nsRefPtr<Row> row(new Row());
401 NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
403 nsresult rv = row->initialize(aStatement);
404 NS_ENSURE_SUCCESS(rv, rv);
406 rv = mResultSet->add(row);
407 NS_ENSURE_SUCCESS(rv, rv);
409 // If we have hit our maximum number of allowed results, or if we have hit
410 // the maximum amount of time we want to wait for results, notify the
411 // calling thread about it.
412 TimeStamp now = TimeStamp::Now();
413 TimeDuration delta = now - mIntervalStart;
414 if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) {
415 // Notify the caller
416 rv = notifyResults();
417 if (NS_FAILED(rv))
418 return NS_OK; // we'll try again with the next result
420 // Reset our start time
421 mIntervalStart = now;
424 return NS_OK;
427 nsresult
428 AsyncExecuteStatements::notifyComplete()
430 mMutex.AssertNotCurrentThreadOwns();
431 NS_ASSERTION(mState != PENDING,
432 "Still in a pending state when calling Complete!");
434 // Reset our statements before we try to commit or rollback. If we are
435 // canceling and have statements that think they have pending work, the
436 // rollback will fail.
437 for (uint32_t i = 0; i < mStatements.Length(); i++)
438 mStatements[i].reset();
440 // Release references to the statement data as soon as possible. If this
441 // is the last reference, statements will be finalized immediately on the
442 // async thread, hence avoiding several bounces between threads and possible
443 // race conditions with AsyncClose().
444 mStatements.Clear();
446 // Handle our transaction, if we have one
447 if (mHasTransaction) {
448 if (mState == COMPLETED) {
449 nsresult rv = mConnection->commitTransactionInternal(mNativeConnection);
450 if (NS_FAILED(rv)) {
451 mState = ERROR;
452 (void)notifyError(mozIStorageError::ERROR,
453 "Transaction failed to commit");
456 else {
457 DebugOnly<nsresult> rv =
458 mConnection->rollbackTransactionInternal(mNativeConnection);
459 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Transaction failed to rollback");
461 mHasTransaction = false;
464 // Always generate a completion notification; it is what guarantees that our
465 // destruction does not happen here on the async thread.
466 nsRefPtr<CompletionNotifier> completionEvent =
467 new CompletionNotifier(mCallback, mState);
469 // We no longer own mCallback (the CompletionNotifier takes ownership).
470 mCallback = nullptr;
472 (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
474 return NS_OK;
477 nsresult
478 AsyncExecuteStatements::notifyError(int32_t aErrorCode,
479 const char *aMessage)
481 mMutex.AssertNotCurrentThreadOwns();
482 mDBMutex.assertNotCurrentThreadOwns();
484 if (!mCallback)
485 return NS_OK;
487 nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
488 NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
490 return notifyError(errorObj);
493 nsresult
494 AsyncExecuteStatements::notifyError(mozIStorageError *aError)
496 mMutex.AssertNotCurrentThreadOwns();
497 mDBMutex.assertNotCurrentThreadOwns();
499 if (!mCallback)
500 return NS_OK;
502 nsRefPtr<ErrorNotifier> notifier =
503 new ErrorNotifier(mCallback, aError, this);
504 NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
506 return mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
509 nsresult
510 AsyncExecuteStatements::notifyResults()
512 mMutex.AssertNotCurrentThreadOwns();
513 NS_ASSERTION(mCallback, "notifyResults called without a callback!");
515 nsRefPtr<CallbackResultNotifier> notifier =
516 new CallbackResultNotifier(mCallback, mResultSet, this);
517 NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
519 nsresult rv = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
520 if (NS_SUCCEEDED(rv))
521 mResultSet = nullptr; // we no longer own it on success
522 return rv;
525 NS_IMPL_ISUPPORTS(
526 AsyncExecuteStatements,
527 nsIRunnable,
528 mozIStoragePendingStatement
531 bool
532 AsyncExecuteStatements::statementsNeedTransaction()
534 // If there is more than one write statement, run in a transaction.
535 // Additionally, if we have only one statement but it needs a transaction, due
536 // to multiple BindingParams, we will wrap it in one.
537 for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) {
538 transactionsCount += mStatements[i].needsTransaction();
539 if (transactionsCount > 1) {
540 return true;
543 return false;
546 ////////////////////////////////////////////////////////////////////////////////
547 //// mozIStoragePendingStatement
549 NS_IMETHODIMP
550 AsyncExecuteStatements::Cancel()
552 #ifdef DEBUG
553 bool onCallingThread = false;
554 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
555 NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!");
556 #endif
558 // If we have already canceled, we have an error, but always indicate that
559 // we are trying to cancel.
560 NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED);
563 MutexAutoLock lockedScope(mMutex);
565 // We need to indicate that we want to try and cancel now.
566 mCancelRequested = true;
569 return NS_OK;
572 ////////////////////////////////////////////////////////////////////////////////
573 //// nsIRunnable
575 NS_IMETHODIMP
576 AsyncExecuteStatements::Run()
578 MOZ_ASSERT(!mConnection->isClosed());
580 // Do not run if we have been canceled.
582 MutexAutoLock lockedScope(mMutex);
583 if (mCancelRequested)
584 mState = CANCELED;
586 if (mState == CANCELED)
587 return notifyComplete();
589 if (statementsNeedTransaction()) {
590 if (NS_SUCCEEDED(mConnection->beginTransactionInternal(mNativeConnection,
591 mozIStorageConnection::TRANSACTION_IMMEDIATE))) {
592 mHasTransaction = true;
594 #ifdef DEBUG
595 else {
596 NS_WARNING("Unable to create a transaction for async execution.");
598 #endif
601 // Execute each statement, giving the callback results if it returns any.
602 for (uint32_t i = 0; i < mStatements.Length(); i++) {
603 bool finished = (i == (mStatements.Length() - 1));
605 sqlite3_stmt *stmt;
606 { // lock the sqlite mutex so sqlite3_errmsg cannot change
607 SQLiteMutexAutoLock lockedScope(mDBMutex);
609 int rc = mStatements[i].getSqliteStatement(&stmt);
610 if (rc != SQLITE_OK) {
611 // Set our error state.
612 mState = ERROR;
614 // Build the error object; can't call notifyError with the lock held
615 nsCOMPtr<mozIStorageError> errorObj(
616 new Error(rc, ::sqlite3_errmsg(mNativeConnection))
619 // We cannot hold the DB mutex and call notifyError.
620 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
621 (void)notifyError(errorObj);
623 break;
627 // If we have parameters to bind, bind them, execute, and process.
628 if (mStatements[i].hasParametersToBeBound()) {
629 if (!bindExecuteAndProcessStatement(mStatements[i], finished))
630 break;
632 // Otherwise, just execute and process the statement.
633 else if (!executeAndProcessStatement(stmt, finished)) {
634 break;
638 // If we still have results that we haven't notified about, take care of
639 // them now.
640 if (mResultSet)
641 (void)notifyResults();
643 // Notify about completion
644 return notifyComplete();
647 } // namespace storage
648 } // namespace mozilla