Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / storage / src / mozStorageEvents.cpp
bloba90bffa1e5441c1081f47ac00861f166010118ac
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=2 sts=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
14 * License.
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.
23 * Contributor(s):
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"
44 #include "prtime.h"
46 #include "sqlite3.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"
57 /**
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
66 * end up blocking it.
68 #define MAX_MILLISECONDS_BETWEEN_RESULTS 100
69 #define MAX_ROWS_PER_RESULT 15
71 ////////////////////////////////////////////////////////////////////////////////
72 //// Asynchronous Statement Execution
74 /**
75 * Enum used to describe the state of execution.
77 enum ExecutionState {
78 PENDING = -1
79 , COMPLETED = mozIStorageStatementCallback::REASON_FINISHED
80 , CANCELED = mozIStorageStatementCallback::REASON_CANCELED
81 , ERROR = mozIStorageStatementCallback::REASON_ERROR
84 /**
85 * Interface used to check if an event should run.
87 class iEventStatus : public nsISupports
89 public:
90 virtual PRBool runEvent() = 0;
93 /**
94 * Notifies a callback with a result set.
96 class CallbackResultNotifier : public nsIRunnable
98 public:
99 NS_DECL_ISUPPORTS
101 CallbackResultNotifier(mozIStorageStatementCallback *aCallback,
102 mozIStorageResultSet *aResults,
103 iEventStatus *aEventStatus) :
104 mCallback(aCallback)
105 , mResults(aResults)
106 , mEventStatus(aEventStatus)
110 NS_IMETHOD Run()
112 NS_ASSERTION(mCallback, "Trying to notify about results without a callback!");
114 if (mEventStatus->runEvent())
115 (void)mCallback->HandleResult(mResults);
117 return NS_OK;
120 private:
121 CallbackResultNotifier() { }
123 mozIStorageStatementCallback *mCallback;
124 nsCOMPtr<mozIStorageResultSet> mResults;
125 nsRefPtr<iEventStatus> mEventStatus;
127 NS_IMPL_THREADSAFE_ISUPPORTS1(
128 CallbackResultNotifier,
129 nsIRunnable
133 * Notifies the calling thread that an error has occurred.
135 class ErrorNotifier : public nsIRunnable
137 public:
138 NS_DECL_ISUPPORTS
140 ErrorNotifier(mozIStorageStatementCallback *aCallback,
141 mozIStorageError *aErrorObj,
142 iEventStatus *aEventStatus) :
143 mCallback(aCallback)
144 , mErrorObj(aErrorObj)
145 , mEventStatus(aEventStatus)
149 NS_IMETHOD Run()
151 if (mEventStatus->runEvent() && mCallback)
152 (void)mCallback->HandleError(mErrorObj);
154 return NS_OK;
157 private:
158 ErrorNotifier() { }
160 mozIStorageStatementCallback *mCallback;
161 nsCOMPtr<mozIStorageError> mErrorObj;
162 nsRefPtr<iEventStatus> mEventStatus;
164 NS_IMPL_THREADSAFE_ISUPPORTS1(
165 ErrorNotifier,
166 nsIRunnable
170 * Notifies the calling thread that the statement has finished executing.
172 class CompletionNotifier : public nsIRunnable
174 public:
175 NS_DECL_ISUPPORTS
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) :
183 mCallback(aCallback)
184 , mReason(aReason)
188 NS_IMETHOD Run()
190 (void)mCallback->HandleCompletion(mReason);
191 NS_RELEASE(mCallback);
193 return NS_OK;
196 virtual void cancel()
198 // Update our reason so the completion notifier knows what is up.
199 mReason = CANCELED;
202 private:
203 CompletionNotifier() { }
205 mozIStorageStatementCallback *mCallback;
206 ExecutionState mReason;
208 NS_IMPL_THREADSAFE_ISUPPORTS1(
209 CompletionNotifier,
210 nsIRunnable
214 * Executes a statement asynchronously in the background.
216 class AsyncExecute : public nsIRunnable
217 , public mozIStoragePendingStatement
218 , public iEventStatus
220 public:
221 NS_DECL_ISUPPORTS
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())
235 , mState(PENDING)
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);
247 return NS_OK;
250 NS_IMETHOD Run()
252 // do not run if we have been canceled
254 nsAutoLock mutex(mLock);
255 if (mCancelRequested) {
256 mState = CANCELED;
257 mutex.unlock();
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))
275 break;
278 // If we still have results that we haven't notified about, take care of
279 // them now.
280 if (mResultSet)
281 (void)NotifyResults();
283 // Notify about completion
284 return NotifyComplete();
287 NS_IMETHOD Cancel(PRBool *_successful)
289 #ifdef DEBUG
290 PRBool onCallingThread = PR_FALSE;
291 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
292 NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!");
293 #endif
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
313 // run or not.
315 return NS_OK;
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.
322 PRBool runEvent()
324 #ifdef DEBUG
325 PRBool onCallingThread = PR_FALSE;
326 (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
327 NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
328 #endif
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;
336 private:
337 AsyncExecute() : mMaxIntervalWait(0) { }
339 ~AsyncExecute()
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
351 * @param aStatement
352 * The statement to execute and then process.
353 * @param aFinished
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);
365 nsresult rv = NS_OK;
366 while (PR_TRUE) {
367 int rc = sqlite3_step(aStatement);
368 // Break out if we have no more results
369 if (rc == SQLITE_DONE)
370 break;
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);
380 continue;
383 // Set error state
384 mState = ERROR;
386 // Drop our mutex - NotifyError doesn't want it held
387 mutex.unlock();
389 // Notify
390 sqlite3 *db = sqlite3_db_handle(aStatement);
391 (void)NotifyError(rc, sqlite3_errmsg(db));
393 // And stop processing statements
394 return PR_FALSE;
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
400 // while loop.
401 if (!mCallback)
402 break;
404 // If we have been canceled, there is no point in going on...
405 if (mCancelRequested) {
406 mState = CANCELED;
407 return PR_FALSE;
410 // Build our results and notify if it's time.
411 rv = BuildAndNotifyResults(aStatement);
412 if (NS_FAILED(rv))
413 break;
416 // If we have an error that we have not already notified about, set our
417 // state accordingly, and notify.
418 if (NS_FAILED(rv)) {
419 mState = ERROR;
421 // Drop our mutex - NotifyError doesn't want it held
422 mutex.unlock();
424 // Notify, and stop processing statements.
425 (void)NotifyError(mozIStorageError::ERROR, "");
426 return PR_FALSE;
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.
432 if (aFinished)
433 mState = COMPLETED;
435 return PR_TRUE;
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.
442 * @pre mLock is held
444 * @param aStatement
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.
457 if (!mResultSet)
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) {
476 // Notify the caller
477 rv = NotifyResults();
478 if (NS_FAILED(rv))
479 return NS_OK; // we'll try again with the next result
481 // Reset our start time
482 mIntervalStart = now;
485 return NS_OK;
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();
502 if (NS_FAILED(rv)) {
503 mState = ERROR;
504 (void)NotifyError(mozIStorageError::ERROR,
505 "Transaction failed to commit");
508 else {
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.
522 if (mCallback) {
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).
528 mCallback = nsnull;
530 (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
533 return NS_OK;
537 * Notifies callback about an error.
539 * @pre mLock is not held
541 * @param aErrorCode
542 * The error code defined in mozIStorageError for the error.
543 * @param aMessage
544 * The error string, if any.
546 nsresult NotifyError(PRInt32 aErrorCode, const char *aMessage)
548 if (!mCallback)
549 return NS_OK;
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
578 return rv;
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:
612 * -mState
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).
617 PRLock *mLock;
619 NS_IMPL_THREADSAFE_ISUPPORTS2(
620 AsyncExecute,
621 nsIRunnable,
622 mozIStoragePendingStatement
625 nsresult
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);
646 return NS_OK;