|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
2 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "nsAutoPtr.h" |
|
8 |
|
9 #include "sqlite3.h" |
|
10 |
|
11 #include "mozIStorageStatementCallback.h" |
|
12 #include "mozStorageBindingParams.h" |
|
13 #include "mozStorageHelper.h" |
|
14 #include "mozStorageResultSet.h" |
|
15 #include "mozStorageRow.h" |
|
16 #include "mozStorageConnection.h" |
|
17 #include "mozStorageError.h" |
|
18 #include "mozStoragePrivateHelpers.h" |
|
19 #include "mozStorageStatementData.h" |
|
20 #include "mozStorageAsyncStatementExecution.h" |
|
21 |
|
22 #include "mozilla/Telemetry.h" |
|
23 |
|
24 namespace mozilla { |
|
25 namespace storage { |
|
26 |
|
27 /** |
|
28 * The following constants help batch rows into result sets. |
|
29 * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that |
|
30 * takes less than 200 milliseconds is considered to feel instantaneous to end |
|
31 * users. MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of |
|
32 * dispatches to calling thread, while also providing reasonably-sized sets of |
|
33 * data for consumers. Both of these constants are used because we assume that |
|
34 * consumers are trying to avoid blocking their execution thread for long |
|
35 * periods of time, and dispatching many small events to the calling thread will |
|
36 * end up blocking it. |
|
37 */ |
|
38 #define MAX_MILLISECONDS_BETWEEN_RESULTS 75 |
|
39 #define MAX_ROWS_PER_RESULT 15 |
|
40 |
|
41 //////////////////////////////////////////////////////////////////////////////// |
|
42 //// Local Classes |
|
43 |
|
44 namespace { |
|
45 |
|
46 typedef AsyncExecuteStatements::ExecutionState ExecutionState; |
|
47 typedef AsyncExecuteStatements::StatementDataArray StatementDataArray; |
|
48 |
|
49 /** |
|
50 * Notifies a callback with a result set. |
|
51 */ |
|
52 class CallbackResultNotifier : public nsRunnable |
|
53 { |
|
54 public: |
|
55 CallbackResultNotifier(mozIStorageStatementCallback *aCallback, |
|
56 mozIStorageResultSet *aResults, |
|
57 AsyncExecuteStatements *aEventStatus) : |
|
58 mCallback(aCallback) |
|
59 , mResults(aResults) |
|
60 , mEventStatus(aEventStatus) |
|
61 { |
|
62 } |
|
63 |
|
64 NS_IMETHOD Run() |
|
65 { |
|
66 NS_ASSERTION(mCallback, "Trying to notify about results without a callback!"); |
|
67 |
|
68 if (mEventStatus->shouldNotify()) { |
|
69 // Hold a strong reference to the callback while notifying it, so that if |
|
70 // it spins the event loop, the callback won't be released and freed out |
|
71 // from under us. |
|
72 nsCOMPtr<mozIStorageStatementCallback> callback = |
|
73 do_QueryInterface(mCallback); |
|
74 |
|
75 (void)mCallback->HandleResult(mResults); |
|
76 } |
|
77 |
|
78 return NS_OK; |
|
79 } |
|
80 |
|
81 private: |
|
82 mozIStorageStatementCallback *mCallback; |
|
83 nsCOMPtr<mozIStorageResultSet> mResults; |
|
84 nsRefPtr<AsyncExecuteStatements> mEventStatus; |
|
85 }; |
|
86 |
|
87 /** |
|
88 * Notifies the calling thread that an error has occurred. |
|
89 */ |
|
90 class ErrorNotifier : public nsRunnable |
|
91 { |
|
92 public: |
|
93 ErrorNotifier(mozIStorageStatementCallback *aCallback, |
|
94 mozIStorageError *aErrorObj, |
|
95 AsyncExecuteStatements *aEventStatus) : |
|
96 mCallback(aCallback) |
|
97 , mErrorObj(aErrorObj) |
|
98 , mEventStatus(aEventStatus) |
|
99 { |
|
100 } |
|
101 |
|
102 NS_IMETHOD Run() |
|
103 { |
|
104 if (mEventStatus->shouldNotify() && mCallback) { |
|
105 // Hold a strong reference to the callback while notifying it, so that if |
|
106 // it spins the event loop, the callback won't be released and freed out |
|
107 // from under us. |
|
108 nsCOMPtr<mozIStorageStatementCallback> callback = |
|
109 do_QueryInterface(mCallback); |
|
110 |
|
111 (void)mCallback->HandleError(mErrorObj); |
|
112 } |
|
113 |
|
114 return NS_OK; |
|
115 } |
|
116 |
|
117 private: |
|
118 mozIStorageStatementCallback *mCallback; |
|
119 nsCOMPtr<mozIStorageError> mErrorObj; |
|
120 nsRefPtr<AsyncExecuteStatements> mEventStatus; |
|
121 }; |
|
122 |
|
123 /** |
|
124 * Notifies the calling thread that the statement has finished executing. Takes |
|
125 * ownership of the StatementData so it is released on the proper thread. |
|
126 */ |
|
127 class CompletionNotifier : public nsRunnable |
|
128 { |
|
129 public: |
|
130 /** |
|
131 * This takes ownership of the callback and the StatementData. They are |
|
132 * released on the thread this is dispatched to (which should always be the |
|
133 * calling thread). |
|
134 */ |
|
135 CompletionNotifier(mozIStorageStatementCallback *aCallback, |
|
136 ExecutionState aReason) |
|
137 : mCallback(aCallback) |
|
138 , mReason(aReason) |
|
139 { |
|
140 } |
|
141 |
|
142 NS_IMETHOD Run() |
|
143 { |
|
144 if (mCallback) { |
|
145 (void)mCallback->HandleCompletion(mReason); |
|
146 NS_RELEASE(mCallback); |
|
147 } |
|
148 |
|
149 return NS_OK; |
|
150 } |
|
151 |
|
152 private: |
|
153 mozIStorageStatementCallback *mCallback; |
|
154 ExecutionState mReason; |
|
155 }; |
|
156 |
|
157 } // anonymous namespace |
|
158 |
|
159 //////////////////////////////////////////////////////////////////////////////// |
|
160 //// AsyncExecuteStatements |
|
161 |
|
162 /* static */ |
|
163 nsresult |
|
164 AsyncExecuteStatements::execute(StatementDataArray &aStatements, |
|
165 Connection *aConnection, |
|
166 sqlite3 *aNativeConnection, |
|
167 mozIStorageStatementCallback *aCallback, |
|
168 mozIStoragePendingStatement **_stmt) |
|
169 { |
|
170 // Create our event to run in the background |
|
171 nsRefPtr<AsyncExecuteStatements> event = |
|
172 new AsyncExecuteStatements(aStatements, aConnection, aNativeConnection, |
|
173 aCallback); |
|
174 NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); |
|
175 |
|
176 // Dispatch it to the background |
|
177 nsIEventTarget *target = aConnection->getAsyncExecutionTarget(); |
|
178 |
|
179 // If we don't have a valid target, this is a bug somewhere else. In the past, |
|
180 // this assert found cases where a Run method would schedule a new statement |
|
181 // without checking if asyncClose had been called. The caller must prevent |
|
182 // that from happening or, if the work is not critical, just avoid creating |
|
183 // the new statement during shutdown. See bug 718449 for an example. |
|
184 MOZ_ASSERT(target); |
|
185 if (!target) { |
|
186 return NS_ERROR_NOT_AVAILABLE; |
|
187 } |
|
188 |
|
189 nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL); |
|
190 NS_ENSURE_SUCCESS(rv, rv); |
|
191 |
|
192 // Return it as the pending statement object and track it. |
|
193 NS_ADDREF(*_stmt = event); |
|
194 return NS_OK; |
|
195 } |
|
196 |
|
197 AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements, |
|
198 Connection *aConnection, |
|
199 sqlite3 *aNativeConnection, |
|
200 mozIStorageStatementCallback *aCallback) |
|
201 : mConnection(aConnection) |
|
202 , mNativeConnection(aNativeConnection) |
|
203 , mHasTransaction(false) |
|
204 , mCallback(aCallback) |
|
205 , mCallingThread(::do_GetCurrentThread()) |
|
206 , mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS)) |
|
207 , mIntervalStart(TimeStamp::Now()) |
|
208 , mState(PENDING) |
|
209 , mCancelRequested(false) |
|
210 , mMutex(aConnection->sharedAsyncExecutionMutex) |
|
211 , mDBMutex(aConnection->sharedDBMutex) |
|
212 , mRequestStartDate(TimeStamp::Now()) |
|
213 { |
|
214 (void)mStatements.SwapElements(aStatements); |
|
215 NS_ASSERTION(mStatements.Length(), "We weren't given any statements!"); |
|
216 NS_IF_ADDREF(mCallback); |
|
217 } |
|
218 |
|
219 AsyncExecuteStatements::~AsyncExecuteStatements() |
|
220 { |
|
221 MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point"); |
|
222 } |
|
223 |
|
224 bool |
|
225 AsyncExecuteStatements::shouldNotify() |
|
226 { |
|
227 #ifdef DEBUG |
|
228 mMutex.AssertNotCurrentThreadOwns(); |
|
229 |
|
230 bool onCallingThread = false; |
|
231 (void)mCallingThread->IsOnCurrentThread(&onCallingThread); |
|
232 NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!"); |
|
233 #endif |
|
234 |
|
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; |
|
239 } |
|
240 |
|
241 bool |
|
242 AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData, |
|
243 bool aLastStatement) |
|
244 { |
|
245 mMutex.AssertNotCurrentThreadOwns(); |
|
246 |
|
247 sqlite3_stmt *aStatement = nullptr; |
|
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); |
|
252 |
|
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; |
|
265 |
|
266 // And notify. |
|
267 (void)notifyError(error); |
|
268 return false; |
|
269 } |
|
270 |
|
271 // Advance our iterator, execute, and then process the statement. |
|
272 itr++; |
|
273 bool lastStatement = aLastStatement && itr == end; |
|
274 continueProcessing = executeAndProcessStatement(aStatement, lastStatement); |
|
275 |
|
276 // Always reset our statement. |
|
277 (void)::sqlite3_reset(aStatement); |
|
278 } |
|
279 |
|
280 return continueProcessing; |
|
281 } |
|
282 |
|
283 bool |
|
284 AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement, |
|
285 bool aLastStatement) |
|
286 { |
|
287 mMutex.AssertNotCurrentThreadOwns(); |
|
288 |
|
289 // Execute our statement |
|
290 bool hasResults; |
|
291 do { |
|
292 hasResults = executeStatement(aStatement); |
|
293 |
|
294 // If we had an error, bail. |
|
295 if (mState == ERROR) |
|
296 return false; |
|
297 |
|
298 // If we have been canceled, there is no point in going on... |
|
299 { |
|
300 MutexAutoLock lockedScope(mMutex); |
|
301 if (mCancelRequested) { |
|
302 mState = CANCELED; |
|
303 return false; |
|
304 } |
|
305 } |
|
306 |
|
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; |
|
313 |
|
314 // Notify, and stop processing statements. |
|
315 (void)notifyError(mozIStorageError::ERROR, |
|
316 "An error occurred while notifying about results"); |
|
317 |
|
318 return false; |
|
319 } |
|
320 } while (hasResults); |
|
321 |
|
322 #ifdef DEBUG |
|
323 // Check to make sure that this statement was smart about what it did. |
|
324 checkAndLogStatementPerformance(aStatement); |
|
325 #endif |
|
326 |
|
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; |
|
332 |
|
333 return true; |
|
334 } |
|
335 |
|
336 bool |
|
337 AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement) |
|
338 { |
|
339 mMutex.AssertNotCurrentThreadOwns(); |
|
340 Telemetry::AutoTimer<Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_MS> finallySendExecutionDuration(mRequestStartDate); |
|
341 while (true) { |
|
342 // lock the sqlite mutex so sqlite3_errmsg cannot change |
|
343 SQLiteMutexAutoLock lockedScope(mDBMutex); |
|
344 |
|
345 int rc = mConnection->stepStatement(mNativeConnection, aStatement); |
|
346 // Stop if we have no more results. |
|
347 if (rc == SQLITE_DONE) |
|
348 { |
|
349 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true); |
|
350 return false; |
|
351 } |
|
352 |
|
353 // If we got results, we can return now. |
|
354 if (rc == SQLITE_ROW) |
|
355 { |
|
356 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true); |
|
357 return true; |
|
358 } |
|
359 |
|
360 // Some errors are not fatal, and we can handle them and continue. |
|
361 if (rc == SQLITE_BUSY) { |
|
362 // Don't hold the lock while we call outside our module. |
|
363 SQLiteMutexAutoUnlock unlockedScope(mDBMutex); |
|
364 |
|
365 // Yield, and try again |
|
366 (void)::PR_Sleep(PR_INTERVAL_NO_WAIT); |
|
367 continue; |
|
368 } |
|
369 |
|
370 // Set an error state. |
|
371 mState = ERROR; |
|
372 Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, false); |
|
373 |
|
374 // Construct the error message before giving up the mutex (which we cannot |
|
375 // hold during the call to notifyError). |
|
376 nsCOMPtr<mozIStorageError> errorObj( |
|
377 new Error(rc, ::sqlite3_errmsg(mNativeConnection)) |
|
378 ); |
|
379 // We cannot hold the DB mutex while calling notifyError. |
|
380 SQLiteMutexAutoUnlock unlockedScope(mDBMutex); |
|
381 (void)notifyError(errorObj); |
|
382 |
|
383 // Finally, indicate that we should stop processing. |
|
384 return false; |
|
385 } |
|
386 } |
|
387 |
|
388 nsresult |
|
389 AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement) |
|
390 { |
|
391 NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!"); |
|
392 mMutex.AssertNotCurrentThreadOwns(); |
|
393 |
|
394 // Build result object if we need it. |
|
395 if (!mResultSet) |
|
396 mResultSet = new ResultSet(); |
|
397 NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY); |
|
398 |
|
399 nsRefPtr<Row> row(new Row()); |
|
400 NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY); |
|
401 |
|
402 nsresult rv = row->initialize(aStatement); |
|
403 NS_ENSURE_SUCCESS(rv, rv); |
|
404 |
|
405 rv = mResultSet->add(row); |
|
406 NS_ENSURE_SUCCESS(rv, rv); |
|
407 |
|
408 // If we have hit our maximum number of allowed results, or if we have hit |
|
409 // the maximum amount of time we want to wait for results, notify the |
|
410 // calling thread about it. |
|
411 TimeStamp now = TimeStamp::Now(); |
|
412 TimeDuration delta = now - mIntervalStart; |
|
413 if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) { |
|
414 // Notify the caller |
|
415 rv = notifyResults(); |
|
416 if (NS_FAILED(rv)) |
|
417 return NS_OK; // we'll try again with the next result |
|
418 |
|
419 // Reset our start time |
|
420 mIntervalStart = now; |
|
421 } |
|
422 |
|
423 return NS_OK; |
|
424 } |
|
425 |
|
426 nsresult |
|
427 AsyncExecuteStatements::notifyComplete() |
|
428 { |
|
429 mMutex.AssertNotCurrentThreadOwns(); |
|
430 NS_ASSERTION(mState != PENDING, |
|
431 "Still in a pending state when calling Complete!"); |
|
432 |
|
433 // Reset our statements before we try to commit or rollback. If we are |
|
434 // canceling and have statements that think they have pending work, the |
|
435 // rollback will fail. |
|
436 for (uint32_t i = 0; i < mStatements.Length(); i++) |
|
437 mStatements[i].reset(); |
|
438 |
|
439 // Release references to the statement data as soon as possible. If this |
|
440 // is the last reference, statements will be finalized immediately on the |
|
441 // async thread, hence avoiding several bounces between threads and possible |
|
442 // race conditions with AsyncClose(). |
|
443 mStatements.Clear(); |
|
444 |
|
445 // Handle our transaction, if we have one |
|
446 if (mHasTransaction) { |
|
447 if (mState == COMPLETED) { |
|
448 nsresult rv = mConnection->commitTransactionInternal(mNativeConnection); |
|
449 if (NS_FAILED(rv)) { |
|
450 mState = ERROR; |
|
451 (void)notifyError(mozIStorageError::ERROR, |
|
452 "Transaction failed to commit"); |
|
453 } |
|
454 } |
|
455 else { |
|
456 NS_WARN_IF(NS_FAILED(mConnection->rollbackTransactionInternal(mNativeConnection))); |
|
457 } |
|
458 mHasTransaction = false; |
|
459 } |
|
460 |
|
461 // Always generate a completion notification; it is what guarantees that our |
|
462 // destruction does not happen here on the async thread. |
|
463 nsRefPtr<CompletionNotifier> completionEvent = |
|
464 new CompletionNotifier(mCallback, mState); |
|
465 |
|
466 // We no longer own mCallback (the CompletionNotifier takes ownership). |
|
467 mCallback = nullptr; |
|
468 |
|
469 (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL); |
|
470 |
|
471 return NS_OK; |
|
472 } |
|
473 |
|
474 nsresult |
|
475 AsyncExecuteStatements::notifyError(int32_t aErrorCode, |
|
476 const char *aMessage) |
|
477 { |
|
478 mMutex.AssertNotCurrentThreadOwns(); |
|
479 mDBMutex.assertNotCurrentThreadOwns(); |
|
480 |
|
481 if (!mCallback) |
|
482 return NS_OK; |
|
483 |
|
484 nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage)); |
|
485 NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY); |
|
486 |
|
487 return notifyError(errorObj); |
|
488 } |
|
489 |
|
490 nsresult |
|
491 AsyncExecuteStatements::notifyError(mozIStorageError *aError) |
|
492 { |
|
493 mMutex.AssertNotCurrentThreadOwns(); |
|
494 mDBMutex.assertNotCurrentThreadOwns(); |
|
495 |
|
496 if (!mCallback) |
|
497 return NS_OK; |
|
498 |
|
499 nsRefPtr<ErrorNotifier> notifier = |
|
500 new ErrorNotifier(mCallback, aError, this); |
|
501 NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY); |
|
502 |
|
503 return mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL); |
|
504 } |
|
505 |
|
506 nsresult |
|
507 AsyncExecuteStatements::notifyResults() |
|
508 { |
|
509 mMutex.AssertNotCurrentThreadOwns(); |
|
510 NS_ASSERTION(mCallback, "notifyResults called without a callback!"); |
|
511 |
|
512 nsRefPtr<CallbackResultNotifier> notifier = |
|
513 new CallbackResultNotifier(mCallback, mResultSet, this); |
|
514 NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY); |
|
515 |
|
516 nsresult rv = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL); |
|
517 if (NS_SUCCEEDED(rv)) |
|
518 mResultSet = nullptr; // we no longer own it on success |
|
519 return rv; |
|
520 } |
|
521 |
|
522 NS_IMPL_ISUPPORTS( |
|
523 AsyncExecuteStatements, |
|
524 nsIRunnable, |
|
525 mozIStoragePendingStatement |
|
526 ) |
|
527 |
|
528 bool |
|
529 AsyncExecuteStatements::statementsNeedTransaction() |
|
530 { |
|
531 // If there is more than one write statement, run in a transaction. |
|
532 // Additionally, if we have only one statement but it needs a transaction, due |
|
533 // to multiple BindingParams, we will wrap it in one. |
|
534 for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) { |
|
535 transactionsCount += mStatements[i].needsTransaction(); |
|
536 if (transactionsCount > 1) { |
|
537 return true; |
|
538 } |
|
539 } |
|
540 return false; |
|
541 } |
|
542 |
|
543 //////////////////////////////////////////////////////////////////////////////// |
|
544 //// mozIStoragePendingStatement |
|
545 |
|
546 NS_IMETHODIMP |
|
547 AsyncExecuteStatements::Cancel() |
|
548 { |
|
549 #ifdef DEBUG |
|
550 bool onCallingThread = false; |
|
551 (void)mCallingThread->IsOnCurrentThread(&onCallingThread); |
|
552 NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!"); |
|
553 #endif |
|
554 |
|
555 // If we have already canceled, we have an error, but always indicate that |
|
556 // we are trying to cancel. |
|
557 NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED); |
|
558 |
|
559 { |
|
560 MutexAutoLock lockedScope(mMutex); |
|
561 |
|
562 // We need to indicate that we want to try and cancel now. |
|
563 mCancelRequested = true; |
|
564 } |
|
565 |
|
566 return NS_OK; |
|
567 } |
|
568 |
|
569 //////////////////////////////////////////////////////////////////////////////// |
|
570 //// nsIRunnable |
|
571 |
|
572 NS_IMETHODIMP |
|
573 AsyncExecuteStatements::Run() |
|
574 { |
|
575 MOZ_ASSERT(!mConnection->isClosed()); |
|
576 |
|
577 // Do not run if we have been canceled. |
|
578 { |
|
579 MutexAutoLock lockedScope(mMutex); |
|
580 if (mCancelRequested) |
|
581 mState = CANCELED; |
|
582 } |
|
583 if (mState == CANCELED) |
|
584 return notifyComplete(); |
|
585 |
|
586 if (statementsNeedTransaction()) { |
|
587 Connection* rawConnection = static_cast<Connection*>(mConnection.get()); |
|
588 if (NS_SUCCEEDED(mConnection->beginTransactionInternal(mNativeConnection, |
|
589 mozIStorageConnection::TRANSACTION_IMMEDIATE))) { |
|
590 mHasTransaction = true; |
|
591 } |
|
592 #ifdef DEBUG |
|
593 else { |
|
594 NS_WARNING("Unable to create a transaction for async execution."); |
|
595 } |
|
596 #endif |
|
597 } |
|
598 |
|
599 // Execute each statement, giving the callback results if it returns any. |
|
600 for (uint32_t i = 0; i < mStatements.Length(); i++) { |
|
601 bool finished = (i == (mStatements.Length() - 1)); |
|
602 |
|
603 sqlite3_stmt *stmt; |
|
604 { // lock the sqlite mutex so sqlite3_errmsg cannot change |
|
605 SQLiteMutexAutoLock lockedScope(mDBMutex); |
|
606 |
|
607 int rc = mStatements[i].getSqliteStatement(&stmt); |
|
608 if (rc != SQLITE_OK) { |
|
609 // Set our error state. |
|
610 mState = ERROR; |
|
611 |
|
612 // Build the error object; can't call notifyError with the lock held |
|
613 nsCOMPtr<mozIStorageError> errorObj( |
|
614 new Error(rc, ::sqlite3_errmsg(mNativeConnection)) |
|
615 ); |
|
616 { |
|
617 // We cannot hold the DB mutex and call notifyError. |
|
618 SQLiteMutexAutoUnlock unlockedScope(mDBMutex); |
|
619 (void)notifyError(errorObj); |
|
620 } |
|
621 break; |
|
622 } |
|
623 } |
|
624 |
|
625 // If we have parameters to bind, bind them, execute, and process. |
|
626 if (mStatements[i].hasParametersToBeBound()) { |
|
627 if (!bindExecuteAndProcessStatement(mStatements[i], finished)) |
|
628 break; |
|
629 } |
|
630 // Otherwise, just execute and process the statement. |
|
631 else if (!executeAndProcessStatement(stmt, finished)) { |
|
632 break; |
|
633 } |
|
634 } |
|
635 |
|
636 // If we still have results that we haven't notified about, take care of |
|
637 // them now. |
|
638 if (mResultSet) |
|
639 (void)notifyResults(); |
|
640 |
|
641 // Notify about completion |
|
642 return notifyComplete(); |
|
643 } |
|
644 |
|
645 } // namespace storage |
|
646 } // namespace mozilla |