Bug 1441336 - Use addon permissions for PerformanceTiming properties r=bz,kmag
[gecko.git] / storage / mozStorageAsyncStatementExecution.cpp
blob656b3b70959bd27ac6a7e13da47d233377e99ebf
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
47 AsyncExecuteStatements::execute(StatementDataArray &aStatements,
48 Connection *aConnection,
49 sqlite3 *aNativeConnection,
50 mozIStorageStatementCallback *aCallback,
51 mozIStoragePendingStatement **_stmt)
53 // Create our event to run in the background
54 RefPtr<AsyncExecuteStatements> event =
55 new AsyncExecuteStatements(aStatements, aConnection, aNativeConnection,
56 aCallback);
57 NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
59 // Dispatch it to the background
60 nsIEventTarget *target = aConnection->getAsyncExecutionTarget();
62 // If we don't have a valid target, this is a bug somewhere else. In the past,
63 // this assert found cases where a Run method would schedule a new statement
64 // without checking if asyncClose had been called. The caller must prevent
65 // that from happening or, if the work is not critical, just avoid creating
66 // the new statement during shutdown. See bug 718449 for an example.
67 MOZ_ASSERT(target);
68 if (!target) {
69 return NS_ERROR_NOT_AVAILABLE;
72 nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
73 NS_ENSURE_SUCCESS(rv, rv);
75 // Return it as the pending statement object and track it.
76 event.forget(_stmt);
77 return NS_OK;
80 AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements,
81 Connection *aConnection,
82 sqlite3 *aNativeConnection,
83 mozIStorageStatementCallback *aCallback)
84 : mConnection(aConnection)
85 , mNativeConnection(aNativeConnection)
86 , mHasTransaction(false)
87 , mCallback(aCallback)
88 , mCallingThread(::do_GetCurrentThread())
89 , mMaxWait(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 , mRequestStartDate(TimeStamp::Now())
97 (void)mStatements.SwapElements(aStatements);
98 NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
101 AsyncExecuteStatements::~AsyncExecuteStatements()
103 MOZ_ASSERT(!mCallback, "Never called the Completion callback!");
104 MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point");
105 if (mCallback) {
106 NS_ProxyRelease("AsyncExecuteStatements::mCallback", mCallingThread,
107 mCallback.forget());
111 bool
112 AsyncExecuteStatements::shouldNotify()
114 #ifdef DEBUG
115 mMutex.AssertNotCurrentThreadOwns();
117 bool onCallingThread = false;
118 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
119 NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
120 #endif
122 // We do not need to acquire mMutex here because it can only ever be written
123 // to on the calling thread, and the only thread that can call us is the
124 // calling thread, so we know that our access is serialized.
125 return !mCancelRequested;
128 bool
129 AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData,
130 bool aLastStatement)
132 mMutex.AssertNotCurrentThreadOwns();
134 sqlite3_stmt *aStatement = nullptr;
135 // This cannot fail; we are only called if it's available.
136 (void)aData.getSqliteStatement(&aStatement);
137 NS_ASSERTION(aStatement, "You broke the code; do not call here like that!");
138 BindingParamsArray *paramsArray(aData);
140 // Iterate through all of our parameters, bind them, and execute.
141 bool continueProcessing = true;
142 BindingParamsArray::iterator itr = paramsArray->begin();
143 BindingParamsArray::iterator end = paramsArray->end();
144 while (itr != end && continueProcessing) {
145 // Bind the data to our statement.
146 nsCOMPtr<IStorageBindingParamsInternal> bindingInternal =
147 do_QueryInterface(*itr);
148 nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement);
149 if (error) {
150 // Set our error state.
151 mState = ERROR;
153 // And notify.
154 (void)notifyError(error);
155 return false;
158 // Advance our iterator, execute, and then process the statement.
159 itr++;
160 bool lastStatement = aLastStatement && itr == end;
161 continueProcessing = executeAndProcessStatement(aStatement, lastStatement);
163 // Always reset our statement.
164 (void)::sqlite3_reset(aStatement);
167 return continueProcessing;
170 bool
171 AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement,
172 bool aLastStatement)
174 mMutex.AssertNotCurrentThreadOwns();
176 // Execute our statement
177 bool hasResults;
178 do {
179 hasResults = executeStatement(aStatement);
181 // If we had an error, bail.
182 if (mState == ERROR || mState == CANCELED)
183 return false;
185 // If we have been canceled, there is no point in going on...
187 MutexAutoLock lockedScope(mMutex);
188 if (mCancelRequested) {
189 mState = CANCELED;
190 return false;
194 // Build our result set and notify if we got anything back and have a
195 // callback to notify.
196 if (mCallback && hasResults &&
197 NS_FAILED(buildAndNotifyResults(aStatement))) {
198 // We had an error notifying, so we notify on error and stop processing.
199 mState = ERROR;
201 // Notify, and stop processing statements.
202 (void)notifyError(mozIStorageError::ERROR,
203 "An error occurred while notifying about results");
205 return false;
207 } while (hasResults);
209 #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
210 if (MOZ_LOG_TEST(gStorageLog, LogLevel::Warning))
211 #endif
213 // Check to make sure that this statement was smart about what it did.
214 checkAndLogStatementPerformance(aStatement);
217 // If we are done, we need to set our state accordingly while we still hold
218 // our mutex. We would have already returned if we were canceled or had
219 // an error at this point.
220 if (aLastStatement)
221 mState = COMPLETED;
223 return true;
226 bool
227 AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement)
229 mMutex.AssertNotCurrentThreadOwns();
230 Telemetry::AutoTimer<Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_MS> finallySendExecutionDuration(mRequestStartDate);
231 while (true) {
232 // lock the sqlite mutex so sqlite3_errmsg cannot change
233 SQLiteMutexAutoLock lockedScope(mDBMutex);
235 int rc = mConnection->stepStatement(mNativeConnection, aStatement);
236 // Stop if we have no more results.
237 if (rc == SQLITE_DONE)
239 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
240 return false;
243 // If we got results, we can return now.
244 if (rc == SQLITE_ROW)
246 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
247 return true;
250 // Some errors are not fatal, and we can handle them and continue.
251 if (rc == SQLITE_BUSY) {
253 // Don't hold the lock while we call outside our module.
254 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
255 // Yield, and try again
256 (void)::PR_Sleep(PR_INTERVAL_NO_WAIT);
258 ::sqlite3_reset(aStatement);
259 continue;
262 if (rc == SQLITE_INTERRUPT) {
263 mState = CANCELED;
264 return false;
267 // Set an error state.
268 mState = ERROR;
269 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, false);
271 // Construct the error message before giving up the mutex (which we cannot
272 // hold during the call to notifyError).
273 nsCOMPtr<mozIStorageError> errorObj(
274 new Error(rc, ::sqlite3_errmsg(mNativeConnection))
276 // We cannot hold the DB mutex while calling notifyError.
277 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
278 (void)notifyError(errorObj);
280 // Finally, indicate that we should stop processing.
281 return false;
285 nsresult
286 AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement)
288 NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
289 mMutex.AssertNotCurrentThreadOwns();
291 // Build result object if we need it.
292 if (!mResultSet)
293 mResultSet = new ResultSet();
294 NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
296 RefPtr<Row> row(new Row());
297 NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
299 nsresult rv = row->initialize(aStatement);
300 NS_ENSURE_SUCCESS(rv, rv);
302 rv = mResultSet->add(row);
303 NS_ENSURE_SUCCESS(rv, rv);
305 // If we have hit our maximum number of allowed results, or if we have hit
306 // the maximum amount of time we want to wait for results, notify the
307 // calling thread about it.
308 TimeStamp now = TimeStamp::Now();
309 TimeDuration delta = now - mIntervalStart;
310 if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) {
311 // Notify the caller
312 rv = notifyResults();
313 if (NS_FAILED(rv))
314 return NS_OK; // we'll try again with the next result
316 // Reset our start time
317 mIntervalStart = now;
320 return NS_OK;
323 nsresult
324 AsyncExecuteStatements::notifyComplete()
326 mMutex.AssertNotCurrentThreadOwns();
327 NS_ASSERTION(mState != PENDING,
328 "Still in a pending state when calling Complete!");
330 // Reset our statements before we try to commit or rollback. If we are
331 // canceling and have statements that think they have pending work, the
332 // rollback will fail.
333 for (uint32_t i = 0; i < mStatements.Length(); i++)
334 mStatements[i].reset();
336 // Release references to the statement data as soon as possible. If this
337 // is the last reference, statements will be finalized immediately on the
338 // async thread, hence avoiding several bounces between threads and possible
339 // race conditions with AsyncClose().
340 mStatements.Clear();
342 // Handle our transaction, if we have one
343 if (mHasTransaction) {
344 if (mState == COMPLETED) {
345 nsresult rv = mConnection->commitTransactionInternal(mNativeConnection);
346 if (NS_FAILED(rv)) {
347 mState = ERROR;
348 (void)notifyError(mozIStorageError::ERROR,
349 "Transaction failed to commit");
352 else {
353 DebugOnly<nsresult> rv =
354 mConnection->rollbackTransactionInternal(mNativeConnection);
355 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Transaction failed to rollback");
357 mHasTransaction = false;
360 // This will take ownership of mCallback and make sure its destruction will
361 // happen on the owner thread.
362 Unused << mCallingThread->Dispatch(
363 NewRunnableMethod("AsyncExecuteStatements::notifyCompleteOnCallingThread",
364 this, &AsyncExecuteStatements::notifyCompleteOnCallingThread),
365 NS_DISPATCH_NORMAL);
367 return NS_OK;
370 nsresult
371 AsyncExecuteStatements::notifyCompleteOnCallingThread() {
372 MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
373 // Take ownership of mCallback and responsibility for freeing it when we
374 // release it. Any notifyResultsOnCallingThread and notifyErrorOnCallingThread
375 // calls on the stack spinning the event loop have guaranteed their safety by
376 // creating their own strong reference before invoking the callback.
377 nsCOMPtr<mozIStorageStatementCallback> callback = mCallback.forget();
378 if (callback) {
379 Unused << callback->HandleCompletion(mState);
381 return NS_OK;
384 nsresult
385 AsyncExecuteStatements::notifyError(int32_t aErrorCode,
386 const char *aMessage)
388 mMutex.AssertNotCurrentThreadOwns();
389 mDBMutex.assertNotCurrentThreadOwns();
391 if (!mCallback)
392 return NS_OK;
394 nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
395 NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
397 return notifyError(errorObj);
400 nsresult
401 AsyncExecuteStatements::notifyError(mozIStorageError *aError)
403 mMutex.AssertNotCurrentThreadOwns();
404 mDBMutex.assertNotCurrentThreadOwns();
406 if (!mCallback)
407 return NS_OK;
409 Unused << mCallingThread->Dispatch(
410 NewRunnableMethod<nsCOMPtr<mozIStorageError>>("AsyncExecuteStatements::notifyErrorOnCallingThread",
411 this, &AsyncExecuteStatements::notifyErrorOnCallingThread, aError),
412 NS_DISPATCH_NORMAL);
414 return NS_OK;
417 nsresult
418 AsyncExecuteStatements::notifyErrorOnCallingThread(mozIStorageError *aError) {
419 MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
420 // Acquire our own strong reference so that if the callback spins a nested
421 // event loop and notifyCompleteOnCallingThread is executed, forgetting
422 // mCallback, we still have a valid/strong reference that won't be freed until
423 // we exit.
424 nsCOMPtr<mozIStorageStatementCallback> callback = mCallback;
425 if (shouldNotify() && callback) {
426 Unused << callback->HandleError(aError);
428 return NS_OK;
431 nsresult
432 AsyncExecuteStatements::notifyResults()
434 mMutex.AssertNotCurrentThreadOwns();
435 MOZ_ASSERT(mCallback, "notifyResults called without a callback!");
437 // This takes ownership of mResultSet, a new one will be generated in
438 // buildAndNotifyResults() when further results will arrive.
439 Unused << mCallingThread->Dispatch(
440 NewRunnableMethod<RefPtr<ResultSet>>("AsyncExecuteStatements::notifyResultsOnCallingThread",
441 this, &AsyncExecuteStatements::notifyResultsOnCallingThread, mResultSet.forget()),
442 NS_DISPATCH_NORMAL);
444 return NS_OK;
447 nsresult
448 AsyncExecuteStatements::notifyResultsOnCallingThread(ResultSet *aResultSet)
450 MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
451 // Acquire our own strong reference so that if the callback spins a nested
452 // event loop and notifyCompleteOnCallingThread is executed, forgetting
453 // mCallback, we still have a valid/strong reference that won't be freed until
454 // we exit.
455 nsCOMPtr<mozIStorageStatementCallback> callback = mCallback;
456 if (shouldNotify() && callback) {
457 Unused << callback->HandleResult(aResultSet);
459 return NS_OK;
462 NS_IMPL_ISUPPORTS(
463 AsyncExecuteStatements,
464 nsIRunnable,
465 mozIStoragePendingStatement
468 bool
469 AsyncExecuteStatements::statementsNeedTransaction()
471 // If there is more than one write statement, run in a transaction.
472 // Additionally, if we have only one statement but it needs a transaction, due
473 // to multiple BindingParams, we will wrap it in one.
474 for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) {
475 transactionsCount += mStatements[i].needsTransaction();
476 if (transactionsCount > 1) {
477 return true;
480 return false;
483 ////////////////////////////////////////////////////////////////////////////////
484 //// mozIStoragePendingStatement
486 NS_IMETHODIMP
487 AsyncExecuteStatements::Cancel()
489 #ifdef DEBUG
490 bool onCallingThread = false;
491 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
492 NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!");
493 #endif
495 // If we have already canceled, we have an error, but always indicate that
496 // we are trying to cancel.
497 NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED);
500 MutexAutoLock lockedScope(mMutex);
502 // We need to indicate that we want to try and cancel now.
503 mCancelRequested = true;
506 return NS_OK;
509 ////////////////////////////////////////////////////////////////////////////////
510 //// nsIRunnable
512 NS_IMETHODIMP
513 AsyncExecuteStatements::Run()
515 MOZ_ASSERT(mConnection->isConnectionReadyOnThisThread());
517 // Do not run if we have been canceled.
519 MutexAutoLock lockedScope(mMutex);
520 if (mCancelRequested)
521 mState = CANCELED;
523 if (mState == CANCELED)
524 return notifyComplete();
526 if (statementsNeedTransaction() && mConnection->getAutocommit()) {
527 if (NS_SUCCEEDED(mConnection->beginTransactionInternal(mNativeConnection,
528 mozIStorageConnection::TRANSACTION_IMMEDIATE))) {
529 mHasTransaction = true;
531 #ifdef DEBUG
532 else {
533 NS_WARNING("Unable to create a transaction for async execution.");
535 #endif
538 // Execute each statement, giving the callback results if it returns any.
539 for (uint32_t i = 0; i < mStatements.Length(); i++) {
540 bool finished = (i == (mStatements.Length() - 1));
542 sqlite3_stmt *stmt;
543 { // lock the sqlite mutex so sqlite3_errmsg cannot change
544 SQLiteMutexAutoLock lockedScope(mDBMutex);
546 int rc = mStatements[i].getSqliteStatement(&stmt);
547 if (rc != SQLITE_OK) {
548 // Set our error state.
549 mState = ERROR;
551 // Build the error object; can't call notifyError with the lock held
552 nsCOMPtr<mozIStorageError> errorObj(
553 new Error(rc, ::sqlite3_errmsg(mNativeConnection))
556 // We cannot hold the DB mutex and call notifyError.
557 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
558 (void)notifyError(errorObj);
560 break;
564 // If we have parameters to bind, bind them, execute, and process.
565 if (mStatements[i].hasParametersToBeBound()) {
566 if (!bindExecuteAndProcessStatement(mStatements[i], finished))
567 break;
569 // Otherwise, just execute and process the statement.
570 else if (!executeAndProcessStatement(stmt, finished)) {
571 break;
575 // If we still have results that we haven't notified about, take care of
576 // them now.
577 if (mResultSet)
578 (void)notifyResults();
580 // Notify about completion
581 return notifyComplete();
584 } // namespace storage
585 } // namespace mozilla