Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/Telemetry.h"
24 namespace mozilla {
25 namespace storage {
27 /**
28 * The following constants help batch rows into result sets.
29 * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that
30 * takes less than 200 milliseconds is considered to feel instantaneous to end
31 * users. MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of
32 * dispatches to calling thread, while also providing reasonably-sized sets of
33 * data for consumers. Both of these constants are used because we assume that
34 * consumers are trying to avoid blocking their execution thread for long
35 * periods of time, and dispatching many small events to the calling thread will
36 * end up blocking it.
37 */
38 #define MAX_MILLISECONDS_BETWEEN_RESULTS 75
39 #define MAX_ROWS_PER_RESULT 15
41 ////////////////////////////////////////////////////////////////////////////////
42 //// Local Classes
44 namespace {
46 typedef AsyncExecuteStatements::ExecutionState ExecutionState;
47 typedef AsyncExecuteStatements::StatementDataArray StatementDataArray;
49 /**
50 * Notifies a callback with a result set.
51 */
52 class CallbackResultNotifier : public nsRunnable
53 {
54 public:
55 CallbackResultNotifier(mozIStorageStatementCallback *aCallback,
56 mozIStorageResultSet *aResults,
57 AsyncExecuteStatements *aEventStatus) :
58 mCallback(aCallback)
59 , mResults(aResults)
60 , mEventStatus(aEventStatus)
61 {
62 }
64 NS_IMETHOD Run()
65 {
66 NS_ASSERTION(mCallback, "Trying to notify about results without a callback!");
68 if (mEventStatus->shouldNotify()) {
69 // Hold a strong reference to the callback while notifying it, so that if
70 // it spins the event loop, the callback won't be released and freed out
71 // from under us.
72 nsCOMPtr<mozIStorageStatementCallback> callback =
73 do_QueryInterface(mCallback);
75 (void)mCallback->HandleResult(mResults);
76 }
78 return NS_OK;
79 }
81 private:
82 mozIStorageStatementCallback *mCallback;
83 nsCOMPtr<mozIStorageResultSet> mResults;
84 nsRefPtr<AsyncExecuteStatements> mEventStatus;
85 };
87 /**
88 * Notifies the calling thread that an error has occurred.
89 */
90 class ErrorNotifier : public nsRunnable
91 {
92 public:
93 ErrorNotifier(mozIStorageStatementCallback *aCallback,
94 mozIStorageError *aErrorObj,
95 AsyncExecuteStatements *aEventStatus) :
96 mCallback(aCallback)
97 , mErrorObj(aErrorObj)
98 , mEventStatus(aEventStatus)
99 {
100 }
102 NS_IMETHOD Run()
103 {
104 if (mEventStatus->shouldNotify() && mCallback) {
105 // Hold a strong reference to the callback while notifying it, so that if
106 // it spins the event loop, the callback won't be released and freed out
107 // from under us.
108 nsCOMPtr<mozIStorageStatementCallback> callback =
109 do_QueryInterface(mCallback);
111 (void)mCallback->HandleError(mErrorObj);
112 }
114 return NS_OK;
115 }
117 private:
118 mozIStorageStatementCallback *mCallback;
119 nsCOMPtr<mozIStorageError> mErrorObj;
120 nsRefPtr<AsyncExecuteStatements> mEventStatus;
121 };
123 /**
124 * Notifies the calling thread that the statement has finished executing. Takes
125 * ownership of the StatementData so it is released on the proper thread.
126 */
127 class CompletionNotifier : public nsRunnable
128 {
129 public:
130 /**
131 * This takes ownership of the callback and the StatementData. They are
132 * released on the thread this is dispatched to (which should always be the
133 * calling thread).
134 */
135 CompletionNotifier(mozIStorageStatementCallback *aCallback,
136 ExecutionState aReason)
137 : mCallback(aCallback)
138 , mReason(aReason)
139 {
140 }
142 NS_IMETHOD Run()
143 {
144 if (mCallback) {
145 (void)mCallback->HandleCompletion(mReason);
146 NS_RELEASE(mCallback);
147 }
149 return NS_OK;
150 }
152 private:
153 mozIStorageStatementCallback *mCallback;
154 ExecutionState mReason;
155 };
157 } // anonymous namespace
159 ////////////////////////////////////////////////////////////////////////////////
160 //// AsyncExecuteStatements
162 /* static */
163 nsresult
164 AsyncExecuteStatements::execute(StatementDataArray &aStatements,
165 Connection *aConnection,
166 sqlite3 *aNativeConnection,
167 mozIStorageStatementCallback *aCallback,
168 mozIStoragePendingStatement **_stmt)
169 {
170 // Create our event to run in the background
171 nsRefPtr<AsyncExecuteStatements> event =
172 new AsyncExecuteStatements(aStatements, aConnection, aNativeConnection,
173 aCallback);
174 NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
176 // Dispatch it to the background
177 nsIEventTarget *target = aConnection->getAsyncExecutionTarget();
179 // If we don't have a valid target, this is a bug somewhere else. In the past,
180 // this assert found cases where a Run method would schedule a new statement
181 // without checking if asyncClose had been called. The caller must prevent
182 // that from happening or, if the work is not critical, just avoid creating
183 // the new statement during shutdown. See bug 718449 for an example.
184 MOZ_ASSERT(target);
185 if (!target) {
186 return NS_ERROR_NOT_AVAILABLE;
187 }
189 nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
190 NS_ENSURE_SUCCESS(rv, rv);
192 // Return it as the pending statement object and track it.
193 NS_ADDREF(*_stmt = event);
194 return NS_OK;
195 }
197 AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements,
198 Connection *aConnection,
199 sqlite3 *aNativeConnection,
200 mozIStorageStatementCallback *aCallback)
201 : mConnection(aConnection)
202 , mNativeConnection(aNativeConnection)
203 , mHasTransaction(false)
204 , mCallback(aCallback)
205 , mCallingThread(::do_GetCurrentThread())
206 , mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS))
207 , mIntervalStart(TimeStamp::Now())
208 , mState(PENDING)
209 , mCancelRequested(false)
210 , mMutex(aConnection->sharedAsyncExecutionMutex)
211 , mDBMutex(aConnection->sharedDBMutex)
212 , mRequestStartDate(TimeStamp::Now())
213 {
214 (void)mStatements.SwapElements(aStatements);
215 NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
216 NS_IF_ADDREF(mCallback);
217 }
219 AsyncExecuteStatements::~AsyncExecuteStatements()
220 {
221 MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point");
222 }
224 bool
225 AsyncExecuteStatements::shouldNotify()
226 {
227 #ifdef DEBUG
228 mMutex.AssertNotCurrentThreadOwns();
230 bool onCallingThread = false;
231 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
232 NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
233 #endif
235 // We do not need to acquire mMutex here because it can only ever be written
236 // to on the calling thread, and the only thread that can call us is the
237 // calling thread, so we know that our access is serialized.
238 return !mCancelRequested;
239 }
241 bool
242 AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData,
243 bool aLastStatement)
244 {
245 mMutex.AssertNotCurrentThreadOwns();
247 sqlite3_stmt *aStatement = nullptr;
248 // This cannot fail; we are only called if it's available.
249 (void)aData.getSqliteStatement(&aStatement);
250 NS_ASSERTION(aStatement, "You broke the code; do not call here like that!");
251 BindingParamsArray *paramsArray(aData);
253 // Iterate through all of our parameters, bind them, and execute.
254 bool continueProcessing = true;
255 BindingParamsArray::iterator itr = paramsArray->begin();
256 BindingParamsArray::iterator end = paramsArray->end();
257 while (itr != end && continueProcessing) {
258 // Bind the data to our statement.
259 nsCOMPtr<IStorageBindingParamsInternal> bindingInternal =
260 do_QueryInterface(*itr);
261 nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement);
262 if (error) {
263 // Set our error state.
264 mState = ERROR;
266 // And notify.
267 (void)notifyError(error);
268 return false;
269 }
271 // Advance our iterator, execute, and then process the statement.
272 itr++;
273 bool lastStatement = aLastStatement && itr == end;
274 continueProcessing = executeAndProcessStatement(aStatement, lastStatement);
276 // Always reset our statement.
277 (void)::sqlite3_reset(aStatement);
278 }
280 return continueProcessing;
281 }
283 bool
284 AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement,
285 bool aLastStatement)
286 {
287 mMutex.AssertNotCurrentThreadOwns();
289 // Execute our statement
290 bool hasResults;
291 do {
292 hasResults = executeStatement(aStatement);
294 // If we had an error, bail.
295 if (mState == ERROR)
296 return false;
298 // If we have been canceled, there is no point in going on...
299 {
300 MutexAutoLock lockedScope(mMutex);
301 if (mCancelRequested) {
302 mState = CANCELED;
303 return false;
304 }
305 }
307 // Build our result set and notify if we got anything back and have a
308 // callback to notify.
309 if (mCallback && hasResults &&
310 NS_FAILED(buildAndNotifyResults(aStatement))) {
311 // We had an error notifying, so we notify on error and stop processing.
312 mState = ERROR;
314 // Notify, and stop processing statements.
315 (void)notifyError(mozIStorageError::ERROR,
316 "An error occurred while notifying about results");
318 return false;
319 }
320 } while (hasResults);
322 #ifdef DEBUG
323 // Check to make sure that this statement was smart about what it did.
324 checkAndLogStatementPerformance(aStatement);
325 #endif
327 // If we are done, we need to set our state accordingly while we still hold
328 // our mutex. We would have already returned if we were canceled or had
329 // an error at this point.
330 if (aLastStatement)
331 mState = COMPLETED;
333 return true;
334 }
336 bool
337 AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement)
338 {
339 mMutex.AssertNotCurrentThreadOwns();
340 Telemetry::AutoTimer<Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_MS> finallySendExecutionDuration(mRequestStartDate);
341 while (true) {
342 // lock the sqlite mutex so sqlite3_errmsg cannot change
343 SQLiteMutexAutoLock lockedScope(mDBMutex);
345 int rc = mConnection->stepStatement(mNativeConnection, aStatement);
346 // Stop if we have no more results.
347 if (rc == SQLITE_DONE)
348 {
349 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
350 return false;
351 }
353 // If we got results, we can return now.
354 if (rc == SQLITE_ROW)
355 {
356 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
357 return true;
358 }
360 // Some errors are not fatal, and we can handle them and continue.
361 if (rc == SQLITE_BUSY) {
362 // Don't hold the lock while we call outside our module.
363 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
365 // Yield, and try again
366 (void)::PR_Sleep(PR_INTERVAL_NO_WAIT);
367 continue;
368 }
370 // Set an error state.
371 mState = ERROR;
372 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, false);
374 // Construct the error message before giving up the mutex (which we cannot
375 // hold during the call to notifyError).
376 nsCOMPtr<mozIStorageError> errorObj(
377 new Error(rc, ::sqlite3_errmsg(mNativeConnection))
378 );
379 // We cannot hold the DB mutex while calling notifyError.
380 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
381 (void)notifyError(errorObj);
383 // Finally, indicate that we should stop processing.
384 return false;
385 }
386 }
388 nsresult
389 AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement)
390 {
391 NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
392 mMutex.AssertNotCurrentThreadOwns();
394 // Build result object if we need it.
395 if (!mResultSet)
396 mResultSet = new ResultSet();
397 NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
399 nsRefPtr<Row> row(new Row());
400 NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
402 nsresult rv = row->initialize(aStatement);
403 NS_ENSURE_SUCCESS(rv, rv);
405 rv = mResultSet->add(row);
406 NS_ENSURE_SUCCESS(rv, rv);
408 // If we have hit our maximum number of allowed results, or if we have hit
409 // the maximum amount of time we want to wait for results, notify the
410 // calling thread about it.
411 TimeStamp now = TimeStamp::Now();
412 TimeDuration delta = now - mIntervalStart;
413 if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) {
414 // Notify the caller
415 rv = notifyResults();
416 if (NS_FAILED(rv))
417 return NS_OK; // we'll try again with the next result
419 // Reset our start time
420 mIntervalStart = now;
421 }
423 return NS_OK;
424 }
426 nsresult
427 AsyncExecuteStatements::notifyComplete()
428 {
429 mMutex.AssertNotCurrentThreadOwns();
430 NS_ASSERTION(mState != PENDING,
431 "Still in a pending state when calling Complete!");
433 // Reset our statements before we try to commit or rollback. If we are
434 // canceling and have statements that think they have pending work, the
435 // rollback will fail.
436 for (uint32_t i = 0; i < mStatements.Length(); i++)
437 mStatements[i].reset();
439 // Release references to the statement data as soon as possible. If this
440 // is the last reference, statements will be finalized immediately on the
441 // async thread, hence avoiding several bounces between threads and possible
442 // race conditions with AsyncClose().
443 mStatements.Clear();
445 // Handle our transaction, if we have one
446 if (mHasTransaction) {
447 if (mState == COMPLETED) {
448 nsresult rv = mConnection->commitTransactionInternal(mNativeConnection);
449 if (NS_FAILED(rv)) {
450 mState = ERROR;
451 (void)notifyError(mozIStorageError::ERROR,
452 "Transaction failed to commit");
453 }
454 }
455 else {
456 NS_WARN_IF(NS_FAILED(mConnection->rollbackTransactionInternal(mNativeConnection)));
457 }
458 mHasTransaction = false;
459 }
461 // Always generate a completion notification; it is what guarantees that our
462 // destruction does not happen here on the async thread.
463 nsRefPtr<CompletionNotifier> completionEvent =
464 new CompletionNotifier(mCallback, mState);
466 // We no longer own mCallback (the CompletionNotifier takes ownership).
467 mCallback = nullptr;
469 (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
471 return NS_OK;
472 }
474 nsresult
475 AsyncExecuteStatements::notifyError(int32_t aErrorCode,
476 const char *aMessage)
477 {
478 mMutex.AssertNotCurrentThreadOwns();
479 mDBMutex.assertNotCurrentThreadOwns();
481 if (!mCallback)
482 return NS_OK;
484 nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
485 NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
487 return notifyError(errorObj);
488 }
490 nsresult
491 AsyncExecuteStatements::notifyError(mozIStorageError *aError)
492 {
493 mMutex.AssertNotCurrentThreadOwns();
494 mDBMutex.assertNotCurrentThreadOwns();
496 if (!mCallback)
497 return NS_OK;
499 nsRefPtr<ErrorNotifier> notifier =
500 new ErrorNotifier(mCallback, aError, this);
501 NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
503 return mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
504 }
506 nsresult
507 AsyncExecuteStatements::notifyResults()
508 {
509 mMutex.AssertNotCurrentThreadOwns();
510 NS_ASSERTION(mCallback, "notifyResults called without a callback!");
512 nsRefPtr<CallbackResultNotifier> notifier =
513 new CallbackResultNotifier(mCallback, mResultSet, this);
514 NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
516 nsresult rv = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
517 if (NS_SUCCEEDED(rv))
518 mResultSet = nullptr; // we no longer own it on success
519 return rv;
520 }
522 NS_IMPL_ISUPPORTS(
523 AsyncExecuteStatements,
524 nsIRunnable,
525 mozIStoragePendingStatement
526 )
528 bool
529 AsyncExecuteStatements::statementsNeedTransaction()
530 {
531 // If there is more than one write statement, run in a transaction.
532 // Additionally, if we have only one statement but it needs a transaction, due
533 // to multiple BindingParams, we will wrap it in one.
534 for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) {
535 transactionsCount += mStatements[i].needsTransaction();
536 if (transactionsCount > 1) {
537 return true;
538 }
539 }
540 return false;
541 }
543 ////////////////////////////////////////////////////////////////////////////////
544 //// mozIStoragePendingStatement
546 NS_IMETHODIMP
547 AsyncExecuteStatements::Cancel()
548 {
549 #ifdef DEBUG
550 bool onCallingThread = false;
551 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
552 NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!");
553 #endif
555 // If we have already canceled, we have an error, but always indicate that
556 // we are trying to cancel.
557 NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED);
559 {
560 MutexAutoLock lockedScope(mMutex);
562 // We need to indicate that we want to try and cancel now.
563 mCancelRequested = true;
564 }
566 return NS_OK;
567 }
569 ////////////////////////////////////////////////////////////////////////////////
570 //// nsIRunnable
572 NS_IMETHODIMP
573 AsyncExecuteStatements::Run()
574 {
575 MOZ_ASSERT(!mConnection->isClosed());
577 // Do not run if we have been canceled.
578 {
579 MutexAutoLock lockedScope(mMutex);
580 if (mCancelRequested)
581 mState = CANCELED;
582 }
583 if (mState == CANCELED)
584 return notifyComplete();
586 if (statementsNeedTransaction()) {
587 Connection* rawConnection = static_cast<Connection*>(mConnection.get());
588 if (NS_SUCCEEDED(mConnection->beginTransactionInternal(mNativeConnection,
589 mozIStorageConnection::TRANSACTION_IMMEDIATE))) {
590 mHasTransaction = true;
591 }
592 #ifdef DEBUG
593 else {
594 NS_WARNING("Unable to create a transaction for async execution.");
595 }
596 #endif
597 }
599 // Execute each statement, giving the callback results if it returns any.
600 for (uint32_t i = 0; i < mStatements.Length(); i++) {
601 bool finished = (i == (mStatements.Length() - 1));
603 sqlite3_stmt *stmt;
604 { // lock the sqlite mutex so sqlite3_errmsg cannot change
605 SQLiteMutexAutoLock lockedScope(mDBMutex);
607 int rc = mStatements[i].getSqliteStatement(&stmt);
608 if (rc != SQLITE_OK) {
609 // Set our error state.
610 mState = ERROR;
612 // Build the error object; can't call notifyError with the lock held
613 nsCOMPtr<mozIStorageError> errorObj(
614 new Error(rc, ::sqlite3_errmsg(mNativeConnection))
615 );
616 {
617 // We cannot hold the DB mutex and call notifyError.
618 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
619 (void)notifyError(errorObj);
620 }
621 break;
622 }
623 }
625 // If we have parameters to bind, bind them, execute, and process.
626 if (mStatements[i].hasParametersToBeBound()) {
627 if (!bindExecuteAndProcessStatement(mStatements[i], finished))
628 break;
629 }
630 // Otherwise, just execute and process the statement.
631 else if (!executeAndProcessStatement(stmt, finished)) {
632 break;
633 }
634 }
636 // If we still have results that we haven't notified about, take care of
637 // them now.
638 if (mResultSet)
639 (void)notifyResults();
641 // Notify about completion
642 return notifyComplete();
643 }
645 } // namespace storage
646 } // namespace mozilla