Bug 628922 - layout should use cached nsIAccessibilityService, r=davidb, sr=roc,...
[mozilla-central.git] / storage / src / mozStorageAsyncStatementExecution.cpp
blobdf354aad514bfd6aceb8903f3d9af19e5e1b0736
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 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is mozilla.org code.
18 * The Initial Developer of the Original Code is
19 * Mozilla Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 2008
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Shawn Wilsher <me@shawnwilsher.com> (Original Author)
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsAutoPtr.h"
41 #include "prtime.h"
43 #include "sqlite3.h"
45 #include "mozIStorageStatementCallback.h"
46 #include "mozStorageBindingParams.h"
47 #include "mozStorageHelper.h"
48 #include "mozStorageResultSet.h"
49 #include "mozStorageRow.h"
50 #include "mozStorageConnection.h"
51 #include "mozStorageError.h"
52 #include "mozStoragePrivateHelpers.h"
53 #include "mozStorageStatementData.h"
54 #include "mozStorageAsyncStatementExecution.h"
56 namespace mozilla {
57 namespace storage {
59 /**
60 * The following constants help batch rows into result sets.
61 * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that
62 * takes less than 200 milliseconds is considered to feel instantaneous to end
63 * users. MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of
64 * dispatches to calling thread, while also providing reasonably-sized sets of
65 * data for consumers. Both of these constants are used because we assume that
66 * consumers are trying to avoid blocking their execution thread for long
67 * periods of time, and dispatching many small events to the calling thread will
68 * end up blocking it.
70 #define MAX_MILLISECONDS_BETWEEN_RESULTS 75
71 #define MAX_ROWS_PER_RESULT 15
73 ////////////////////////////////////////////////////////////////////////////////
74 //// Local Classes
76 namespace {
78 typedef AsyncExecuteStatements::ExecutionState ExecutionState;
80 /**
81 * Notifies a callback with a result set.
83 class CallbackResultNotifier : public nsRunnable
85 public:
86 CallbackResultNotifier(mozIStorageStatementCallback *aCallback,
87 mozIStorageResultSet *aResults,
88 AsyncExecuteStatements *aEventStatus) :
89 mCallback(aCallback)
90 , mResults(aResults)
91 , mEventStatus(aEventStatus)
95 NS_IMETHOD Run()
97 NS_ASSERTION(mCallback, "Trying to notify about results without a callback!");
99 if (mEventStatus->shouldNotify())
100 (void)mCallback->HandleResult(mResults);
102 return NS_OK;
105 private:
106 mozIStorageStatementCallback *mCallback;
107 nsCOMPtr<mozIStorageResultSet> mResults;
108 nsRefPtr<AsyncExecuteStatements> mEventStatus;
112 * Notifies the calling thread that an error has occurred.
114 class ErrorNotifier : public nsRunnable
116 public:
117 ErrorNotifier(mozIStorageStatementCallback *aCallback,
118 mozIStorageError *aErrorObj,
119 AsyncExecuteStatements *aEventStatus) :
120 mCallback(aCallback)
121 , mErrorObj(aErrorObj)
122 , mEventStatus(aEventStatus)
126 NS_IMETHOD Run()
128 if (mEventStatus->shouldNotify() && mCallback)
129 (void)mCallback->HandleError(mErrorObj);
131 return NS_OK;
134 private:
135 mozIStorageStatementCallback *mCallback;
136 nsCOMPtr<mozIStorageError> mErrorObj;
137 nsRefPtr<AsyncExecuteStatements> mEventStatus;
141 * Notifies the calling thread that the statement has finished executing. Keeps
142 * the AsyncExecuteStatements instance alive long enough so that it does not
143 * get destroyed on the async thread if there are no other references alive.
145 class CompletionNotifier : public nsRunnable
147 public:
149 * This takes ownership of the callback. It is released on the thread this is
150 * dispatched to (which should always be the calling thread).
152 CompletionNotifier(mozIStorageStatementCallback *aCallback,
153 ExecutionState aReason,
154 AsyncExecuteStatements *aKeepAsyncAlive)
155 : mKeepAsyncAlive(aKeepAsyncAlive)
156 , mCallback(aCallback)
157 , mReason(aReason)
161 NS_IMETHOD Run()
163 if (mCallback) {
164 (void)mCallback->HandleCompletion(mReason);
165 NS_RELEASE(mCallback);
168 return NS_OK;
171 private:
172 nsRefPtr<AsyncExecuteStatements> mKeepAsyncAlive;
173 mozIStorageStatementCallback *mCallback;
174 ExecutionState mReason;
177 } // anonymous namespace
179 ////////////////////////////////////////////////////////////////////////////////
180 //// AsyncExecuteStatements
182 /* static */
183 nsresult
184 AsyncExecuteStatements::execute(StatementDataArray &aStatements,
185 Connection *aConnection,
186 mozIStorageStatementCallback *aCallback,
187 mozIStoragePendingStatement **_stmt)
189 // Create our event to run in the background
190 nsRefPtr<AsyncExecuteStatements> event =
191 new AsyncExecuteStatements(aStatements, aConnection, aCallback);
192 NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
194 // Dispatch it to the background
195 nsIEventTarget *target = aConnection->getAsyncExecutionTarget();
196 NS_ENSURE_TRUE(target, NS_ERROR_NOT_AVAILABLE);
197 nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
198 NS_ENSURE_SUCCESS(rv, rv);
200 // Return it as the pending statement object and track it.
201 NS_ADDREF(*_stmt = event);
202 return NS_OK;
205 AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements,
206 Connection *aConnection,
207 mozIStorageStatementCallback *aCallback)
208 : mConnection(aConnection)
209 , mTransactionManager(nsnull)
210 , mCallback(aCallback)
211 , mCallingThread(::do_GetCurrentThread())
212 , mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS))
213 , mIntervalStart(TimeStamp::Now())
214 , mState(PENDING)
215 , mCancelRequested(false)
216 , mMutex(aConnection->sharedAsyncExecutionMutex)
217 , mDBMutex(aConnection->sharedDBMutex)
219 (void)mStatements.SwapElements(aStatements);
220 NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
221 NS_IF_ADDREF(mCallback);
224 bool
225 AsyncExecuteStatements::shouldNotify()
227 #ifdef DEBUG
228 mMutex.AssertNotCurrentThreadOwns();
230 PRBool onCallingThread = PR_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;
241 bool
242 AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData,
243 bool aLastStatement)
245 mMutex.AssertNotCurrentThreadOwns();
247 sqlite3_stmt *aStatement = nsnull;
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;
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);
280 return continueProcessing;
283 bool
284 AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement,
285 bool aLastStatement)
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...
300 MutexAutoLock lockedScope(mMutex);
301 if (mCancelRequested) {
302 mState = CANCELED;
303 return false;
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;
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;
336 bool
337 AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement)
339 mMutex.AssertNotCurrentThreadOwns();
341 while (true) {
342 // lock the sqlite mutex so sqlite3_errmsg cannot change
343 SQLiteMutexAutoLock lockedScope(mDBMutex);
345 int rc = stepStmt(aStatement);
346 // Stop if we have no more results.
347 if (rc == SQLITE_DONE)
348 return false;
350 // If we got results, we can return now.
351 if (rc == SQLITE_ROW)
352 return true;
354 // Some errors are not fatal, and we can handle them and continue.
355 if (rc == SQLITE_BUSY) {
356 // Don't hold the lock while we call outside our module.
357 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
359 // Yield, and try again
360 (void)::PR_Sleep(PR_INTERVAL_NO_WAIT);
361 continue;
364 // Set an error state.
365 mState = ERROR;
367 // Construct the error message before giving up the mutex (which we cannot
368 // hold during the call to notifyError).
369 sqlite3 *db = mConnection->GetNativeConnection();
370 nsCOMPtr<mozIStorageError> errorObj(new Error(rc, ::sqlite3_errmsg(db)));
371 // We cannot hold the DB mutex while calling notifyError.
372 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
373 (void)notifyError(errorObj);
375 // Finally, indicate that we should stop processing.
376 return false;
380 nsresult
381 AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement)
383 NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
384 mMutex.AssertNotCurrentThreadOwns();
386 // Build result object if we need it.
387 if (!mResultSet)
388 mResultSet = new ResultSet();
389 NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
391 nsRefPtr<Row> row(new Row());
392 NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
394 nsresult rv = row->initialize(aStatement);
395 NS_ENSURE_SUCCESS(rv, rv);
397 rv = mResultSet->add(row);
398 NS_ENSURE_SUCCESS(rv, rv);
400 // If we have hit our maximum number of allowed results, or if we have hit
401 // the maximum amount of time we want to wait for results, notify the
402 // calling thread about it.
403 TimeStamp now = TimeStamp::Now();
404 TimeDuration delta = now - mIntervalStart;
405 if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) {
406 // Notify the caller
407 rv = notifyResults();
408 if (NS_FAILED(rv))
409 return NS_OK; // we'll try again with the next result
411 // Reset our start time
412 mIntervalStart = now;
415 return NS_OK;
418 nsresult
419 AsyncExecuteStatements::notifyComplete()
421 mMutex.AssertNotCurrentThreadOwns();
422 NS_ASSERTION(mState != PENDING,
423 "Still in a pending state when calling Complete!");
425 // Finalize our statements before we try to commit or rollback. If we are
426 // canceling and have statements that think they have pending work, the
427 // rollback will fail.
428 for (PRUint32 i = 0; i < mStatements.Length(); i++)
429 mStatements[i].finalize();
431 // Handle our transaction, if we have one
432 if (mTransactionManager) {
433 if (mState == COMPLETED) {
434 nsresult rv = mTransactionManager->Commit();
435 if (NS_FAILED(rv)) {
436 mState = ERROR;
437 (void)notifyError(mozIStorageError::ERROR,
438 "Transaction failed to commit");
441 else {
442 (void)mTransactionManager->Rollback();
444 delete mTransactionManager;
445 mTransactionManager = nsnull;
448 // Always generate a completion notification; it is what guarantees that our
449 // destruction does not happen here on the async thread.
450 nsRefPtr<CompletionNotifier> completionEvent =
451 new CompletionNotifier(mCallback, mState, this);
452 NS_ENSURE_TRUE(completionEvent, NS_ERROR_OUT_OF_MEMORY);
454 // We no longer own mCallback (the CompletionNotifier takes ownership).
455 mCallback = nsnull;
457 (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
459 return NS_OK;
462 nsresult
463 AsyncExecuteStatements::notifyError(PRInt32 aErrorCode,
464 const char *aMessage)
466 mMutex.AssertNotCurrentThreadOwns();
467 mDBMutex.assertNotCurrentThreadOwns();
469 if (!mCallback)
470 return NS_OK;
472 nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
473 NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
475 return notifyError(errorObj);
478 nsresult
479 AsyncExecuteStatements::notifyError(mozIStorageError *aError)
481 mMutex.AssertNotCurrentThreadOwns();
482 mDBMutex.assertNotCurrentThreadOwns();
484 if (!mCallback)
485 return NS_OK;
487 nsRefPtr<ErrorNotifier> notifier =
488 new ErrorNotifier(mCallback, aError, this);
489 NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
491 return mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
494 nsresult
495 AsyncExecuteStatements::notifyResults()
497 mMutex.AssertNotCurrentThreadOwns();
498 NS_ASSERTION(mCallback, "notifyResults called without a callback!");
500 nsRefPtr<CallbackResultNotifier> notifier =
501 new CallbackResultNotifier(mCallback, mResultSet, this);
502 NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
504 nsresult rv = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
505 if (NS_SUCCEEDED(rv))
506 mResultSet = nsnull; // we no longer own it on success
507 return rv;
510 NS_IMPL_THREADSAFE_ISUPPORTS2(
511 AsyncExecuteStatements,
512 nsIRunnable,
513 mozIStoragePendingStatement
516 ////////////////////////////////////////////////////////////////////////////////
517 //// mozIStoragePendingStatement
519 NS_IMETHODIMP
520 AsyncExecuteStatements::Cancel()
522 #ifdef DEBUG
523 PRBool onCallingThread = PR_FALSE;
524 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
525 NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!");
526 #endif
528 // If we have already canceled, we have an error, but always indicate that
529 // we are trying to cancel.
530 NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED);
533 MutexAutoLock lockedScope(mMutex);
535 // We need to indicate that we want to try and cancel now.
536 mCancelRequested = true;
539 return NS_OK;
542 ////////////////////////////////////////////////////////////////////////////////
543 //// nsIRunnable
545 NS_IMETHODIMP
546 AsyncExecuteStatements::Run()
548 // Do not run if we have been canceled.
550 MutexAutoLock lockedScope(mMutex);
551 if (mCancelRequested)
552 mState = CANCELED;
554 if (mState == CANCELED)
555 return notifyComplete();
557 // If there is more than one statement, run it in a transaction. We assume
558 // that we have been given write statements since getting a batch of read
559 // statements doesn't make a whole lot of sense.
560 // Additionally, if we have only one statement and it needs a transaction, we
561 // will wrap it in one.
562 if (mStatements.Length() > 1 || mStatements[0].needsTransaction()) {
563 // We don't error if this failed because it's not terrible if it does.
564 mTransactionManager = new mozStorageTransaction(mConnection, PR_FALSE,
565 mozIStorageConnection::TRANSACTION_IMMEDIATE);
568 // Execute each statement, giving the callback results if it returns any.
569 for (PRUint32 i = 0; i < mStatements.Length(); i++) {
570 bool finished = (i == (mStatements.Length() - 1));
572 sqlite3_stmt *stmt;
573 { // lock the sqlite mutex so sqlite3_errmsg cannot change
574 SQLiteMutexAutoLock lockedScope(mDBMutex);
576 int rc = mStatements[i].getSqliteStatement(&stmt);
577 if (rc != SQLITE_OK) {
578 // Set our error state.
579 mState = ERROR;
581 // Build the error object; can't call notifyError with the lock held
582 sqlite3 *db = mConnection->GetNativeConnection();
583 nsCOMPtr<mozIStorageError> errorObj(
584 new Error(rc, ::sqlite3_errmsg(db))
587 // We cannot hold the DB mutex and call notifyError.
588 SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
589 (void)notifyError(errorObj);
591 break;
595 // If we have parameters to bind, bind them, execute, and process.
596 if (mStatements[i].hasParametersToBeBound()) {
597 if (!bindExecuteAndProcessStatement(mStatements[i], finished))
598 break;
600 // Otherwise, just execute and process the statement.
601 else if (!executeAndProcessStatement(stmt, finished)) {
602 break;
606 // If we still have results that we haven't notified about, take care of
607 // them now.
608 if (mResultSet)
609 (void)notifyResults();
611 // Notify about completion
612 return notifyComplete();
615 } // namespace storage
616 } // namespace mozilla