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