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
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
,
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.
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.
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())
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");
106 NS_ProxyRelease("AsyncExecuteStatements::mCallback", mCallingThread
,
112 AsyncExecuteStatements::shouldNotify()
115 mMutex
.AssertNotCurrentThreadOwns();
117 bool onCallingThread
= false;
118 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
119 NS_ASSERTION(onCallingThread
, "runEvent not running on the calling thread!");
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
;
129 AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData
&aData
,
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
);
150 // Set our error state.
154 (void)notifyError(error
);
158 // Advance our iterator, execute, and then process the statement.
160 bool lastStatement
= aLastStatement
&& itr
== end
;
161 continueProcessing
= executeAndProcessStatement(aStatement
, lastStatement
);
163 // Always reset our statement.
164 (void)::sqlite3_reset(aStatement
);
167 return continueProcessing
;
171 AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt
*aStatement
,
174 mMutex
.AssertNotCurrentThreadOwns();
176 // Execute our statement
179 hasResults
= executeStatement(aStatement
);
181 // If we had an error, bail.
182 if (mState
== ERROR
|| mState
== CANCELED
)
185 // If we have been canceled, there is no point in going on...
187 MutexAutoLock
lockedScope(mMutex
);
188 if (mCancelRequested
) {
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.
201 // Notify, and stop processing statements.
202 (void)notifyError(mozIStorageError::ERROR
,
203 "An error occurred while notifying about results");
207 } while (hasResults
);
209 #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
210 if (MOZ_LOG_TEST(gStorageLog
, LogLevel::Warning
))
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.
227 AsyncExecuteStatements::executeStatement(sqlite3_stmt
*aStatement
)
229 mMutex
.AssertNotCurrentThreadOwns();
230 Telemetry::AutoTimer
<Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_MS
> finallySendExecutionDuration(mRequestStartDate
);
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);
243 // If we got results, we can return now.
244 if (rc
== SQLITE_ROW
)
246 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS
, 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
);
262 if (rc
== SQLITE_INTERRUPT
) {
267 // Set an error state.
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.
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.
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
) {
312 rv
= notifyResults();
314 return NS_OK
; // we'll try again with the next result
316 // Reset our start time
317 mIntervalStart
= now
;
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().
342 // Handle our transaction, if we have one
343 if (mHasTransaction
) {
344 if (mState
== COMPLETED
) {
345 nsresult rv
= mConnection
->commitTransactionInternal(mNativeConnection
);
348 (void)notifyError(mozIStorageError::ERROR
,
349 "Transaction failed to commit");
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
),
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();
379 Unused
<< callback
->HandleCompletion(mState
);
385 AsyncExecuteStatements::notifyError(int32_t aErrorCode
,
386 const char *aMessage
)
388 mMutex
.AssertNotCurrentThreadOwns();
389 mDBMutex
.assertNotCurrentThreadOwns();
394 nsCOMPtr
<mozIStorageError
> errorObj(new Error(aErrorCode
, aMessage
));
395 NS_ENSURE_TRUE(errorObj
, NS_ERROR_OUT_OF_MEMORY
);
397 return notifyError(errorObj
);
401 AsyncExecuteStatements::notifyError(mozIStorageError
*aError
)
403 mMutex
.AssertNotCurrentThreadOwns();
404 mDBMutex
.assertNotCurrentThreadOwns();
409 Unused
<< mCallingThread
->Dispatch(
410 NewRunnableMethod
<nsCOMPtr
<mozIStorageError
>>("AsyncExecuteStatements::notifyErrorOnCallingThread",
411 this, &AsyncExecuteStatements::notifyErrorOnCallingThread
, aError
),
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
424 nsCOMPtr
<mozIStorageStatementCallback
> callback
= mCallback
;
425 if (shouldNotify() && callback
) {
426 Unused
<< callback
->HandleError(aError
);
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()),
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
455 nsCOMPtr
<mozIStorageStatementCallback
> callback
= mCallback
;
456 if (shouldNotify() && callback
) {
457 Unused
<< callback
->HandleResult(aResultSet
);
463 AsyncExecuteStatements
,
465 mozIStoragePendingStatement
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) {
483 ////////////////////////////////////////////////////////////////////////////////
484 //// mozIStoragePendingStatement
487 AsyncExecuteStatements::Cancel()
490 bool onCallingThread
= false;
491 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
492 NS_ASSERTION(onCallingThread
, "Not canceling from the calling thread!");
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;
509 ////////////////////////////////////////////////////////////////////////////////
513 AsyncExecuteStatements::Run()
515 MOZ_ASSERT(mConnection
->isConnectionReadyOnThisThread());
517 // Do not run if we have been canceled.
519 MutexAutoLock
lockedScope(mMutex
);
520 if (mCancelRequested
)
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;
533 NS_WARNING("Unable to create a transaction for async execution.");
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));
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.
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
);
564 // If we have parameters to bind, bind them, execute, and process.
565 if (mStatements
[i
].hasParametersToBeBound()) {
566 if (!bindExecuteAndProcessStatement(mStatements
[i
], finished
))
569 // Otherwise, just execute and process the statement.
570 else if (!executeAndProcessStatement(stmt
, finished
)) {
575 // If we still have results that we haven't notified about, take care of
578 (void)notifyResults();
580 // Notify about completion
581 return notifyComplete();
584 } // namespace storage
585 } // namespace mozilla