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