1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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 "nsThreadUtils.h"
41 #include "nsAutoPtr.h"
42 #include "nsAutoLock.h"
43 #include "nsCOMArray.h"
48 #include "mozIStorageStatementCallback.h"
49 #include "mozIStoragePendingStatement.h"
50 #include "mozStorageHelper.h"
51 #include "mozStorageResultSet.h"
52 #include "mozStorageRow.h"
53 #include "mozStorageConnection.h"
54 #include "mozStorageError.h"
55 #include "mozStorageEvents.h"
58 * The following constants help batch rows into result sets.
59 * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that
60 * takes less than 200 milliseconds is considered to feel instantaneous to end
61 * users. MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of
62 * dispatches to calling thread, while also providing reasonably-sized sets of
63 * data for consumers. Both of these constants are used because we assume that
64 * consumers are trying to avoid blocking their execution thread for long
65 * periods of time, and dispatching many small events to the calling thread will
68 #define MAX_MILLISECONDS_BETWEEN_RESULTS 100
69 #define MAX_ROWS_PER_RESULT 15
71 ////////////////////////////////////////////////////////////////////////////////
72 //// Asynchronous Statement Execution
75 * Enum used to describe the state of execution.
79 , COMPLETED
= mozIStorageStatementCallback::REASON_FINISHED
80 , CANCELED
= mozIStorageStatementCallback::REASON_CANCELED
81 , ERROR
= mozIStorageStatementCallback::REASON_ERROR
85 * Interface used to check if an event should run.
87 class iEventStatus
: public nsISupports
90 virtual PRBool
runEvent() = 0;
94 * Notifies a callback with a result set.
96 class CallbackResultNotifier
: public nsIRunnable
101 CallbackResultNotifier(mozIStorageStatementCallback
*aCallback
,
102 mozIStorageResultSet
*aResults
,
103 iEventStatus
*aEventStatus
) :
106 , mEventStatus(aEventStatus
)
112 NS_ASSERTION(mCallback
, "Trying to notify about results without a callback!");
114 if (mEventStatus
->runEvent())
115 (void)mCallback
->HandleResult(mResults
);
121 CallbackResultNotifier() { }
123 mozIStorageStatementCallback
*mCallback
;
124 nsCOMPtr
<mozIStorageResultSet
> mResults
;
125 nsRefPtr
<iEventStatus
> mEventStatus
;
127 NS_IMPL_THREADSAFE_ISUPPORTS1(
128 CallbackResultNotifier
,
133 * Notifies the calling thread that an error has occurred.
135 class ErrorNotifier
: public nsIRunnable
140 ErrorNotifier(mozIStorageStatementCallback
*aCallback
,
141 mozIStorageError
*aErrorObj
,
142 iEventStatus
*aEventStatus
) :
144 , mErrorObj(aErrorObj
)
145 , mEventStatus(aEventStatus
)
151 if (mEventStatus
->runEvent() && mCallback
)
152 (void)mCallback
->HandleError(mErrorObj
);
160 mozIStorageStatementCallback
*mCallback
;
161 nsCOMPtr
<mozIStorageError
> mErrorObj
;
162 nsRefPtr
<iEventStatus
> mEventStatus
;
164 NS_IMPL_THREADSAFE_ISUPPORTS1(
170 * Notifies the calling thread that the statement has finished executing.
172 class CompletionNotifier
: public nsIRunnable
178 * This takes ownership of the callback. It is released on the thread this is
179 * dispatched to (which should always be the calling thread).
181 CompletionNotifier(mozIStorageStatementCallback
*aCallback
,
182 ExecutionState aReason
) :
190 (void)mCallback
->HandleCompletion(mReason
);
191 NS_RELEASE(mCallback
);
196 virtual void cancel()
198 // Update our reason so the completion notifier knows what is up.
203 CompletionNotifier() { }
205 mozIStorageStatementCallback
*mCallback
;
206 ExecutionState mReason
;
208 NS_IMPL_THREADSAFE_ISUPPORTS1(
214 * Executes a statement asynchronously in the background.
216 class AsyncExecute
: public nsIRunnable
217 , public mozIStoragePendingStatement
218 , public iEventStatus
224 * This takes ownership of both the statement and the callback.
226 AsyncExecute(nsTArray
<sqlite3_stmt
*> &aStatements
,
227 mozIStorageConnection
*aConnection
,
228 mozIStorageStatementCallback
*aCallback
) :
229 mConnection(aConnection
)
230 , mTransactionManager(nsnull
)
231 , mCallback(aCallback
)
232 , mCallingThread(do_GetCurrentThread())
233 , mMaxIntervalWait(PR_MicrosecondsToInterval(MAX_MILLISECONDS_BETWEEN_RESULTS
))
234 , mIntervalStart(PR_IntervalNow())
236 , mCancelRequested(PR_FALSE
)
237 , mLock(nsAutoLock::NewLock("AsyncExecute::mLock"))
239 (void)mStatements
.SwapElements(aStatements
);
240 NS_ASSERTION(mStatements
.Length(), "We weren't given any statements!");
243 nsresult
initialize()
245 NS_ENSURE_TRUE(mLock
, NS_ERROR_OUT_OF_MEMORY
);
246 NS_IF_ADDREF(mCallback
);
252 // do not run if we have been canceled
254 nsAutoLock
mutex(mLock
);
255 if (mCancelRequested
) {
258 return NotifyComplete();
262 // If there is more than one statement, run it in a transaction. We assume
263 // that we have been given write statements since getting a batch of read
264 // statements doesn't make a whole lot of sense.
265 if (mStatements
.Length() > 1) {
266 // We don't error if this failed because it's not terrible if it does.
267 mTransactionManager
= new mozStorageTransaction(mConnection
, PR_FALSE
,
268 mozIStorageConnection::TRANSACTION_IMMEDIATE
);
271 // Execute each statement, giving the callback results if it returns any.
272 for (PRUint32 i
= 0; i
< mStatements
.Length(); i
++) {
273 PRBool finished
= (i
== (mStatements
.Length() - 1));
274 if (!ExecuteAndProcessStatement(mStatements
[i
], finished
))
278 // If we still have results that we haven't notified about, take care of
281 (void)NotifyResults();
283 // Notify about completion
284 return NotifyComplete();
287 NS_IMETHOD
Cancel(PRBool
*_successful
)
290 PRBool onCallingThread
= PR_FALSE
;
291 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
292 NS_ASSERTION(onCallingThread
, "Not canceling from the calling thread!");
295 // If we have already canceled, we have an error, but always indicate that
296 // we are trying to cancel.
297 NS_ENSURE_FALSE(mCancelRequested
, NS_ERROR_UNEXPECTED
);
300 nsAutoLock
mutex(mLock
);
302 // We need to indicate that we want to try and cancel now.
303 mCancelRequested
= PR_TRUE
;
305 // Establish if we can cancel
306 *_successful
= (mState
== PENDING
);
309 // Note, it is possible for us to return false here, and end up canceling
310 // events that have been dispatched to the calling thread. This is OK,
311 // however, because only read statements (such as SELECT) are going to be
312 // posting events to the calling thread that actually check if they should
319 * This is part of iEventStatus. It indicates if an event should be ran based
320 * on if we are trying to cancel or not.
325 PRBool onCallingThread
= PR_FALSE
;
326 (void)mCallingThread
->IsOnCurrentThread(&onCallingThread
);
327 NS_ASSERTION(onCallingThread
, "runEvent not running on the calling thread!");
330 // We do not need to acquire mLock here because it can only ever be written
331 // to on the calling thread, and the only thread that can call us is the
332 // calling thread, so we know that our access is serialized.
333 return !mCancelRequested
;
337 AsyncExecute() : mMaxIntervalWait(0) { }
341 nsAutoLock::DestroyLock(mLock
);
345 * Executes a given statement until completion, an error occurs, or we are
346 * canceled. If aFinished is true, we know that we are the last statement,
347 * and should set mState accordingly.
349 * @pre mLock is not held
352 * The statement to execute and then process.
354 * Indicates if this is the last statement or not. If it is, we have
355 * to set the proper state.
356 * @returns true if we should continue to process statements, false otherwise.
358 PRBool
ExecuteAndProcessStatement(sqlite3_stmt
*aStatement
, PRBool aFinished
)
360 // We need to hold a lock for statement execution so we can properly
361 // reflect state in case we are canceled. We unlock in a few areas in
362 // order to allow for cancelation to occur.
363 nsAutoLock
mutex(mLock
);
367 int rc
= sqlite3_step(aStatement
);
368 // Break out if we have no more results
369 if (rc
== SQLITE_DONE
)
372 // Some errors are not fatal, and we can handle them and continue.
373 if (rc
!= SQLITE_OK
&& rc
!= SQLITE_ROW
) {
374 if (rc
== SQLITE_BUSY
) {
375 // We do not want to hold our lock while we yield.
376 nsAutoUnlock
cancelationScope(mLock
);
378 // Yield, and try again
379 (void)PR_Sleep(PR_INTERVAL_NO_WAIT
);
386 // Drop our mutex - NotifyError doesn't want it held
390 sqlite3
*db
= sqlite3_db_handle(aStatement
);
391 (void)NotifyError(rc
, sqlite3_errmsg(db
));
393 // And stop processing statements
397 // If we do not have a callback, there's no point in executing this
398 // statement anymore, but we wish to continue to execute statements. We
399 // also need to update our state if we are finished, so break out of the
404 // If we have been canceled, there is no point in going on...
405 if (mCancelRequested
) {
410 // Build our results and notify if it's time.
411 rv
= BuildAndNotifyResults(aStatement
);
416 // If we have an error that we have not already notified about, set our
417 // state accordingly, and notify.
421 // Drop our mutex - NotifyError doesn't want it held
424 // Notify, and stop processing statements.
425 (void)NotifyError(mozIStorageError::ERROR
, "");
429 // If we are done, we need to set our state accordingly while we still
430 // hold our lock. We would have already returned if we were canceled or had
431 // an error at this point.
439 * Builds a result set up with a row from a given statement. If we meet the
440 * right criteria, go ahead and notify about this results too.
445 * The statement to get the row data from.
447 nsresult
BuildAndNotifyResults(sqlite3_stmt
*aStatement
)
449 NS_ASSERTION(mCallback
, "Trying to dispatch results without a callback!");
451 // At this point, it is safe to not hold the lock and allow for cancelation.
452 // We may add an event to the calling thread, but that thread will not end
453 // up running when it checks back with us to see if it should run.
454 nsAutoUnlock
cancelationScope(mLock
);
456 // Build result object if we need it.
458 mResultSet
= new mozStorageResultSet();
459 NS_ENSURE_TRUE(mResultSet
, NS_ERROR_OUT_OF_MEMORY
);
461 nsRefPtr
<mozStorageRow
> row(new mozStorageRow());
462 NS_ENSURE_TRUE(row
, NS_ERROR_OUT_OF_MEMORY
);
464 nsresult rv
= row
->initialize(aStatement
);
465 NS_ENSURE_SUCCESS(rv
, rv
);
467 rv
= mResultSet
->add(row
);
468 NS_ENSURE_SUCCESS(rv
, rv
);
470 // If we have hit our maximum number of allowed results, or if we have hit
471 // the maximum amount of time we want to wait for results, notify the
472 // calling thread about it.
473 PRIntervalTime now
= PR_IntervalNow();
474 PRIntervalTime delta
= now
- mIntervalStart
;
475 if (mResultSet
->rows() >= MAX_ROWS_PER_RESULT
|| delta
> mMaxIntervalWait
) {
477 rv
= NotifyResults();
479 return NS_OK
; // we'll try again with the next result
481 // Reset our start time
482 mIntervalStart
= now
;
489 * Notifies callback about completion, and does any necessary cleanup.
491 * @pre mLock is not held
493 nsresult
NotifyComplete()
495 NS_ASSERTION(mState
!= PENDING
,
496 "Still in a pending state when calling Complete!");
498 // Handle our transaction, if we have one
499 if (mTransactionManager
) {
500 if (mState
== COMPLETED
) {
501 nsresult rv
= mTransactionManager
->Commit();
504 (void)NotifyError(mozIStorageError::ERROR
,
505 "Transaction failed to commit");
509 (void)mTransactionManager
->Rollback();
511 delete mTransactionManager
;
512 mTransactionManager
= nsnull
;
515 // Finalize our statements
516 for (PRUint32 i
= 0; i
< mStatements
.Length(); i
++) {
517 (void)sqlite3_finalize(mStatements
[i
]);
518 mStatements
[i
] = NULL
;
521 // Notify about completion iff we have a callback.
523 nsRefPtr
<CompletionNotifier
> completionEvent
=
524 new CompletionNotifier(mCallback
, mState
);
525 NS_ENSURE_TRUE(completionEvent
, NS_ERROR_OUT_OF_MEMORY
);
527 // We no longer own mCallback (the CompletionNotifier takes ownership).
530 (void)mCallingThread
->Dispatch(completionEvent
, NS_DISPATCH_NORMAL
);
537 * Notifies callback about an error.
539 * @pre mLock is not held
542 * The error code defined in mozIStorageError for the error.
544 * The error string, if any.
546 nsresult
NotifyError(PRInt32 aErrorCode
, const char *aMessage
)
551 nsCOMPtr
<mozIStorageError
> errorObj
=
552 new mozStorageError(aErrorCode
, aMessage
);
553 NS_ENSURE_TRUE(errorObj
, NS_ERROR_OUT_OF_MEMORY
);
555 nsRefPtr
<ErrorNotifier
> notifier
=
556 new ErrorNotifier(mCallback
, errorObj
, this);
557 NS_ENSURE_TRUE(notifier
, NS_ERROR_OUT_OF_MEMORY
);
559 return mCallingThread
->Dispatch(notifier
, NS_DISPATCH_NORMAL
);
563 * Notifies the callback about a result set.
565 * @pre mLock is not held
567 nsresult
NotifyResults()
569 NS_ASSERTION(mCallback
, "NotifyResults called without a callback!");
571 nsRefPtr
<CallbackResultNotifier
> notifier
=
572 new CallbackResultNotifier(mCallback
, mResultSet
, this);
573 NS_ENSURE_TRUE(notifier
, NS_ERROR_OUT_OF_MEMORY
);
575 nsresult rv
= mCallingThread
->Dispatch(notifier
, NS_DISPATCH_NORMAL
);
576 if (NS_SUCCEEDED(rv
))
577 mResultSet
= nsnull
; // we no longer own it on success
581 nsTArray
<sqlite3_stmt
*> mStatements
;
582 mozIStorageConnection
*mConnection
;
583 mozStorageTransaction
*mTransactionManager
;
584 mozIStorageStatementCallback
*mCallback
;
585 nsCOMPtr
<nsIThread
> mCallingThread
;
586 nsRefPtr
<mozStorageResultSet
> mResultSet
;
589 * The maximum amount of time we want to wait between results. Defined by
590 * MAX_MILLISECONDS_BETWEEN_RESULTS and set at construction.
592 const PRIntervalTime mMaxIntervalWait
;
595 * The start time since our last set of results.
597 PRIntervalTime mIntervalStart
;
600 * Indicates the state the object is currently in.
602 ExecutionState mState
;
605 * Indicates if we should try to cancel at a cancelation point or not.
607 PRBool mCancelRequested
;
610 * This is the lock that protects our state from changing. This includes the
611 * following variables:
613 * -mCancelRequested is only set on the calling thread while the lock is
614 * held. It is always read from within the lock on the background thread,
615 * but not on the calling thread (see runEvent for why).
619 NS_IMPL_THREADSAFE_ISUPPORTS2(
622 mozIStoragePendingStatement
626 NS_executeAsync(nsTArray
<sqlite3_stmt
*> &aStatements
,
627 mozStorageConnection
*aConnection
,
628 mozIStorageStatementCallback
*aCallback
,
629 mozIStoragePendingStatement
**_stmt
)
631 // Create our event to run in the background
632 nsRefPtr
<AsyncExecute
> event(new AsyncExecute(aStatements
, aConnection
, aCallback
));
633 NS_ENSURE_TRUE(event
, NS_ERROR_OUT_OF_MEMORY
);
635 nsresult rv
= event
->initialize();
636 NS_ENSURE_SUCCESS(rv
, rv
);
638 // Dispatch it to the background
639 nsCOMPtr
<nsIEventTarget
> target(aConnection
->getAsyncExecutionTarget());
640 NS_ENSURE_TRUE(target
, NS_ERROR_NOT_AVAILABLE
);
641 rv
= target
->Dispatch(event
, NS_DISPATCH_NORMAL
);
642 NS_ENSURE_SUCCESS(rv
, rv
);
644 // Return it as the pending statement object
645 NS_ADDREF(*_stmt
= event
);