Line data Source code
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/DebugOnly.h"
23 : #include "mozilla/Telemetry.h"
24 :
25 : namespace mozilla {
26 : namespace storage {
27 :
28 : /**
29 : * The following constants help batch rows into result sets.
30 : * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that
31 : * takes less than 200 milliseconds is considered to feel instantaneous to end
32 : * users. MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of
33 : * dispatches to calling thread, while also providing reasonably-sized sets of
34 : * data for consumers. Both of these constants are used because we assume that
35 : * consumers are trying to avoid blocking their execution thread for long
36 : * periods of time, and dispatching many small events to the calling thread will
37 : * end up blocking it.
38 : */
39 : #define MAX_MILLISECONDS_BETWEEN_RESULTS 75
40 : #define MAX_ROWS_PER_RESULT 15
41 :
42 : ////////////////////////////////////////////////////////////////////////////////
43 : //// AsyncExecuteStatements
44 :
45 : /* static */
46 : nsresult
47 11 : AsyncExecuteStatements::execute(StatementDataArray &aStatements,
48 : Connection *aConnection,
49 : sqlite3 *aNativeConnection,
50 : mozIStorageStatementCallback *aCallback,
51 : mozIStoragePendingStatement **_stmt)
52 : {
53 : // Create our event to run in the background
54 : RefPtr<AsyncExecuteStatements> event =
55 : new AsyncExecuteStatements(aStatements, aConnection, aNativeConnection,
56 22 : aCallback);
57 11 : NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
58 :
59 : // Dispatch it to the background
60 11 : nsIEventTarget *target = aConnection->getAsyncExecutionTarget();
61 :
62 : // If we don't have a valid target, this is a bug somewhere else. In the past,
63 : // this assert found cases where a Run method would schedule a new statement
64 : // without checking if asyncClose had been called. The caller must prevent
65 : // that from happening or, if the work is not critical, just avoid creating
66 : // the new statement during shutdown. See bug 718449 for an example.
67 11 : MOZ_ASSERT(target);
68 11 : if (!target) {
69 0 : return NS_ERROR_NOT_AVAILABLE;
70 : }
71 :
72 11 : nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
73 11 : NS_ENSURE_SUCCESS(rv, rv);
74 :
75 : // Return it as the pending statement object and track it.
76 11 : event.forget(_stmt);
77 11 : return NS_OK;
78 : }
79 :
80 11 : AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements,
81 : Connection *aConnection,
82 : sqlite3 *aNativeConnection,
83 11 : mozIStorageStatementCallback *aCallback)
84 : : mConnection(aConnection)
85 : , mNativeConnection(aNativeConnection)
86 : , mHasTransaction(false)
87 : , mCallback(aCallback)
88 22 : , mCallingThread(::do_GetCurrentThread())
89 : , mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS))
90 : , mIntervalStart(TimeStamp::Now())
91 : , mState(PENDING)
92 : , mCancelRequested(false)
93 : , mMutex(aConnection->sharedAsyncExecutionMutex)
94 : , mDBMutex(aConnection->sharedDBMutex)
95 33 : , mRequestStartDate(TimeStamp::Now())
96 : {
97 11 : (void)mStatements.SwapElements(aStatements);
98 11 : NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
99 11 : }
100 :
101 6 : AsyncExecuteStatements::~AsyncExecuteStatements()
102 : {
103 3 : MOZ_ASSERT(!mCallback, "Never called the Completion callback!");
104 3 : MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point");
105 3 : if (mCallback) {
106 : NS_ProxyRelease("AsyncExecuteStatements::mCallback", mCallingThread,
107 0 : mCallback.forget());
108 : }
109 3 : }
110 :
111 : bool
112 3 : AsyncExecuteStatements::shouldNotify()
113 : {
114 : #ifdef DEBUG
115 3 : mMutex.AssertNotCurrentThreadOwns();
116 :
117 3 : bool onCallingThread = false;
118 3 : (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
119 3 : NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
120 : #endif
121 :
122 : // We do not need to acquire mMutex here because it can only ever be written
123 : // to on the calling thread, and the only thread that can call us is the
124 : // calling thread, so we know that our access is serialized.
125 3 : return !mCancelRequested;
126 : }
127 :
128 : bool
129 6 : AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData,
130 : bool aLastStatement)
131 : {
132 6 : mMutex.AssertNotCurrentThreadOwns();
133 :
134 6 : sqlite3_stmt *aStatement = nullptr;
135 : // This cannot fail; we are only called if it's available.
136 6 : (void)aData.getSqliteStatement(&aStatement);
137 6 : NS_ASSERTION(aStatement, "You broke the code; do not call here like that!");
138 6 : BindingParamsArray *paramsArray(aData);
139 :
140 : // Iterate through all of our parameters, bind them, and execute.
141 6 : bool continueProcessing = true;
142 6 : BindingParamsArray::iterator itr = paramsArray->begin();
143 6 : BindingParamsArray::iterator end = paramsArray->end();
144 18 : while (itr != end && continueProcessing) {
145 : // Bind the data to our statement.
146 : nsCOMPtr<IStorageBindingParamsInternal> bindingInternal =
147 12 : do_QueryInterface(*itr);
148 12 : nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement);
149 6 : if (error) {
150 : // Set our error state.
151 0 : mState = ERROR;
152 :
153 : // And notify.
154 0 : (void)notifyError(error);
155 0 : return false;
156 : }
157 :
158 : // Advance our iterator, execute, and then process the statement.
159 6 : itr++;
160 6 : bool lastStatement = aLastStatement && itr == end;
161 6 : continueProcessing = executeAndProcessStatement(aStatement, lastStatement);
162 :
163 : // Always reset our statement.
164 6 : (void)::sqlite3_reset(aStatement);
165 : }
166 :
167 6 : return continueProcessing;
168 : }
169 :
170 : bool
171 11 : AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement,
172 : bool aLastStatement)
173 : {
174 11 : mMutex.AssertNotCurrentThreadOwns();
175 :
176 : // Execute our statement
177 : bool hasResults;
178 15 : do {
179 15 : hasResults = executeStatement(aStatement);
180 :
181 : // If we had an error, bail.
182 15 : if (mState == ERROR)
183 0 : return false;
184 :
185 : // If we have been canceled, there is no point in going on...
186 : {
187 30 : MutexAutoLock lockedScope(mMutex);
188 15 : if (mCancelRequested) {
189 0 : mState = CANCELED;
190 0 : return false;
191 : }
192 : }
193 :
194 : // Build our result set and notify if we got anything back and have a
195 : // callback to notify.
196 19 : if (mCallback && hasResults &&
197 4 : NS_FAILED(buildAndNotifyResults(aStatement))) {
198 : // We had an error notifying, so we notify on error and stop processing.
199 0 : mState = ERROR;
200 :
201 : // Notify, and stop processing statements.
202 : (void)notifyError(mozIStorageError::ERROR,
203 0 : "An error occurred while notifying about results");
204 :
205 0 : return false;
206 : }
207 : } while (hasResults);
208 :
209 : #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
210 11 : if (MOZ_LOG_TEST(gStorageLog, LogLevel::Warning))
211 : #endif
212 : {
213 : // Check to make sure that this statement was smart about what it did.
214 0 : checkAndLogStatementPerformance(aStatement);
215 : }
216 :
217 : // If we are done, we need to set our state accordingly while we still hold
218 : // our mutex. We would have already returned if we were canceled or had
219 : // an error at this point.
220 11 : if (aLastStatement)
221 11 : mState = COMPLETED;
222 :
223 11 : return true;
224 : }
225 :
226 : bool
227 15 : AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement)
228 : {
229 15 : mMutex.AssertNotCurrentThreadOwns();
230 30 : Telemetry::AutoTimer<Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_MS> finallySendExecutionDuration(mRequestStartDate);
231 : while (true) {
232 : // lock the sqlite mutex so sqlite3_errmsg cannot change
233 15 : SQLiteMutexAutoLock lockedScope(mDBMutex);
234 :
235 15 : int rc = mConnection->stepStatement(mNativeConnection, aStatement);
236 : // Stop if we have no more results.
237 15 : if (rc == SQLITE_DONE)
238 : {
239 11 : Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
240 11 : return false;
241 : }
242 :
243 : // If we got results, we can return now.
244 4 : if (rc == SQLITE_ROW)
245 : {
246 4 : Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true);
247 4 : return true;
248 : }
249 :
250 : // Some errors are not fatal, and we can handle them and continue.
251 0 : if (rc == SQLITE_BUSY) {
252 : // Don't hold the lock while we call outside our module.
253 0 : SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
254 :
255 : // Yield, and try again
256 0 : (void)::PR_Sleep(PR_INTERVAL_NO_WAIT);
257 0 : continue;
258 : }
259 :
260 : // Set an error state.
261 0 : mState = ERROR;
262 0 : Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, false);
263 :
264 : // Construct the error message before giving up the mutex (which we cannot
265 : // hold during the call to notifyError).
266 : nsCOMPtr<mozIStorageError> errorObj(
267 0 : new Error(rc, ::sqlite3_errmsg(mNativeConnection))
268 0 : );
269 : // We cannot hold the DB mutex while calling notifyError.
270 0 : SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
271 0 : (void)notifyError(errorObj);
272 :
273 : // Finally, indicate that we should stop processing.
274 0 : return false;
275 0 : }
276 : }
277 :
278 : nsresult
279 4 : AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement)
280 : {
281 4 : NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
282 4 : mMutex.AssertNotCurrentThreadOwns();
283 :
284 : // Build result object if we need it.
285 4 : if (!mResultSet)
286 3 : mResultSet = new ResultSet();
287 4 : NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
288 :
289 8 : RefPtr<Row> row(new Row());
290 4 : NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
291 :
292 4 : nsresult rv = row->initialize(aStatement);
293 4 : NS_ENSURE_SUCCESS(rv, rv);
294 :
295 4 : rv = mResultSet->add(row);
296 4 : NS_ENSURE_SUCCESS(rv, rv);
297 :
298 : // If we have hit our maximum number of allowed results, or if we have hit
299 : // the maximum amount of time we want to wait for results, notify the
300 : // calling thread about it.
301 4 : TimeStamp now = TimeStamp::Now();
302 4 : TimeDuration delta = now - mIntervalStart;
303 4 : if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) {
304 : // Notify the caller
305 0 : rv = notifyResults();
306 0 : if (NS_FAILED(rv))
307 0 : return NS_OK; // we'll try again with the next result
308 :
309 : // Reset our start time
310 0 : mIntervalStart = now;
311 : }
312 :
313 4 : return NS_OK;
314 : }
315 :
316 : nsresult
317 11 : AsyncExecuteStatements::notifyComplete()
318 : {
319 11 : mMutex.AssertNotCurrentThreadOwns();
320 11 : NS_ASSERTION(mState != PENDING,
321 : "Still in a pending state when calling Complete!");
322 :
323 : // Reset our statements before we try to commit or rollback. If we are
324 : // canceling and have statements that think they have pending work, the
325 : // rollback will fail.
326 22 : for (uint32_t i = 0; i < mStatements.Length(); i++)
327 11 : mStatements[i].reset();
328 :
329 : // Release references to the statement data as soon as possible. If this
330 : // is the last reference, statements will be finalized immediately on the
331 : // async thread, hence avoiding several bounces between threads and possible
332 : // race conditions with AsyncClose().
333 11 : mStatements.Clear();
334 :
335 : // Handle our transaction, if we have one
336 11 : if (mHasTransaction) {
337 0 : if (mState == COMPLETED) {
338 0 : nsresult rv = mConnection->commitTransactionInternal(mNativeConnection);
339 0 : if (NS_FAILED(rv)) {
340 0 : mState = ERROR;
341 : (void)notifyError(mozIStorageError::ERROR,
342 0 : "Transaction failed to commit");
343 : }
344 : }
345 : else {
346 : DebugOnly<nsresult> rv =
347 0 : mConnection->rollbackTransactionInternal(mNativeConnection);
348 0 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Transaction failed to rollback");
349 : }
350 0 : mHasTransaction = false;
351 : }
352 :
353 : // This will take ownership of mCallback and make sure its destruction will
354 : // happen on the owner thread.
355 22 : Unused << mCallingThread->Dispatch(
356 22 : NewRunnableMethod("AsyncExecuteStatements::notifyCompleteOnCallingThread",
357 : this, &AsyncExecuteStatements::notifyCompleteOnCallingThread),
358 11 : NS_DISPATCH_NORMAL);
359 :
360 11 : return NS_OK;
361 : }
362 :
363 : nsresult
364 11 : AsyncExecuteStatements::notifyCompleteOnCallingThread() {
365 11 : MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
366 : // Take ownership of mCallback and responsibility for freeing it when we
367 : // release it. Any notifyResultsOnCallingThread and notifyErrorOnCallingThread
368 : // calls on the stack spinning the event loop have guaranteed their safety by
369 : // creating their own strong reference before invoking the callback.
370 22 : nsCOMPtr<mozIStorageStatementCallback> callback = mCallback.forget();
371 11 : if (callback) {
372 10 : Unused << callback->HandleCompletion(mState);
373 : }
374 22 : return NS_OK;
375 : }
376 :
377 : nsresult
378 0 : AsyncExecuteStatements::notifyError(int32_t aErrorCode,
379 : const char *aMessage)
380 : {
381 0 : mMutex.AssertNotCurrentThreadOwns();
382 0 : mDBMutex.assertNotCurrentThreadOwns();
383 :
384 0 : if (!mCallback)
385 0 : return NS_OK;
386 :
387 0 : nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
388 0 : NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
389 :
390 0 : return notifyError(errorObj);
391 : }
392 :
393 : nsresult
394 0 : AsyncExecuteStatements::notifyError(mozIStorageError *aError)
395 : {
396 0 : mMutex.AssertNotCurrentThreadOwns();
397 0 : mDBMutex.assertNotCurrentThreadOwns();
398 :
399 0 : if (!mCallback)
400 0 : return NS_OK;
401 :
402 0 : Unused << mCallingThread->Dispatch(
403 0 : NewRunnableMethod<nsCOMPtr<mozIStorageError>>("AsyncExecuteStatements::notifyErrorOnCallingThread",
404 : this, &AsyncExecuteStatements::notifyErrorOnCallingThread, aError),
405 0 : NS_DISPATCH_NORMAL);
406 :
407 0 : return NS_OK;
408 : }
409 :
410 : nsresult
411 0 : AsyncExecuteStatements::notifyErrorOnCallingThread(mozIStorageError *aError) {
412 0 : MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
413 : // Acquire our own strong reference so that if the callback spins a nested
414 : // event loop and notifyCompleteOnCallingThread is executed, forgetting
415 : // mCallback, we still have a valid/strong reference that won't be freed until
416 : // we exit.
417 0 : nsCOMPtr<mozIStorageStatementCallback> callback = mCallback;
418 0 : if (shouldNotify() && callback) {
419 0 : Unused << callback->HandleError(aError);
420 : }
421 0 : return NS_OK;
422 : }
423 :
424 : nsresult
425 3 : AsyncExecuteStatements::notifyResults()
426 : {
427 3 : mMutex.AssertNotCurrentThreadOwns();
428 3 : MOZ_ASSERT(mCallback, "notifyResults called without a callback!");
429 :
430 : // This takes ownership of mResultSet, a new one will be generated in
431 : // buildAndNotifyResults() when further results will arrive.
432 6 : Unused << mCallingThread->Dispatch(
433 6 : NewRunnableMethod<RefPtr<ResultSet>>("AsyncExecuteStatements::notifyResultsOnCallingThread",
434 6 : this, &AsyncExecuteStatements::notifyResultsOnCallingThread, mResultSet.forget()),
435 3 : NS_DISPATCH_NORMAL);
436 :
437 3 : return NS_OK;
438 : }
439 :
440 : nsresult
441 3 : AsyncExecuteStatements::notifyResultsOnCallingThread(ResultSet *aResultSet)
442 : {
443 3 : MOZ_ASSERT(mCallingThread->IsOnCurrentThread());
444 : // Acquire our own strong reference so that if the callback spins a nested
445 : // event loop and notifyCompleteOnCallingThread is executed, forgetting
446 : // mCallback, we still have a valid/strong reference that won't be freed until
447 : // we exit.
448 6 : nsCOMPtr<mozIStorageStatementCallback> callback = mCallback;
449 3 : if (shouldNotify() && callback) {
450 3 : Unused << callback->HandleResult(aResultSet);
451 : }
452 6 : return NS_OK;
453 : }
454 :
455 257 : NS_IMPL_ISUPPORTS(
456 : AsyncExecuteStatements,
457 : nsIRunnable,
458 : mozIStoragePendingStatement
459 : )
460 :
461 : bool
462 11 : AsyncExecuteStatements::statementsNeedTransaction()
463 : {
464 : // If there is more than one write statement, run in a transaction.
465 : // Additionally, if we have only one statement but it needs a transaction, due
466 : // to multiple BindingParams, we will wrap it in one.
467 22 : for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) {
468 11 : transactionsCount += mStatements[i].needsTransaction();
469 11 : if (transactionsCount > 1) {
470 0 : return true;
471 : }
472 : }
473 11 : return false;
474 : }
475 :
476 : ////////////////////////////////////////////////////////////////////////////////
477 : //// mozIStoragePendingStatement
478 :
479 : NS_IMETHODIMP
480 0 : AsyncExecuteStatements::Cancel()
481 : {
482 : #ifdef DEBUG
483 0 : bool onCallingThread = false;
484 0 : (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
485 0 : NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!");
486 : #endif
487 :
488 : // If we have already canceled, we have an error, but always indicate that
489 : // we are trying to cancel.
490 0 : NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED);
491 :
492 : {
493 0 : MutexAutoLock lockedScope(mMutex);
494 :
495 : // We need to indicate that we want to try and cancel now.
496 0 : mCancelRequested = true;
497 : }
498 :
499 0 : return NS_OK;
500 : }
501 :
502 : ////////////////////////////////////////////////////////////////////////////////
503 : //// nsIRunnable
504 :
505 : NS_IMETHODIMP
506 11 : AsyncExecuteStatements::Run()
507 : {
508 11 : MOZ_ASSERT(mConnection->isConnectionReadyOnThisThread());
509 :
510 : // Do not run if we have been canceled.
511 : {
512 22 : MutexAutoLock lockedScope(mMutex);
513 11 : if (mCancelRequested)
514 0 : mState = CANCELED;
515 : }
516 11 : if (mState == CANCELED)
517 0 : return notifyComplete();
518 :
519 11 : if (statementsNeedTransaction() && mConnection->getAutocommit()) {
520 0 : if (NS_SUCCEEDED(mConnection->beginTransactionInternal(mNativeConnection,
521 : mozIStorageConnection::TRANSACTION_IMMEDIATE))) {
522 0 : mHasTransaction = true;
523 : }
524 : #ifdef DEBUG
525 : else {
526 0 : NS_WARNING("Unable to create a transaction for async execution.");
527 : }
528 : #endif
529 : }
530 :
531 : // Execute each statement, giving the callback results if it returns any.
532 22 : for (uint32_t i = 0; i < mStatements.Length(); i++) {
533 11 : bool finished = (i == (mStatements.Length() - 1));
534 :
535 : sqlite3_stmt *stmt;
536 : { // lock the sqlite mutex so sqlite3_errmsg cannot change
537 22 : SQLiteMutexAutoLock lockedScope(mDBMutex);
538 :
539 11 : int rc = mStatements[i].getSqliteStatement(&stmt);
540 11 : if (rc != SQLITE_OK) {
541 : // Set our error state.
542 0 : mState = ERROR;
543 :
544 : // Build the error object; can't call notifyError with the lock held
545 : nsCOMPtr<mozIStorageError> errorObj(
546 0 : new Error(rc, ::sqlite3_errmsg(mNativeConnection))
547 0 : );
548 : {
549 : // We cannot hold the DB mutex and call notifyError.
550 0 : SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
551 0 : (void)notifyError(errorObj);
552 : }
553 0 : break;
554 : }
555 : }
556 :
557 : // If we have parameters to bind, bind them, execute, and process.
558 11 : if (mStatements[i].hasParametersToBeBound()) {
559 6 : if (!bindExecuteAndProcessStatement(mStatements[i], finished))
560 0 : break;
561 : }
562 : // Otherwise, just execute and process the statement.
563 5 : else if (!executeAndProcessStatement(stmt, finished)) {
564 0 : break;
565 : }
566 : }
567 :
568 : // If we still have results that we haven't notified about, take care of
569 : // them now.
570 11 : if (mResultSet)
571 3 : (void)notifyResults();
572 :
573 : // Notify about completion
574 11 : return notifyComplete();
575 : }
576 :
577 : } // namespace storage
578 : } // namespace mozilla
|