storage/src/mozStorageAsyncStatementExecution.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial