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/. */
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
;
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
42 #define MAX_MILLISECONDS_BETWEEN_RESULTS 75
43 #define MAX_ROWS_PER_RESULT 15
45 ////////////////////////////////////////////////////////////////////////////////
46 //// AsyncExecuteStatements
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.
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.
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),
88 mCallingThread(::do_GetCurrentThread()),
90 TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS
)),
91 mIntervalStart(TimeStamp::Now()),
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");
103 NS_ProxyRelease("AsyncExecuteStatements::mCallback", mCallingThread
,
108 bool AsyncExecuteStatements::shouldNotify() {
110 mMutex
.AssertNotCurrentThreadOwns();
112 bool onCallingThread
= false;
113 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
114 NS_ASSERTION(onCallingThread
, "runEvent not running on the calling thread!");
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(
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
);
145 // Set our error state.
149 (void)notifyError(error
);
153 // Advance our iterator, execute, and then process the statement.
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(
174 "executeAndProcessStatement called without an initialized statement");
176 // Execute our statement
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
) {
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.
200 // Notify, and stop processing statements.
201 (void)notifyError(mozIStorageError::ERROR
,
202 "An error occurred while notifying about results");
206 } while (hasResults
);
208 #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
209 if (MOZ_LOG_TEST(gStorageLog
, LogLevel::Warning
))
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
;
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;
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
) {
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
);
263 aData
.MaybeRecordQueryStatus(rc
);
265 // Stop if we have no more results.
266 if (rc
== SQLITE_DONE
) {
270 // If we got results, we can return now.
271 if (rc
== SQLITE_ROW
) {
275 if (rc
== SQLITE_INTERRUPT
) {
280 // Set an error state.
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.
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
) {
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
;
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().
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
,
355 // We cannot hold the DB mutex while calling notifyError.
356 SQLiteMutexAutoUnlock
unlockedScope(mDBMutex
);
357 (void)notifyError(mozIStorageError::ERROR
,
358 "Transaction failed to commit");
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",
373 &AsyncExecuteStatements::notifyCompleteOnCallingThread
),
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
);
388 Unused
<< callback
->HandleCompletion(mState
);
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
),
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
428 nsCOMPtr
<mozIStorageStatementCallback
> callback
= mCallback
;
429 if (shouldNotify() && callback
) {
430 Unused
<< callback
->HandleError(aError
);
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()),
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
458 nsCOMPtr
<mozIStorageStatementCallback
> callback
= mCallback
;
459 if (shouldNotify() && callback
) {
460 Unused
<< callback
->HandleResult(aResultSet
);
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) {
481 ////////////////////////////////////////////////////////////////////////////////
482 //// mozIStoragePendingStatement
485 AsyncExecuteStatements::Cancel() {
487 bool onCallingThread
= false;
488 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
489 NS_ASSERTION(onCallingThread
, "Not canceling from the calling thread!");
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;
506 ////////////////////////////////////////////////////////////////////////////////
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;
530 NS_WARNING("Unable to create a transaction for async execution.");
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));
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.
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
);
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
)) {
571 // If we still have results that we haven't notified about, take care of
573 if (mResultSet
) (void)notifyResults();
575 // Notify about completion
576 return notifyComplete();
579 } // namespace storage
580 } // namespace mozilla