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"
45 #include "mozIStorageStatementCallback.h"
46 #include "mozIStoragePendingStatement.h"
47 #include "mozStorageStatement.h"
48 #include "mozStorageResultSet.h"
49 #include "mozStorageRow.h"
50 #include "mozStorageBackground.h"
51 #include "mozStorageError.h"
52 #include "mozStorageEvents.h"
54 ////////////////////////////////////////////////////////////////////////////////
55 //// Asynchronous Statement Execution
58 * Enum used to describe the state of execution.
62 , COMPLETED
= mozIStorageStatementCallback::REASON_FINISHED
63 , CANCELED
= mozIStorageStatementCallback::REASON_CANCELED
64 , ERROR
= mozIStorageStatementCallback::REASON_ERROR
68 * Interface used to cancel pending events.
70 class iCancelable
: public nsISupports
74 * Tells an event to cancel itself.
76 virtual void cancel() = 0;
80 * Interface used to notify of event completion.
82 class iCompletionNotifier
: public nsISupports
86 * Called when an event is completed and no longer needs to be tracked.
89 * The event that has finished.
91 virtual void completed(iCancelable
*aEvent
) = 0;
95 * Notifies a callback with a result set.
97 class CallbackResultNotifier
: public nsIRunnable
103 CallbackResultNotifier(mozIStorageStatementCallback
*aCallback
,
104 mozIStorageResultSet
*aResults
,
105 iCompletionNotifier
*aNotifier
) :
108 , mCompletionNotifier(aNotifier
)
109 , mCanceled(PR_FALSE
)
116 (void)mCallback
->HandleResult(mResults
);
118 // Notify owner AsyncExecute that we have completed
119 mCompletionNotifier
->completed(this);
120 // It is likely that the completion notifier holds a reference to us as
121 // well, so we release our reference to it here to avoid cycles.
122 mCompletionNotifier
= nsnull
;
126 virtual void cancel()
128 // Atomically set our status so we know to not run.
129 PR_AtomicSet(&mCanceled
, PR_TRUE
);
132 CallbackResultNotifier() { }
134 mozIStorageStatementCallback
*mCallback
;
135 nsCOMPtr
<mozIStorageResultSet
> mResults
;
136 nsRefPtr
<iCompletionNotifier
> mCompletionNotifier
;
139 NS_IMPL_THREADSAFE_ISUPPORTS1(
140 CallbackResultNotifier
,
145 * Notifies the calling thread that an error has occurred.
147 class ErrorNotifier
: public nsIRunnable
153 ErrorNotifier(mozIStorageStatementCallback
*aCallback
,
154 mozIStorageError
*aErrorObj
,
155 iCompletionNotifier
*aCompletionNotifier
) :
157 , mErrorObj(aErrorObj
)
158 , mCanceled(PR_FALSE
)
159 , mCompletionNotifier(aCompletionNotifier
)
166 (void)mCallback
->HandleError(mErrorObj
);
168 mCompletionNotifier
->completed(this);
169 // It is likely that the completion notifier holds a reference to us as
170 // well, so we release our reference to it here to avoid cycles.
171 mCompletionNotifier
= nsnull
;
175 virtual void cancel()
177 // Atomically set our status so we know to not run.
178 PR_AtomicSet(&mCanceled
, PR_TRUE
);
181 static inline iCancelable
*Dispatch(nsIThread
*aCallingThread
,
182 mozIStorageStatementCallback
*aCallback
,
183 iCompletionNotifier
*aCompletionNotifier
,
185 const char *aMessage
)
187 nsCOMPtr
<mozIStorageError
> errorObj(new mozStorageError(aResult
, aMessage
));
191 ErrorNotifier
*notifier
=
192 new ErrorNotifier(aCallback
, errorObj
, aCompletionNotifier
);
193 (void)aCallingThread
->Dispatch(notifier
, NS_DISPATCH_NORMAL
);
199 mozIStorageStatementCallback
*mCallback
;
200 nsCOMPtr
<mozIStorageError
> mErrorObj
;
202 nsRefPtr
<iCompletionNotifier
> mCompletionNotifier
;
204 NS_IMPL_THREADSAFE_ISUPPORTS1(
210 * Notifies the calling thread that the statement has finished executing.
212 class CompletionNotifier
: public nsIRunnable
219 * This takes ownership of the callback and statement. Both are released
220 * on the thread this is dispatched to (which should always be the calling
223 CompletionNotifier(mozIStorageStatementCallback
*aCallback
,
224 ExecutionState aReason
,
225 mozIStorageStatement
*aStatement
,
226 iCompletionNotifier
*aCompletionNotifier
) :
229 , mStatement(aStatement
)
230 , mCompletionNotifier(aCompletionNotifier
)
236 NS_RELEASE(mStatement
);
238 (void)mCallback
->HandleCompletion(mReason
);
239 NS_RELEASE(mCallback
);
242 mCompletionNotifier
->completed(this);
243 // It is likely that the completion notifier holds a reference to us as
244 // well, so we release our reference to it here to avoid cycles.
245 mCompletionNotifier
= nsnull
;
249 virtual void cancel()
251 // Update our reason so the completion notifier knows what is up.
256 CompletionNotifier() { }
258 mozIStorageStatementCallback
*mCallback
;
259 ExecutionState mReason
;
260 mozIStorageStatement
*mStatement
;
261 nsRefPtr
<iCompletionNotifier
> mCompletionNotifier
;
263 NS_IMPL_THREADSAFE_ISUPPORTS1(
269 * Executes a statement asynchronously in the background.
271 class AsyncExecute
: public nsIRunnable
272 , public mozIStoragePendingStatement
273 , public iCompletionNotifier
279 * This takes ownership of both the statement and the callback.
281 AsyncExecute(mozStorageStatement
*aStatement
,
282 mozIStorageStatementCallback
*aCallback
) :
283 mStatement(aStatement
)
284 , mCallback(aCallback
)
285 , mCallingThread(do_GetCurrentThread())
287 , mStateMutex(nsAutoLock::NewLock("AsyncExecute::mStateMutex"))
288 , mPendingEventsMutex(nsAutoLock::NewLock("AsyncExecute::mPendingEventsMutex"))
292 nsresult
initialize()
294 NS_ENSURE_TRUE(mStateMutex
, NS_ERROR_OUT_OF_MEMORY
);
295 NS_ENSURE_TRUE(mPendingEventsMutex
, NS_ERROR_OUT_OF_MEMORY
);
296 NS_ADDREF(mStatement
);
297 NS_IF_ADDREF(mCallback
);
303 // do not run if we have been canceled
305 nsAutoLock
mutex(mStateMutex
);
306 if (mState
== CANCELED
)
310 // Execute the statement, giving the callback results
311 // XXX better chunking of results?
315 rv
= mStatement
->ExecuteStep(&hasResults
);
316 // Break out if we have no more results
317 if (NS_SUCCEEDED(rv
) && !hasResults
)
320 // Some errors are not fatal, but we still need to report them
322 // Get the real result code
323 sqlite3
*db
= sqlite3_db_handle(mStatement
->NativeStatement());
324 int err
= sqlite3_errcode(db
);
325 if (err
== SQLITE_BUSY
) {
326 // Yield, and try again
327 PR_Sleep(PR_INTERVAL_NO_WAIT
);
333 nsAutoLock
mutex(mStateMutex
);
338 iCancelable
*cancelable
= ErrorNotifier::Dispatch(
339 mCallingThread
, mCallback
, this, err
, sqlite3_errmsg(db
)
342 nsAutoLock
mutex(mPendingEventsMutex
);
343 (void)mPendingEvents
.AppendObject(cancelable
);
350 // Check to see if we have been canceled
352 nsAutoLock
mutex(mStateMutex
);
353 if (mState
== CANCELED
)
357 // If we do not have a callback, but are getting results, we should stop
358 // now since all this work isn't going to accomplish anything
360 nsAutoLock
mutex(mStateMutex
);
365 // Build result object
366 nsRefPtr
<mozStorageResultSet
> results(new mozStorageResultSet());
370 nsRefPtr
<mozStorageRow
> row(new mozStorageRow());
374 rv
= row
->initialize(mStatement
->NativeStatement());
378 rv
= results
->add(row
);
383 nsRefPtr
<CallbackResultNotifier
> notifier
=
384 new CallbackResultNotifier(mCallback
, results
, this);
388 nsresult status
= mCallingThread
->Dispatch(notifier
, NS_DISPATCH_NORMAL
);
389 if (NS_SUCCEEDED(status
)) {
390 nsAutoLock
mutex(mPendingEventsMutex
);
391 (void)mPendingEvents
.AppendObject(notifier
);
395 // This is a fatal error :(
399 nsAutoLock
mutex(mStateMutex
);
404 iCancelable
*cancelable
= ErrorNotifier::Dispatch(
405 mCallingThread
, mCallback
, this, mozIStorageError::ERROR
, ""
408 nsAutoLock
mutex(mPendingEventsMutex
);
409 (void)mPendingEvents
.AppendObject(cancelable
);
413 // No more results, so update state if needed
415 nsAutoLock
mutex(mStateMutex
);
416 if (mState
== PENDING
)
419 // Notify about completion
424 static PRBool
cancelEnumerator(iCancelable
*aCancelable
, void *)
426 (void)aCancelable
->cancel();
432 // Check and update our state
434 nsAutoLock
mutex(mStateMutex
);
435 NS_ENSURE_TRUE(mState
== PENDING
|| mState
== COMPLETED
,
436 NS_ERROR_UNEXPECTED
);
440 // Cancel all our pending events on the calling thread
442 nsAutoLock
mutex(mPendingEventsMutex
);
443 (void)mPendingEvents
.EnumerateForwards(&AsyncExecute::cancelEnumerator
,
445 mPendingEvents
.Clear();
451 virtual void completed(iCancelable
*aCancelable
)
453 nsAutoLock
mutex(mPendingEventsMutex
);
454 (void)mPendingEvents
.RemoveObject(aCancelable
);
462 NS_ASSERTION(mPendingEvents
.Count() == 0, "Still pending events!");
463 nsAutoLock::DestroyLock(mStateMutex
);
464 nsAutoLock::DestroyLock(mPendingEventsMutex
);
468 * Notifies callback about completion, and does any necessary cleanup.
469 * @note: When calling this function, mStateMutex must be held.
473 // Reset the statement
474 (void)mStatement
->Reset();
476 // Notify about completion
477 NS_ASSERTION(mState
!= PENDING
,
478 "Still in a pending state when calling Complete!");
479 nsRefPtr
<CompletionNotifier
> completionEvent
=
480 new CompletionNotifier(mCallback
, mState
, mStatement
, this);
481 nsresult rv
= mCallingThread
->Dispatch(completionEvent
, NS_DISPATCH_NORMAL
);
482 if (NS_SUCCEEDED(rv
)) {
483 nsAutoLock
mutex(mPendingEventsMutex
);
484 (void)mPendingEvents
.AppendObject(completionEvent
);
487 // We no longer own mCallback or mStatement (the CompletionNotifier takes
488 // ownership), so null them out
494 mozStorageStatement
*mStatement
;
495 mozIStorageStatementCallback
*mCallback
;
496 nsCOMPtr
<nsIThread
> mCallingThread
;
499 * Indicates the state the object is currently in.
501 ExecutionState mState
;
504 * Mutex to protect mState.
509 * Stores a list of pending events that have not yet completed on the
512 nsCOMArray
<iCancelable
> mPendingEvents
;
515 * Mutex to protect mPendingEvents.
517 PRLock
*mPendingEventsMutex
;
519 NS_IMPL_THREADSAFE_ISUPPORTS2(
522 mozIStoragePendingStatement
526 NS_executeAsync(mozStorageStatement
*aStatement
,
527 mozIStorageStatementCallback
*aCallback
,
528 mozIStoragePendingStatement
**_stmt
)
530 // Create our event to run in the background
531 nsRefPtr
<AsyncExecute
> event(new AsyncExecute(aStatement
, aCallback
));
532 NS_ENSURE_TRUE(event
, NS_ERROR_OUT_OF_MEMORY
);
534 nsresult rv
= event
->initialize();
535 NS_ENSURE_SUCCESS(rv
, rv
);
537 // Dispatch it to the background
538 nsIEventTarget
*target
= mozStorageBackground::getService()->target();
539 rv
= target
->Dispatch(event
, NS_DISPATCH_NORMAL
);
540 NS_ENSURE_SUCCESS(rv
, rv
);
542 // Return it as the pending statement object
543 NS_ADDREF(*_stmt
= event
);