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 ////////////////////////////////////////////////////////////////////////////////
47 typedef AsyncExecuteStatements::ExecutionState ExecutionState
;
48 typedef AsyncExecuteStatements::StatementDataArray StatementDataArray
;
51 * Notifies a callback with a result set.
53 class CallbackResultNotifier
: public nsRunnable
56 CallbackResultNotifier(mozIStorageStatementCallback
*aCallback
,
57 mozIStorageResultSet
*aResults
,
58 AsyncExecuteStatements
*aEventStatus
) :
61 , mEventStatus(aEventStatus
)
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
73 nsCOMPtr
<mozIStorageStatementCallback
> callback
=
74 do_QueryInterface(mCallback
);
76 (void)mCallback
->HandleResult(mResults
);
83 mozIStorageStatementCallback
*mCallback
;
84 nsCOMPtr
<mozIStorageResultSet
> mResults
;
85 nsRefPtr
<AsyncExecuteStatements
> mEventStatus
;
89 * Notifies the calling thread that an error has occurred.
91 class ErrorNotifier
: public nsRunnable
94 ErrorNotifier(mozIStorageStatementCallback
*aCallback
,
95 mozIStorageError
*aErrorObj
,
96 AsyncExecuteStatements
*aEventStatus
) :
98 , mErrorObj(aErrorObj
)
99 , mEventStatus(aEventStatus
)
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
109 nsCOMPtr
<mozIStorageStatementCallback
> callback
=
110 do_QueryInterface(mCallback
);
112 (void)mCallback
->HandleError(mErrorObj
);
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
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
136 CompletionNotifier(mozIStorageStatementCallback
*aCallback
,
137 ExecutionState aReason
)
138 : mCallback(aCallback
)
146 (void)mCallback
->HandleCompletion(mReason
);
147 NS_RELEASE(mCallback
);
154 mozIStorageStatementCallback
*mCallback
;
155 ExecutionState mReason
;
158 } // anonymous namespace
160 ////////////////////////////////////////////////////////////////////////////////
161 //// AsyncExecuteStatements
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
,
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.
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
);
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())
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");
226 AsyncExecuteStatements::shouldNotify()
229 mMutex
.AssertNotCurrentThreadOwns();
231 bool onCallingThread
= false;
232 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
233 NS_ASSERTION(onCallingThread
, "runEvent not running on the calling thread!");
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
;
243 AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData
&aData
,
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
);
264 // Set our error state.
268 (void)notifyError(error
);
272 // Advance our iterator, execute, and then process the statement.
274 bool lastStatement
= aLastStatement
&& itr
== end
;
275 continueProcessing
= executeAndProcessStatement(aStatement
, lastStatement
);
277 // Always reset our statement.
278 (void)::sqlite3_reset(aStatement
);
281 return continueProcessing
;
285 AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt
*aStatement
,
288 mMutex
.AssertNotCurrentThreadOwns();
290 // Execute our statement
293 hasResults
= executeStatement(aStatement
);
295 // If we had an error, bail.
299 // If we have been canceled, there is no point in going on...
301 MutexAutoLock
lockedScope(mMutex
);
302 if (mCancelRequested
) {
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.
315 // Notify, and stop processing statements.
316 (void)notifyError(mozIStorageError::ERROR
,
317 "An error occurred while notifying about results");
321 } while (hasResults
);
324 // Check to make sure that this statement was smart about what it did.
325 checkAndLogStatementPerformance(aStatement
);
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.
338 AsyncExecuteStatements::executeStatement(sqlite3_stmt
*aStatement
)
340 mMutex
.AssertNotCurrentThreadOwns();
341 Telemetry::AutoTimer
<Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_MS
> finallySendExecutionDuration(mRequestStartDate
);
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);
354 // If we got results, we can return now.
355 if (rc
== SQLITE_ROW
)
357 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS
, 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
);
371 // Set an error state.
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.
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.
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
) {
416 rv
= notifyResults();
418 return NS_OK
; // we'll try again with the next result
420 // Reset our start time
421 mIntervalStart
= now
;
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().
446 // Handle our transaction, if we have one
447 if (mHasTransaction
) {
448 if (mState
== COMPLETED
) {
449 nsresult rv
= mConnection
->commitTransactionInternal(mNativeConnection
);
452 (void)notifyError(mozIStorageError::ERROR
,
453 "Transaction failed to commit");
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).
472 (void)mCallingThread
->Dispatch(completionEvent
, NS_DISPATCH_NORMAL
);
478 AsyncExecuteStatements::notifyError(int32_t aErrorCode
,
479 const char *aMessage
)
481 mMutex
.AssertNotCurrentThreadOwns();
482 mDBMutex
.assertNotCurrentThreadOwns();
487 nsCOMPtr
<mozIStorageError
> errorObj(new Error(aErrorCode
, aMessage
));
488 NS_ENSURE_TRUE(errorObj
, NS_ERROR_OUT_OF_MEMORY
);
490 return notifyError(errorObj
);
494 AsyncExecuteStatements::notifyError(mozIStorageError
*aError
)
496 mMutex
.AssertNotCurrentThreadOwns();
497 mDBMutex
.assertNotCurrentThreadOwns();
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
);
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
526 AsyncExecuteStatements
,
528 mozIStoragePendingStatement
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) {
546 ////////////////////////////////////////////////////////////////////////////////
547 //// mozIStoragePendingStatement
550 AsyncExecuteStatements::Cancel()
553 bool onCallingThread
= false;
554 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
555 NS_ASSERTION(onCallingThread
, "Not canceling from the calling thread!");
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;
572 ////////////////////////////////////////////////////////////////////////////////
576 AsyncExecuteStatements::Run()
578 MOZ_ASSERT(!mConnection
->isClosed());
580 // Do not run if we have been canceled.
582 MutexAutoLock
lockedScope(mMutex
);
583 if (mCancelRequested
)
586 if (mState
== CANCELED
)
587 return notifyComplete();
589 if (statementsNeedTransaction()) {
590 if (NS_SUCCEEDED(mConnection
->beginTransactionInternal(mNativeConnection
,
591 mozIStorageConnection::TRANSACTION_IMMEDIATE
))) {
592 mHasTransaction
= true;
596 NS_WARNING("Unable to create a transaction for async execution.");
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));
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.
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
);
627 // If we have parameters to bind, bind them, execute, and process.
628 if (mStatements
[i
].hasParametersToBeBound()) {
629 if (!bindExecuteAndProcessStatement(mStatements
[i
], finished
))
632 // Otherwise, just execute and process the statement.
633 else if (!executeAndProcessStatement(stmt
, finished
)) {
638 // If we still have results that we haven't notified about, take care of
641 (void)notifyResults();
643 // Notify about completion
644 return notifyComplete();
647 } // namespace storage
648 } // namespace mozilla