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