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 41420 : {
88 : public:
89 10355 : CallbackResultNotifier(mozIStorageStatementCallback *aCallback,
90 : mozIStorageResultSet *aResults,
91 : AsyncExecuteStatements *aEventStatus) :
92 : mCallback(aCallback)
93 : , mResults(aResults)
94 10355 : , mEventStatus(aEventStatus)
95 : {
96 10355 : }
97 :
98 10355 : NS_IMETHOD Run()
99 : {
100 10355 : NS_ASSERTION(mCallback, "Trying to notify about results without a callback!");
101 :
102 10355 : 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 20420 : do_QueryInterface(mCallback);
108 :
109 10210 : (void)mCallback->HandleResult(mResults);
110 : }
111 :
112 10355 : 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 96 : {
126 : public:
127 24 : ErrorNotifier(mozIStorageStatementCallback *aCallback,
128 : mozIStorageError *aErrorObj,
129 : AsyncExecuteStatements *aEventStatus) :
130 : mCallback(aCallback)
131 : , mErrorObj(aErrorObj)
132 24 : , mEventStatus(aEventStatus)
133 : {
134 24 : }
135 :
136 24 : NS_IMETHOD Run()
137 : {
138 24 : 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 48 : do_QueryInterface(mCallback);
144 :
145 24 : (void)mCallback->HandleError(mErrorObj);
146 : }
147 :
148 24 : 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 171332 : {
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 42833 : CompletionNotifier(mozIStorageStatementCallback *aCallback,
170 : ExecutionState aReason,
171 : StatementDataArray &aStatementData)
172 : : mCallback(aCallback)
173 42833 : , mReason(aReason)
174 : {
175 42833 : mStatementData.SwapElements(aStatementData);
176 42833 : }
177 :
178 42833 : NS_IMETHOD Run()
179 : {
180 42833 : if (mCallback) {
181 41351 : (void)mCallback->HandleCompletion(mReason);
182 41351 : 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 42833 : mStatementData.Clear();
189 :
190 42833 : 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 42833 : 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 85666 : new AsyncExecuteStatements(aStatements, aConnection, aCallback);
214 42833 : NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
215 :
216 : // Dispatch it to the background
217 42833 : 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 42833 : MOZ_ASSERT(target);
225 42833 : if (!target) {
226 0 : return NS_ERROR_NOT_AVAILABLE;
227 : }
228 :
229 42833 : nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
230 42833 : NS_ENSURE_SUCCESS(rv, rv);
231 :
232 : // Return it as the pending statement object and track it.
233 42833 : NS_ADDREF(*_stmt = event);
234 42833 : return NS_OK;
235 : }
236 :
237 42833 : 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 42833 : , mRequestStartDate(TimeStamp::Now())
251 : {
252 42833 : (void)mStatements.SwapElements(aStatements);
253 42833 : NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
254 42833 : NS_IF_ADDREF(mCallback);
255 42833 : }
256 :
257 : bool
258 10379 : AsyncExecuteStatements::shouldNotify()
259 : {
260 : #ifdef DEBUG
261 10379 : mMutex.AssertNotCurrentThreadOwns();
262 :
263 10379 : bool onCallingThread = false;
264 10379 : (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
265 10379 : 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 10379 : return !mCancelRequested;
272 : }
273 :
274 : bool
275 49122 : AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData,
276 : bool aLastStatement)
277 : {
278 49122 : mMutex.AssertNotCurrentThreadOwns();
279 :
280 49122 : sqlite3_stmt *aStatement = nsnull;
281 : // This cannot fail; we are only called if it's available.
282 49122 : (void)aData.getSqliteStatement(&aStatement);
283 49122 : NS_ASSERTION(aStatement, "You broke the code; do not call here like that!");
284 49122 : BindingParamsArray *paramsArray(aData);
285 :
286 : // Iterate through all of our parameters, bind them, and execute.
287 49122 : bool continueProcessing = true;
288 49122 : BindingParamsArray::iterator itr = paramsArray->begin();
289 49122 : BindingParamsArray::iterator end = paramsArray->end();
290 147514 : while (itr != end && continueProcessing) {
291 : // Bind the data to our statement.
292 : nsCOMPtr<IStorageBindingParamsInternal> bindingInternal =
293 98552 : do_QueryInterface(*itr);
294 98552 : nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement);
295 49276 : 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 49270 : itr++;
306 49270 : bool lastStatement = aLastStatement && itr == end;
307 49270 : continueProcessing = executeAndProcessStatement(aStatement, lastStatement);
308 :
309 : // Always reset our statement.
310 98546 : (void)::sqlite3_reset(aStatement);
311 : }
312 :
313 49116 : return continueProcessing;
314 : }
315 :
316 : bool
317 54209 : AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement,
318 : bool aLastStatement)
319 : {
320 54209 : mMutex.AssertNotCurrentThreadOwns();
321 :
322 : // Execute our statement
323 : bool hasResults;
324 78891 : do {
325 78912 : hasResults = executeStatement(aStatement);
326 :
327 : // If we had an error, bail.
328 78912 : if (mState == ERROR)
329 16 : return false;
330 :
331 : // If we have been canceled, there is no point in going on...
332 : {
333 157792 : MutexAutoLock lockedScope(mMutex);
334 78896 : if (mCancelRequested) {
335 5 : mState = CANCELED;
336 5 : 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 103324 : if (mCallback && hasResults &&
343 24433 : 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 54188 : 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 54188 : if (aLastStatement)
364 42786 : mState = COMPLETED;
365 :
366 54188 : return true;
367 : }
368 :
369 : bool
370 78912 : AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement)
371 : {
372 78912 : mMutex.AssertNotCurrentThreadOwns();
373 157824 : 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 157824 : SQLiteMutexAutoLock lockedScope(mDBMutex);
377 :
378 78912 : int rc = mConnection->stepStatement(aStatement);
379 : // Stop if we have no more results.
380 78912 : if (rc == SQLITE_DONE)
381 : {
382 54188 : Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
383 54188 : return false;
384 : }
385 :
386 : // If we got results, we can return now.
387 24724 : if (rc == SQLITE_ROW)
388 : {
389 24708 : Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
390 24708 : return true;
391 : }
392 :
393 : // Some errors are not fatal, and we can handle them and continue.
394 16 : 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 16 : mState = ERROR;
405 16 : 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 16 : sqlite3 *db = mConnection->GetNativeConnection();
410 48 : nsCOMPtr<mozIStorageError> errorObj(new Error(rc, ::sqlite3_errmsg(db)));
411 : // We cannot hold the DB mutex while calling notifyError.
412 32 : SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
413 16 : (void)notifyError(errorObj);
414 :
415 : // Finally, indicate that we should stop processing.
416 16 : return false;
417 : }
418 : }
419 :
420 : nsresult
421 24433 : AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement)
422 : {
423 24433 : NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
424 24433 : mMutex.AssertNotCurrentThreadOwns();
425 :
426 : // Build result object if we need it.
427 24433 : if (!mResultSet)
428 10355 : mResultSet = new ResultSet();
429 24433 : NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
430 :
431 48866 : nsRefPtr<Row> row(new Row());
432 24433 : NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
433 :
434 24433 : nsresult rv = row->initialize(aStatement);
435 24433 : NS_ENSURE_SUCCESS(rv, rv);
436 :
437 24433 : rv = mResultSet->add(row);
438 24433 : 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 24433 : TimeStamp now = TimeStamp::Now();
444 24433 : TimeDuration delta = now - mIntervalStart;
445 24433 : if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) {
446 : // Notify the caller
447 891 : rv = notifyResults();
448 891 : if (NS_FAILED(rv))
449 0 : return NS_OK; // we'll try again with the next result
450 :
451 : // Reset our start time
452 891 : mIntervalStart = now;
453 : }
454 :
455 24433 : return NS_OK;
456 : }
457 :
458 : nsresult
459 42833 : AsyncExecuteStatements::notifyComplete()
460 : {
461 42833 : mMutex.AssertNotCurrentThreadOwns();
462 42833 : 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 96914 : for (PRUint32 i = 0; i < mStatements.Length(); i++)
469 54081 : mStatements[i].finalize();
470 :
471 : // Handle our transaction, if we have one
472 42833 : if (mTransactionManager) {
473 5138 : if (mState == COMPLETED) {
474 5137 : nsresult rv = mTransactionManager->Commit();
475 5137 : 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 5138 : delete mTransactionManager;
485 5138 : 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 85666 : new CompletionNotifier(mCallback, mState, mStatements);
492 42833 : NS_ASSERTION(mStatements.IsEmpty(),
493 : "Should have given up ownership of mStatements!");
494 :
495 : // We no longer own mCallback (the CompletionNotifier takes ownership).
496 42833 : mCallback = nsnull;
497 :
498 42833 : (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
499 :
500 42833 : 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 26 : AsyncExecuteStatements::notifyError(mozIStorageError *aError)
521 : {
522 26 : mMutex.AssertNotCurrentThreadOwns();
523 26 : mDBMutex.assertNotCurrentThreadOwns();
524 :
525 26 : if (!mCallback)
526 2 : return NS_OK;
527 :
528 : nsRefPtr<ErrorNotifier> notifier =
529 48 : new ErrorNotifier(mCallback, aError, this);
530 24 : NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
531 :
532 24 : return mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
533 : }
534 :
535 : nsresult
536 10355 : AsyncExecuteStatements::notifyResults()
537 : {
538 10355 : mMutex.AssertNotCurrentThreadOwns();
539 10355 : NS_ASSERTION(mCallback, "notifyResults called without a callback!");
540 :
541 : nsRefPtr<CallbackResultNotifier> notifier =
542 31065 : new CallbackResultNotifier(mCallback, mResultSet, this);
543 10355 : NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
544 :
545 10355 : nsresult rv = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
546 10355 : if (NS_SUCCEEDED(rv))
547 10355 : mResultSet = nsnull; // we no longer own it on success
548 10355 : return rv;
549 : }
550 :
551 771924 : NS_IMPL_THREADSAFE_ISUPPORTS2(
552 : AsyncExecuteStatements,
553 : nsIRunnable,
554 : mozIStoragePendingStatement
555 : )
556 :
557 : bool
558 42817 : 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 86311 : for (PRUint32 i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) {
564 48632 : transactionsCount += mStatements[i].needsTransaction();
565 48632 : if (transactionsCount > 1) {
566 5138 : return true;
567 : }
568 : }
569 37679 : return false;
570 : }
571 :
572 : ////////////////////////////////////////////////////////////////////////////////
573 : //// mozIStoragePendingStatement
574 :
575 : NS_IMETHODIMP
576 58 : AsyncExecuteStatements::Cancel()
577 : {
578 : #ifdef DEBUG
579 58 : bool onCallingThread = false;
580 58 : (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
581 58 : 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 58 : NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED);
587 :
588 : {
589 112 : MutexAutoLock lockedScope(mMutex);
590 :
591 : // We need to indicate that we want to try and cancel now.
592 56 : mCancelRequested = true;
593 : }
594 :
595 56 : return NS_OK;
596 : }
597 :
598 : ////////////////////////////////////////////////////////////////////////////////
599 : //// nsIRunnable
600 :
601 : NS_IMETHODIMP
602 42833 : AsyncExecuteStatements::Run()
603 : {
604 : // Do not run if we have been canceled.
605 : {
606 85666 : MutexAutoLock lockedScope(mMutex);
607 42833 : if (mCancelRequested)
608 16 : mState = CANCELED;
609 : }
610 42833 : if (mState == CANCELED)
611 16 : return notifyComplete();
612 :
613 42817 : if (statementsNeedTransaction()) {
614 : mTransactionManager = new mozStorageTransaction(mConnection, false,
615 10276 : mozIStorageConnection::TRANSACTION_IMMEDIATE);
616 : }
617 :
618 : // Execute each statement, giving the callback results if it returns any.
619 96851 : for (PRUint32 i = 0; i < mStatements.Length(); i++) {
620 54065 : bool finished = (i == (mStatements.Length() - 1));
621 :
622 : sqlite3_stmt *stmt;
623 : { // lock the sqlite mutex so sqlite3_errmsg cannot change
624 108130 : SQLiteMutexAutoLock lockedScope(mDBMutex);
625 :
626 54065 : int rc = mStatements[i].getSqliteStatement(&stmt);
627 54065 : 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 54061 : if (mStatements[i].hasParametersToBeBound()) {
647 49122 : if (!bindExecuteAndProcessStatement(mStatements[i], finished))
648 14 : break;
649 : }
650 : // Otherwise, just execute and process the statement.
651 4939 : else if (!executeAndProcessStatement(stmt, finished)) {
652 13 : break;
653 : }
654 : }
655 :
656 : // If we still have results that we haven't notified about, take care of
657 : // them now.
658 42817 : if (mResultSet)
659 9464 : (void)notifyResults();
660 :
661 : // Notify about completion
662 42817 : return notifyComplete();
663 : }
664 :
665 : } // namespace storage
666 : } // namespace mozilla
|