Sat, 03 Jan 2015 20:18:00 +0100
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 |