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/. */
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"
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
39 #define MAX_MILLISECONDS_BETWEEN_RESULTS 75
40 #define MAX_ROWS_PER_RESULT 15
42 ////////////////////////////////////////////////////////////////////////////////
43 //// AsyncExecuteStatements
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.
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.
76 AsyncExecuteStatements::AsyncExecuteStatements(
77 StatementDataArray
& aStatements
, Connection
* aConnection
,
78 sqlite3
* aNativeConnection
, mozIStorageStatementCallback
* aCallback
)
79 : mConnection(aConnection
),
80 mNativeConnection(aNativeConnection
),
81 mHasTransaction(false),
83 mCallingThread(::do_GetCurrentThread()),
85 TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS
)),
86 mIntervalStart(TimeStamp::Now()),
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");
100 NS_ProxyRelease("AsyncExecuteStatements::mCallback", mCallingThread
,
105 bool AsyncExecuteStatements::shouldNotify() {
107 mMutex
.AssertNotCurrentThreadOwns();
109 bool onCallingThread
= false;
110 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
111 NS_ASSERTION(onCallingThread
, "runEvent not running on the calling thread!");
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
);
140 // Set our error state.
144 (void)notifyError(error
);
148 // Advance our iterator, execute, and then process the statement.
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
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
) {
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.
188 // Notify, and stop processing statements.
189 (void)notifyError(mozIStorageError::ERROR
,
190 "An error occurred while notifying about results");
194 } while (hasResults
);
196 #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
197 if (MOZ_LOG_TEST(gStorageLog
, LogLevel::Warning
))
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
;
212 bool AsyncExecuteStatements::executeStatement(sqlite3_stmt
* aStatement
) {
213 mMutex
.AssertNotCurrentThreadOwns();
214 Telemetry::AutoTimer
<Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_MS
>
215 finallySendExecutionDuration(mRequestStartDate
);
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
,
228 // If we got results, we can return now.
229 if (rc
== SQLITE_ROW
) {
230 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS
,
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
);
247 if (rc
== SQLITE_INTERRUPT
) {
252 // Set an error state.
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.
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
) {
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
;
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().
320 // Handle our transaction, if we have one
321 if (mHasTransaction
) {
322 SQLiteMutexAutoLock
lockedScope(mDBMutex
);
323 if (mState
== COMPLETED
) {
324 nsresult rv
= mConnection
->commitTransactionInternal(lockedScope
,
328 // We cannot hold the DB mutex while calling notifyError.
329 SQLiteMutexAutoUnlock
unlockedScope(mDBMutex
);
330 (void)notifyError(mozIStorageError::ERROR
,
331 "Transaction failed to commit");
334 DebugOnly
<nsresult
> rv
= mConnection
->rollbackTransactionInternal(
335 lockedScope
, mNativeConnection
);
336 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Transaction failed to rollback");
338 mHasTransaction
= false;
341 // This will take ownership of mCallback and make sure its destruction will
342 // happen on the owner thread.
343 Unused
<< mCallingThread
->Dispatch(
344 NewRunnableMethod("AsyncExecuteStatements::notifyCompleteOnCallingThread",
346 &AsyncExecuteStatements::notifyCompleteOnCallingThread
),
352 nsresult
AsyncExecuteStatements::notifyCompleteOnCallingThread() {
353 MOZ_ASSERT(mCallingThread
->IsOnCurrentThread());
354 // Take ownership of mCallback and responsibility for freeing it when we
355 // release it. Any notifyResultsOnCallingThread and
356 // notifyErrorOnCallingThread calls on the stack spinning the event loop have
357 // guaranteed their safety by creating their own strong reference before
358 // invoking the callback.
359 nsCOMPtr
<mozIStorageStatementCallback
> callback
= std::move(mCallback
);
361 Unused
<< callback
->HandleCompletion(mState
);
366 nsresult
AsyncExecuteStatements::notifyError(int32_t aErrorCode
,
367 const char* aMessage
) {
368 mMutex
.AssertNotCurrentThreadOwns();
369 mDBMutex
.assertNotCurrentThreadOwns();
371 if (!mCallback
) return NS_OK
;
373 nsCOMPtr
<mozIStorageError
> errorObj(new Error(aErrorCode
, aMessage
));
374 NS_ENSURE_TRUE(errorObj
, NS_ERROR_OUT_OF_MEMORY
);
376 return notifyError(errorObj
);
379 nsresult
AsyncExecuteStatements::notifyError(mozIStorageError
* aError
) {
380 mMutex
.AssertNotCurrentThreadOwns();
381 mDBMutex
.assertNotCurrentThreadOwns();
383 if (!mCallback
) return NS_OK
;
385 Unused
<< mCallingThread
->Dispatch(
386 NewRunnableMethod
<nsCOMPtr
<mozIStorageError
>>(
387 "AsyncExecuteStatements::notifyErrorOnCallingThread", this,
388 &AsyncExecuteStatements::notifyErrorOnCallingThread
, aError
),
394 nsresult
AsyncExecuteStatements::notifyErrorOnCallingThread(
395 mozIStorageError
* aError
) {
396 MOZ_ASSERT(mCallingThread
->IsOnCurrentThread());
397 // Acquire our own strong reference so that if the callback spins a nested
398 // event loop and notifyCompleteOnCallingThread is executed, forgetting
399 // mCallback, we still have a valid/strong reference that won't be freed until
401 nsCOMPtr
<mozIStorageStatementCallback
> callback
= mCallback
;
402 if (shouldNotify() && callback
) {
403 Unused
<< callback
->HandleError(aError
);
408 nsresult
AsyncExecuteStatements::notifyResults() {
409 mMutex
.AssertNotCurrentThreadOwns();
410 MOZ_ASSERT(mCallback
, "notifyResults called without a callback!");
412 // This takes ownership of mResultSet, a new one will be generated in
413 // buildAndNotifyResults() when further results will arrive.
414 Unused
<< mCallingThread
->Dispatch(
415 NewRunnableMethod
<RefPtr
<ResultSet
>>(
416 "AsyncExecuteStatements::notifyResultsOnCallingThread", this,
417 &AsyncExecuteStatements::notifyResultsOnCallingThread
,
418 mResultSet
.forget()),
424 nsresult
AsyncExecuteStatements::notifyResultsOnCallingThread(
425 ResultSet
* aResultSet
) {
426 MOZ_ASSERT(mCallingThread
->IsOnCurrentThread());
427 // Acquire our own strong reference so that if the callback spins a nested
428 // event loop and notifyCompleteOnCallingThread is executed, forgetting
429 // mCallback, we still have a valid/strong reference that won't be freed until
431 nsCOMPtr
<mozIStorageStatementCallback
> callback
= mCallback
;
432 if (shouldNotify() && callback
) {
433 Unused
<< callback
->HandleResult(aResultSet
);
438 NS_IMPL_ISUPPORTS(AsyncExecuteStatements
, nsIRunnable
,
439 mozIStoragePendingStatement
)
441 bool AsyncExecuteStatements::statementsNeedTransaction() {
442 // If there is more than one write statement, run in a transaction.
443 // Additionally, if we have only one statement but it needs a transaction, due
444 // to multiple BindingParams, we will wrap it in one.
445 for (uint32_t i
= 0, transactionsCount
= 0; i
< mStatements
.Length(); ++i
) {
446 transactionsCount
+= mStatements
[i
].needsTransaction();
447 if (transactionsCount
> 1) {
454 ////////////////////////////////////////////////////////////////////////////////
455 //// mozIStoragePendingStatement
458 AsyncExecuteStatements::Cancel() {
460 bool onCallingThread
= false;
461 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
462 NS_ASSERTION(onCallingThread
, "Not canceling from the calling thread!");
465 // If we have already canceled, we have an error, but always indicate that
466 // we are trying to cancel.
467 NS_ENSURE_FALSE(mCancelRequested
, NS_ERROR_UNEXPECTED
);
470 MutexAutoLock
lockedScope(mMutex
);
472 // We need to indicate that we want to try and cancel now.
473 mCancelRequested
= true;
479 ////////////////////////////////////////////////////////////////////////////////
483 AsyncExecuteStatements::Run() {
484 MOZ_ASSERT(mConnection
->isConnectionReadyOnThisThread());
486 // Do not run if we have been canceled.
488 MutexAutoLock
lockedScope(mMutex
);
489 if (mCancelRequested
) mState
= CANCELED
;
491 if (mState
== CANCELED
) return notifyComplete();
493 if (statementsNeedTransaction()) {
494 SQLiteMutexAutoLock
lockedScope(mDBMutex
);
495 if (!mConnection
->transactionInProgress(lockedScope
)) {
496 if (NS_SUCCEEDED(mConnection
->beginTransactionInternal(
497 lockedScope
, mNativeConnection
,
498 mozIStorageConnection::TRANSACTION_IMMEDIATE
))) {
499 mHasTransaction
= true;
503 NS_WARNING("Unable to create a transaction for async execution.");
509 // Execute each statement, giving the callback results if it returns any.
510 for (uint32_t i
= 0; i
< mStatements
.Length(); i
++) {
511 bool finished
= (i
== (mStatements
.Length() - 1));
514 { // lock the sqlite mutex so sqlite3_errmsg cannot change
515 SQLiteMutexAutoLock
lockedScope(mDBMutex
);
517 int rc
= mStatements
[i
].getSqliteStatement(&stmt
);
518 if (rc
!= SQLITE_OK
) {
519 // Set our error state.
522 // Build the error object; can't call notifyError with the lock held
523 nsCOMPtr
<mozIStorageError
> errorObj(
524 new Error(rc
, ::sqlite3_errmsg(mNativeConnection
)));
526 // We cannot hold the DB mutex and call notifyError.
527 SQLiteMutexAutoUnlock
unlockedScope(mDBMutex
);
528 (void)notifyError(errorObj
);
534 // If we have parameters to bind, bind them, execute, and process.
535 if (mStatements
[i
].hasParametersToBeBound()) {
536 if (!bindExecuteAndProcessStatement(mStatements
[i
], finished
)) break;
538 // Otherwise, just execute and process the statement.
539 else if (!executeAndProcessStatement(stmt
, finished
)) {
544 // If we still have results that we haven't notified about, take care of
546 if (mResultSet
) (void)notifyResults();
548 // Notify about completion
549 return notifyComplete();
552 } // namespace storage
553 } // namespace mozilla