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
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/
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
16 * The Original Code is mozilla.org code.
18 * The Initial Developer of the Original Code is
19 * Mozilla Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 2008
21 * the Initial Developer. All Rights Reserved.
24 * Shawn Wilsher <me@shawnwilsher.com> (Original Author)
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsAutoPtr.h"
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"
60 * The following constants help batch rows into result sets.
61 * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that
62 * takes less than 200 milliseconds is considered to feel instantaneous to end
63 * users. MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of
64 * dispatches to calling thread, while also providing reasonably-sized sets of
65 * data for consumers. Both of these constants are used because we assume that
66 * consumers are trying to avoid blocking their execution thread for long
67 * periods of time, and dispatching many small events to the calling thread will
70 #define MAX_MILLISECONDS_BETWEEN_RESULTS 75
71 #define MAX_ROWS_PER_RESULT 15
73 ////////////////////////////////////////////////////////////////////////////////
78 typedef AsyncExecuteStatements::ExecutionState ExecutionState
;
81 * Notifies a callback with a result set.
83 class CallbackResultNotifier
: public nsRunnable
86 CallbackResultNotifier(mozIStorageStatementCallback
*aCallback
,
87 mozIStorageResultSet
*aResults
,
88 AsyncExecuteStatements
*aEventStatus
) :
91 , mEventStatus(aEventStatus
)
97 NS_ASSERTION(mCallback
, "Trying to notify about results without a callback!");
99 if (mEventStatus
->shouldNotify())
100 (void)mCallback
->HandleResult(mResults
);
106 mozIStorageStatementCallback
*mCallback
;
107 nsCOMPtr
<mozIStorageResultSet
> mResults
;
108 nsRefPtr
<AsyncExecuteStatements
> mEventStatus
;
112 * Notifies the calling thread that an error has occurred.
114 class ErrorNotifier
: public nsRunnable
117 ErrorNotifier(mozIStorageStatementCallback
*aCallback
,
118 mozIStorageError
*aErrorObj
,
119 AsyncExecuteStatements
*aEventStatus
) :
121 , mErrorObj(aErrorObj
)
122 , mEventStatus(aEventStatus
)
128 if (mEventStatus
->shouldNotify() && mCallback
)
129 (void)mCallback
->HandleError(mErrorObj
);
135 mozIStorageStatementCallback
*mCallback
;
136 nsCOMPtr
<mozIStorageError
> mErrorObj
;
137 nsRefPtr
<AsyncExecuteStatements
> mEventStatus
;
141 * Notifies the calling thread that the statement has finished executing. Keeps
142 * the AsyncExecuteStatements instance alive long enough so that it does not
143 * get destroyed on the async thread if there are no other references alive.
145 class CompletionNotifier
: public nsRunnable
149 * This takes ownership of the callback. It is released on the thread this is
150 * dispatched to (which should always be the calling thread).
152 CompletionNotifier(mozIStorageStatementCallback
*aCallback
,
153 ExecutionState aReason
,
154 AsyncExecuteStatements
*aKeepAsyncAlive
)
155 : mKeepAsyncAlive(aKeepAsyncAlive
)
156 , mCallback(aCallback
)
164 (void)mCallback
->HandleCompletion(mReason
);
165 NS_RELEASE(mCallback
);
172 nsRefPtr
<AsyncExecuteStatements
> mKeepAsyncAlive
;
173 mozIStorageStatementCallback
*mCallback
;
174 ExecutionState mReason
;
177 } // anonymous namespace
179 ////////////////////////////////////////////////////////////////////////////////
180 //// AsyncExecuteStatements
184 AsyncExecuteStatements::execute(StatementDataArray
&aStatements
,
185 Connection
*aConnection
,
186 mozIStorageStatementCallback
*aCallback
,
187 mozIStoragePendingStatement
**_stmt
)
189 // Create our event to run in the background
190 nsRefPtr
<AsyncExecuteStatements
> event
=
191 new AsyncExecuteStatements(aStatements
, aConnection
, aCallback
);
192 NS_ENSURE_TRUE(event
, NS_ERROR_OUT_OF_MEMORY
);
194 // Dispatch it to the background
195 nsIEventTarget
*target
= aConnection
->getAsyncExecutionTarget();
196 NS_ENSURE_TRUE(target
, NS_ERROR_NOT_AVAILABLE
);
197 nsresult rv
= target
->Dispatch(event
, NS_DISPATCH_NORMAL
);
198 NS_ENSURE_SUCCESS(rv
, rv
);
200 // Return it as the pending statement object and track it.
201 NS_ADDREF(*_stmt
= event
);
205 AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray
&aStatements
,
206 Connection
*aConnection
,
207 mozIStorageStatementCallback
*aCallback
)
208 : mConnection(aConnection
)
209 , mTransactionManager(nsnull
)
210 , mCallback(aCallback
)
211 , mCallingThread(::do_GetCurrentThread())
212 , mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS
))
213 , mIntervalStart(TimeStamp::Now())
215 , mCancelRequested(false)
216 , mMutex(aConnection
->sharedAsyncExecutionMutex
)
217 , mDBMutex(aConnection
->sharedDBMutex
)
219 (void)mStatements
.SwapElements(aStatements
);
220 NS_ASSERTION(mStatements
.Length(), "We weren't given any statements!");
221 NS_IF_ADDREF(mCallback
);
225 AsyncExecuteStatements::shouldNotify()
228 mMutex
.AssertNotCurrentThreadOwns();
230 PRBool onCallingThread
= PR_FALSE
;
231 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
232 NS_ASSERTION(onCallingThread
, "runEvent not running on the calling thread!");
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
;
242 AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData
&aData
,
245 mMutex
.AssertNotCurrentThreadOwns();
247 sqlite3_stmt
*aStatement
= nsnull
;
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
);
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
);
263 // Set our error state.
267 (void)notifyError(error
);
271 // Advance our iterator, execute, and then process the statement.
273 bool lastStatement
= aLastStatement
&& itr
== end
;
274 continueProcessing
= executeAndProcessStatement(aStatement
, lastStatement
);
276 // Always reset our statement.
277 (void)::sqlite3_reset(aStatement
);
280 return continueProcessing
;
284 AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt
*aStatement
,
287 mMutex
.AssertNotCurrentThreadOwns();
289 // Execute our statement
292 hasResults
= executeStatement(aStatement
);
294 // If we had an error, bail.
298 // If we have been canceled, there is no point in going on...
300 MutexAutoLock
lockedScope(mMutex
);
301 if (mCancelRequested
) {
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.
314 // Notify, and stop processing statements.
315 (void)notifyError(mozIStorageError::ERROR
,
316 "An error occurred while notifying about results");
320 } while (hasResults
);
323 // Check to make sure that this statement was smart about what it did.
324 checkAndLogStatementPerformance(aStatement
);
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.
337 AsyncExecuteStatements::executeStatement(sqlite3_stmt
*aStatement
)
339 mMutex
.AssertNotCurrentThreadOwns();
342 // lock the sqlite mutex so sqlite3_errmsg cannot change
343 SQLiteMutexAutoLock
lockedScope(mDBMutex
);
345 int rc
= stepStmt(aStatement
);
346 // Stop if we have no more results.
347 if (rc
== SQLITE_DONE
)
350 // If we got results, we can return now.
351 if (rc
== SQLITE_ROW
)
354 // Some errors are not fatal, and we can handle them and continue.
355 if (rc
== SQLITE_BUSY
) {
356 // Don't hold the lock while we call outside our module.
357 SQLiteMutexAutoUnlock
unlockedScope(mDBMutex
);
359 // Yield, and try again
360 (void)::PR_Sleep(PR_INTERVAL_NO_WAIT
);
364 // Set an error state.
367 // Construct the error message before giving up the mutex (which we cannot
368 // hold during the call to notifyError).
369 sqlite3
*db
= mConnection
->GetNativeConnection();
370 nsCOMPtr
<mozIStorageError
> errorObj(new Error(rc
, ::sqlite3_errmsg(db
)));
371 // We cannot hold the DB mutex while calling notifyError.
372 SQLiteMutexAutoUnlock
unlockedScope(mDBMutex
);
373 (void)notifyError(errorObj
);
375 // Finally, indicate that we should stop processing.
381 AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt
*aStatement
)
383 NS_ASSERTION(mCallback
, "Trying to dispatch results without a callback!");
384 mMutex
.AssertNotCurrentThreadOwns();
386 // Build result object if we need it.
388 mResultSet
= new ResultSet();
389 NS_ENSURE_TRUE(mResultSet
, NS_ERROR_OUT_OF_MEMORY
);
391 nsRefPtr
<Row
> row(new Row());
392 NS_ENSURE_TRUE(row
, NS_ERROR_OUT_OF_MEMORY
);
394 nsresult rv
= row
->initialize(aStatement
);
395 NS_ENSURE_SUCCESS(rv
, rv
);
397 rv
= mResultSet
->add(row
);
398 NS_ENSURE_SUCCESS(rv
, rv
);
400 // If we have hit our maximum number of allowed results, or if we have hit
401 // the maximum amount of time we want to wait for results, notify the
402 // calling thread about it.
403 TimeStamp now
= TimeStamp::Now();
404 TimeDuration delta
= now
- mIntervalStart
;
405 if (mResultSet
->rows() >= MAX_ROWS_PER_RESULT
|| delta
> mMaxWait
) {
407 rv
= notifyResults();
409 return NS_OK
; // we'll try again with the next result
411 // Reset our start time
412 mIntervalStart
= now
;
419 AsyncExecuteStatements::notifyComplete()
421 mMutex
.AssertNotCurrentThreadOwns();
422 NS_ASSERTION(mState
!= PENDING
,
423 "Still in a pending state when calling Complete!");
425 // Finalize our statements before we try to commit or rollback. If we are
426 // canceling and have statements that think they have pending work, the
427 // rollback will fail.
428 for (PRUint32 i
= 0; i
< mStatements
.Length(); i
++)
429 mStatements
[i
].finalize();
431 // Handle our transaction, if we have one
432 if (mTransactionManager
) {
433 if (mState
== COMPLETED
) {
434 nsresult rv
= mTransactionManager
->Commit();
437 (void)notifyError(mozIStorageError::ERROR
,
438 "Transaction failed to commit");
442 (void)mTransactionManager
->Rollback();
444 delete mTransactionManager
;
445 mTransactionManager
= nsnull
;
448 // Always generate a completion notification; it is what guarantees that our
449 // destruction does not happen here on the async thread.
450 nsRefPtr
<CompletionNotifier
> completionEvent
=
451 new CompletionNotifier(mCallback
, mState
, this);
452 NS_ENSURE_TRUE(completionEvent
, NS_ERROR_OUT_OF_MEMORY
);
454 // We no longer own mCallback (the CompletionNotifier takes ownership).
457 (void)mCallingThread
->Dispatch(completionEvent
, NS_DISPATCH_NORMAL
);
463 AsyncExecuteStatements::notifyError(PRInt32 aErrorCode
,
464 const char *aMessage
)
466 mMutex
.AssertNotCurrentThreadOwns();
467 mDBMutex
.assertNotCurrentThreadOwns();
472 nsCOMPtr
<mozIStorageError
> errorObj(new Error(aErrorCode
, aMessage
));
473 NS_ENSURE_TRUE(errorObj
, NS_ERROR_OUT_OF_MEMORY
);
475 return notifyError(errorObj
);
479 AsyncExecuteStatements::notifyError(mozIStorageError
*aError
)
481 mMutex
.AssertNotCurrentThreadOwns();
482 mDBMutex
.assertNotCurrentThreadOwns();
487 nsRefPtr
<ErrorNotifier
> notifier
=
488 new ErrorNotifier(mCallback
, aError
, this);
489 NS_ENSURE_TRUE(notifier
, NS_ERROR_OUT_OF_MEMORY
);
491 return mCallingThread
->Dispatch(notifier
, NS_DISPATCH_NORMAL
);
495 AsyncExecuteStatements::notifyResults()
497 mMutex
.AssertNotCurrentThreadOwns();
498 NS_ASSERTION(mCallback
, "notifyResults called without a callback!");
500 nsRefPtr
<CallbackResultNotifier
> notifier
=
501 new CallbackResultNotifier(mCallback
, mResultSet
, this);
502 NS_ENSURE_TRUE(notifier
, NS_ERROR_OUT_OF_MEMORY
);
504 nsresult rv
= mCallingThread
->Dispatch(notifier
, NS_DISPATCH_NORMAL
);
505 if (NS_SUCCEEDED(rv
))
506 mResultSet
= nsnull
; // we no longer own it on success
510 NS_IMPL_THREADSAFE_ISUPPORTS2(
511 AsyncExecuteStatements
,
513 mozIStoragePendingStatement
516 ////////////////////////////////////////////////////////////////////////////////
517 //// mozIStoragePendingStatement
520 AsyncExecuteStatements::Cancel()
523 PRBool onCallingThread
= PR_FALSE
;
524 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
525 NS_ASSERTION(onCallingThread
, "Not canceling from the calling thread!");
528 // If we have already canceled, we have an error, but always indicate that
529 // we are trying to cancel.
530 NS_ENSURE_FALSE(mCancelRequested
, NS_ERROR_UNEXPECTED
);
533 MutexAutoLock
lockedScope(mMutex
);
535 // We need to indicate that we want to try and cancel now.
536 mCancelRequested
= true;
542 ////////////////////////////////////////////////////////////////////////////////
546 AsyncExecuteStatements::Run()
548 // Do not run if we have been canceled.
550 MutexAutoLock
lockedScope(mMutex
);
551 if (mCancelRequested
)
554 if (mState
== CANCELED
)
555 return notifyComplete();
557 // If there is more than one statement, run it in a transaction. We assume
558 // that we have been given write statements since getting a batch of read
559 // statements doesn't make a whole lot of sense.
560 // Additionally, if we have only one statement and it needs a transaction, we
561 // will wrap it in one.
562 if (mStatements
.Length() > 1 || mStatements
[0].needsTransaction()) {
563 // We don't error if this failed because it's not terrible if it does.
564 mTransactionManager
= new mozStorageTransaction(mConnection
, PR_FALSE
,
565 mozIStorageConnection::TRANSACTION_IMMEDIATE
);
568 // Execute each statement, giving the callback results if it returns any.
569 for (PRUint32 i
= 0; i
< mStatements
.Length(); i
++) {
570 bool finished
= (i
== (mStatements
.Length() - 1));
573 { // lock the sqlite mutex so sqlite3_errmsg cannot change
574 SQLiteMutexAutoLock
lockedScope(mDBMutex
);
576 int rc
= mStatements
[i
].getSqliteStatement(&stmt
);
577 if (rc
!= SQLITE_OK
) {
578 // Set our error state.
581 // Build the error object; can't call notifyError with the lock held
582 sqlite3
*db
= mConnection
->GetNativeConnection();
583 nsCOMPtr
<mozIStorageError
> errorObj(
584 new Error(rc
, ::sqlite3_errmsg(db
))
587 // We cannot hold the DB mutex and call notifyError.
588 SQLiteMutexAutoUnlock
unlockedScope(mDBMutex
);
589 (void)notifyError(errorObj
);
595 // If we have parameters to bind, bind them, execute, and process.
596 if (mStatements
[i
].hasParametersToBeBound()) {
597 if (!bindExecuteAndProcessStatement(mStatements
[i
], finished
))
600 // Otherwise, just execute and process the statement.
601 else if (!executeAndProcessStatement(stmt
, finished
)) {
606 // If we still have results that we haven't notified about, take care of
609 (void)notifyResults();
611 // Notify about completion
612 return notifyComplete();
615 } // namespace storage
616 } // namespace mozilla