Coverage Report

Created: 2018-09-25 14:53

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