зеркало из https://github.com/mozilla/gecko-dev.git
Bug 496019 - mozilla::storage::Connection::Close can spin a nested event loop
Creates a new close method that must be used when using asynchronous statements, and disallow Close from being called in that case. r=asuth sr=vlad a=dbaron --HG-- extra : rebase_source : 8470e30ef4ca1e9fb516284cafb77b070e46fde3
This commit is contained in:
Родитель
22bb3ebf84
Коммит
79d8f44604
|
@ -64,6 +64,7 @@ XPIDLSRCS = \
|
||||||
mozIStoragePendingStatement.idl \
|
mozIStoragePendingStatement.idl \
|
||||||
mozIStorageBindingParamsArray.idl \
|
mozIStorageBindingParamsArray.idl \
|
||||||
mozIStorageBindingParams.idl \
|
mozIStorageBindingParams.idl \
|
||||||
|
mozIStorageCompletionCallback.idl \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
EXPORTS_NAMESPACES = mozilla
|
EXPORTS_NAMESPACES = mozilla
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#include "nsISupports.idl"
|
#include "nsISupports.idl"
|
||||||
|
|
||||||
interface mozIStorageAggregateFunction;
|
interface mozIStorageAggregateFunction;
|
||||||
|
interface mozIStorageCompletionCallback;
|
||||||
interface mozIStorageFunction;
|
interface mozIStorageFunction;
|
||||||
interface mozIStorageProgressHandler;
|
interface mozIStorageProgressHandler;
|
||||||
interface mozIStorageStatement;
|
interface mozIStorageStatement;
|
||||||
|
@ -58,17 +59,36 @@ interface nsIFile;
|
||||||
*
|
*
|
||||||
* @threadsafe
|
* @threadsafe
|
||||||
*/
|
*/
|
||||||
[scriptable, uuid(ac3c486c-69a1-4cbe-8f25-2ad20880eab3)]
|
[scriptable, uuid(5a06b207-1977-47d4-b140-271cb851bb26)]
|
||||||
interface mozIStorageConnection : nsISupports {
|
interface mozIStorageConnection : nsISupports {
|
||||||
/*
|
/*
|
||||||
* Initialization and status
|
* Initialization and status
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes a database connection. C++ callers should simply set the database
|
* Closes a database connection. Callers must finalize all statements created
|
||||||
* variable to NULL.
|
* for this connection prior to calling this method. It is illegal to use
|
||||||
|
* call this method if any asynchronous statements have been executed on this
|
||||||
|
* connection.
|
||||||
|
*
|
||||||
|
* @throws NS_ERROR_UNEXPECTED
|
||||||
|
* If any statement has been executed asynchronously on this object.
|
||||||
|
* @throws NS_ERROR_UNEXPECTED
|
||||||
|
* If is called on a thread other than the one that opened it.
|
||||||
*/
|
*/
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously closes a database connection, allowing all pending
|
||||||
|
* asynchronous statements to complete first.
|
||||||
|
*
|
||||||
|
* @param aCallback [optional]
|
||||||
|
* A callback that will be notified when the close is completed.
|
||||||
|
*
|
||||||
|
* @throws NS_ERROR_UNEXPECTED
|
||||||
|
* If is called on a thread other than the one that opened it.
|
||||||
|
*/
|
||||||
|
void asyncClose([optional] in mozIStorageCompletionCallback aCallback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the connection is open and ready to use. This will be false
|
* Indicates if the connection is open and ready to use. This will be false
|
||||||
|
|
|
@ -190,7 +190,7 @@ AsyncExecuteStatements::execute(StatementDataArray &aStatements,
|
||||||
nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
|
nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
// Return it as the pending statement object
|
// Return it as the pending statement object and track it.
|
||||||
NS_ADDREF(*_stmt = event);
|
NS_ADDREF(*_stmt = event);
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
#include "nsAutoLock.h"
|
#include "nsAutoLock.h"
|
||||||
|
|
||||||
#include "mozIStorageAggregateFunction.h"
|
#include "mozIStorageAggregateFunction.h"
|
||||||
|
#include "mozIStorageCompletionCallback.h"
|
||||||
#include "mozIStorageFunction.h"
|
#include "mozIStorageFunction.h"
|
||||||
|
|
||||||
#include "mozStorageAsyncStatementExecution.h"
|
#include "mozStorageAsyncStatementExecution.h"
|
||||||
|
@ -238,6 +239,48 @@ aggregateFunctionFinalHelper(sqlite3_context *aCtx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// Local Classes
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class AsyncCloseConnection : public nsRunnable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AsyncCloseConnection(Connection *aConnection,
|
||||||
|
nsIEventTarget *aCallingThread,
|
||||||
|
nsIRunnable *aCallbackEvent)
|
||||||
|
: mConnection(aConnection)
|
||||||
|
, mCallingThread(aCallingThread)
|
||||||
|
, mCallbackEvent(aCallbackEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_METHOD Run()
|
||||||
|
{
|
||||||
|
// This event is first dispatched to the background thread to ensure that
|
||||||
|
// all pending asynchronous events are completed, and then back to the
|
||||||
|
// calling thread to actually close and notify.
|
||||||
|
PRBool onCallingThread = PR_FALSE;
|
||||||
|
(void)mCallingThread->IsOnCurrentThread(&onCallingThread);
|
||||||
|
if (!onCallingThread) {
|
||||||
|
(void)mCallingThread->Dispatch(this, NS_DISPATCH_NORMAL);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)mConnection->internalClose();
|
||||||
|
if (mCallbackEvent)
|
||||||
|
(void)mCallingThread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL);
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
nsCOMPtr<Connection> mConnection;
|
||||||
|
nsCOMPtr<nsIEventTarget> mCallingThread;
|
||||||
|
nsCOMPtr<nsIRunnable> mCallbackEvent;
|
||||||
|
};
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
|
@ -247,13 +290,14 @@ aggregateFunctionFinalHelper(sqlite3_context *aCtx)
|
||||||
Connection::Connection(Service *aService)
|
Connection::Connection(Service *aService)
|
||||||
: sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex")
|
: sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex")
|
||||||
, mDBConn(nsnull)
|
, mDBConn(nsnull)
|
||||||
, mAsyncExecutionMutex(nsAutoLock::NewLock("AsyncExecutionMutex"))
|
, mAsyncExecutionMutex("Connection::mAsyncExecutionMutex")
|
||||||
, mAsyncExecutionThreadShuttingDown(PR_FALSE)
|
, mAsyncExecutionThreadShuttingDown(false)
|
||||||
, mTransactionMutex(nsAutoLock::NewLock("TransactionMutex"))
|
, mTransactionMutex(nsAutoLock::NewLock("TransactionMutex"))
|
||||||
, mTransactionInProgress(PR_FALSE)
|
, mTransactionInProgress(PR_FALSE)
|
||||||
, mFunctionsMutex(nsAutoLock::NewLock("FunctionsMutex"))
|
, mFunctionsMutex(nsAutoLock::NewLock("FunctionsMutex"))
|
||||||
, mProgressHandlerMutex(nsAutoLock::NewLock("ProgressHandlerMutex"))
|
, mProgressHandlerMutex(nsAutoLock::NewLock("ProgressHandlerMutex"))
|
||||||
, mProgressHandler(nsnull)
|
, mProgressHandler(nsnull)
|
||||||
|
, mOpenedThread(do_GetCurrentThread())
|
||||||
, mStorageService(aService)
|
, mStorageService(aService)
|
||||||
{
|
{
|
||||||
mFunctions.Init();
|
mFunctions.Init();
|
||||||
|
@ -262,7 +306,6 @@ Connection::Connection(Service *aService)
|
||||||
Connection::~Connection()
|
Connection::~Connection()
|
||||||
{
|
{
|
||||||
(void)Close();
|
(void)Close();
|
||||||
nsAutoLock::DestroyLock(mAsyncExecutionMutex);
|
|
||||||
nsAutoLock::DestroyLock(mTransactionMutex);
|
nsAutoLock::DestroyLock(mTransactionMutex);
|
||||||
nsAutoLock::DestroyLock(mFunctionsMutex);
|
nsAutoLock::DestroyLock(mFunctionsMutex);
|
||||||
nsAutoLock::DestroyLock(mProgressHandlerMutex);
|
nsAutoLock::DestroyLock(mProgressHandlerMutex);
|
||||||
|
@ -276,7 +319,7 @@ NS_IMPL_THREADSAFE_ISUPPORTS1(
|
||||||
already_AddRefed<nsIEventTarget>
|
already_AddRefed<nsIEventTarget>
|
||||||
Connection::getAsyncExecutionTarget()
|
Connection::getAsyncExecutionTarget()
|
||||||
{
|
{
|
||||||
nsAutoLock mutex(mAsyncExecutionMutex);
|
MutexAutoLock lockedScope(mAsyncExecutionMutex);
|
||||||
|
|
||||||
// If we are shutting down the asynchronous thread, don't hand out any more
|
// If we are shutting down the asynchronous thread, don't hand out any more
|
||||||
// references to the thread.
|
// references to the thread.
|
||||||
|
@ -300,7 +343,6 @@ nsresult
|
||||||
Connection::initialize(nsIFile *aDatabaseFile)
|
Connection::initialize(nsIFile *aDatabaseFile)
|
||||||
{
|
{
|
||||||
NS_ASSERTION (!mDBConn, "Initialize called on already opened database!");
|
NS_ASSERTION (!mDBConn, "Initialize called on already opened database!");
|
||||||
NS_ENSURE_TRUE(mAsyncExecutionMutex, NS_ERROR_OUT_OF_MEMORY);
|
|
||||||
NS_ENSURE_TRUE(mTransactionMutex, NS_ERROR_OUT_OF_MEMORY);
|
NS_ENSURE_TRUE(mTransactionMutex, NS_ERROR_OUT_OF_MEMORY);
|
||||||
NS_ENSURE_TRUE(mFunctionsMutex, NS_ERROR_OUT_OF_MEMORY);
|
NS_ENSURE_TRUE(mFunctionsMutex, NS_ERROR_OUT_OF_MEMORY);
|
||||||
NS_ENSURE_TRUE(mProgressHandlerMutex, NS_ERROR_OUT_OF_MEMORY);
|
NS_ENSURE_TRUE(mProgressHandlerMutex, NS_ERROR_OUT_OF_MEMORY);
|
||||||
|
@ -470,14 +512,49 @@ Connection::progressHandler()
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
nsresult
|
||||||
//// mozIStorageConnection
|
Connection::setClosedState()
|
||||||
|
|
||||||
NS_IMETHODIMP
|
|
||||||
Connection::Close()
|
|
||||||
{
|
{
|
||||||
if (!mDBConn)
|
// Ensure that we are on the correct thread to close the database.
|
||||||
return NS_ERROR_NOT_INITIALIZED;
|
PRBool onOpenedThread;
|
||||||
|
nsresult rv = mOpenedThread->IsOnCurrentThread(&onOpenedThread);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
if (!onOpenedThread) {
|
||||||
|
NS_ERROR("Must close the database on the thread that you opened it with!");
|
||||||
|
return NS_ERROR_UNEXPECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag that we are shutting down the async thread, so that
|
||||||
|
// getAsyncExecutionTarget knows not to expose/create the async thread.
|
||||||
|
{
|
||||||
|
MutexAutoLock lockedScope(mAsyncExecutionMutex);
|
||||||
|
NS_ENSURE_FALSE(mAsyncExecutionThreadShuttingDown, NS_ERROR_UNEXPECTED);
|
||||||
|
mAsyncExecutionThreadShuttingDown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
Connection::internalClose()
|
||||||
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
|
// Sanity checks to make sure we are in the proper state before calling this.
|
||||||
|
NS_ASSERTION(mDBConn, "Database connection is already null!");
|
||||||
|
|
||||||
|
{ // Make sure we have marked our async thread as shutting down.
|
||||||
|
MutexAutoLock lockedScope(mAsyncExecutionMutex);
|
||||||
|
NS_ASSERTION(mAsyncExecutionThreadShuttingDown,
|
||||||
|
"Did not call setClosedState!");
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Ensure that we are being called on the thread we were opened with.
|
||||||
|
PRBool onOpenedThread = PR_FALSE;
|
||||||
|
(void)mOpenedThread->IsOnCurrentThread(&onOpenedThread);
|
||||||
|
NS_ASSERTION(onOpenedThread,
|
||||||
|
"Not called on the thread the database was opened on!");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef PR_LOGGING
|
#ifdef PR_LOGGING
|
||||||
nsCAutoString leafName(":memory");
|
nsCAutoString leafName(":memory");
|
||||||
|
@ -487,20 +564,6 @@ Connection::Close()
|
||||||
leafName.get()));
|
leafName.get()));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Flag that we are shutting down the async thread, so that
|
|
||||||
// getAsyncExecutionTarget knows not to expose/create the async thread.
|
|
||||||
{
|
|
||||||
nsAutoLock mutex(mAsyncExecutionMutex);
|
|
||||||
mAsyncExecutionThreadShuttingDown = PR_TRUE;
|
|
||||||
}
|
|
||||||
// Shutdown the async thread if it exists. (Because we just set the flag,
|
|
||||||
// we are the only code that is going to be touching this variable from here
|
|
||||||
// on out.)
|
|
||||||
if (mAsyncExecutionThread) {
|
|
||||||
mAsyncExecutionThread->Shutdown();
|
|
||||||
mAsyncExecutionThread = nsnull;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
// Notify about any non-finalized statements.
|
// Notify about any non-finalized statements.
|
||||||
sqlite3_stmt *stmt = NULL;
|
sqlite3_stmt *stmt = NULL;
|
||||||
|
@ -512,20 +575,64 @@ Connection::Close()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{
|
|
||||||
nsAutoLock mutex(mProgressHandlerMutex);
|
|
||||||
if (mProgressHandler)
|
|
||||||
::sqlite3_progress_handler(mDBConn, 0, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int srv = ::sqlite3_close(mDBConn);
|
int srv = ::sqlite3_close(mDBConn);
|
||||||
NS_ASSERTION(srv == SQLITE_OK,
|
NS_ASSERTION(srv == SQLITE_OK,
|
||||||
"sqlite3_close failed. There are probably outstanding statements that are listed above!");
|
"sqlite3_close failed. There are probably outstanding statements that are listed above!");
|
||||||
|
|
||||||
mDBConn = NULL;
|
mDBConn = NULL;
|
||||||
|
|
||||||
return convertResultCode(srv);
|
return convertResultCode(srv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// mozIStorageConnection
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
Connection::Close()
|
||||||
|
{
|
||||||
|
if (!mDBConn)
|
||||||
|
return NS_ERROR_NOT_INITIALIZED;
|
||||||
|
|
||||||
|
{ // Make sure we have not executed any asynchronous statements.
|
||||||
|
MutexAutoLock lockedScope(mAsyncExecutionMutex);
|
||||||
|
NS_ENSURE_FALSE(mAsyncExecutionThread, NS_ERROR_UNEXPECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
nsresult rv = setClosedState();
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
return internalClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
|
||||||
|
{
|
||||||
|
if (!mDBConn)
|
||||||
|
return NS_ERROR_NOT_INITIALIZED;
|
||||||
|
|
||||||
|
nsCOMPtr<nsIEventTarget> asyncThread(getAsyncExecutionTarget());
|
||||||
|
NS_ENSURE_TRUE(asyncThread, NS_ERROR_UNEXPECTED);
|
||||||
|
|
||||||
|
nsresult rv = setClosedState();
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
// Create our callback event if we were given a callback.
|
||||||
|
nsCOMPtr<nsIRunnable> completeEvent;
|
||||||
|
if (aCallback) {
|
||||||
|
completeEvent = newCompletionEvent(aCallback);
|
||||||
|
NS_ENSURE_TRUE(completeEvent, NS_ERROR_OUT_OF_MEMORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and dispatch our close event to the background thread.
|
||||||
|
nsCOMPtr<nsIRunnable> closeEvent =
|
||||||
|
new AsyncCloseConnection(this, NS_GetCurrentThread(), completeEvent);
|
||||||
|
NS_ENSURE_TRUE(closeEvent, NS_ERROR_OUT_OF_MEMORY);
|
||||||
|
|
||||||
|
rv = asyncThread->Dispatch(closeEvent, NS_DISPATCH_NORMAL);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
Connection::GetConnectionReady(PRBool *_ready)
|
Connection::GetConnectionReady(PRBool *_ready)
|
||||||
{
|
{
|
||||||
|
|
|
@ -100,9 +100,22 @@ public:
|
||||||
*/
|
*/
|
||||||
Mutex sharedAsyncExecutionMutex;
|
Mutex sharedAsyncExecutionMutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the SQLite database, and warns about any non-finalized statements.
|
||||||
|
*/
|
||||||
|
nsresult internalClose();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~Connection();
|
~Connection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the database into a closed state so no further actions can be
|
||||||
|
* performed.
|
||||||
|
*
|
||||||
|
* @note mDBConn is set to NULL in this method.
|
||||||
|
*/
|
||||||
|
nsresult setClosedState();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes a certain primitive type in the database.
|
* Describes a certain primitive type in the database.
|
||||||
*
|
*
|
||||||
|
@ -140,9 +153,9 @@ private:
|
||||||
nsCOMPtr<nsIFile> mDatabaseFile;
|
nsCOMPtr<nsIFile> mDatabaseFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protects access to mAsyncExecutionThread.
|
* Protects access to mAsyncExecutionThread and mPendingStatements.
|
||||||
*/
|
*/
|
||||||
PRLock *mAsyncExecutionMutex;
|
Mutex mAsyncExecutionMutex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lazily created thread for asynchronous statement execution. Consumers
|
* Lazily created thread for asynchronous statement execution. Consumers
|
||||||
|
@ -156,7 +169,7 @@ private:
|
||||||
* references (or to create the thread in the first place). This variable
|
* references (or to create the thread in the first place). This variable
|
||||||
* should be accessed while holding the mAsyncExecutionMutex.
|
* should be accessed while holding the mAsyncExecutionMutex.
|
||||||
*/
|
*/
|
||||||
PRBool mAsyncExecutionThreadShuttingDown;
|
bool mAsyncExecutionThreadShuttingDown;
|
||||||
|
|
||||||
PRLock *mTransactionMutex;
|
PRLock *mTransactionMutex;
|
||||||
PRBool mTransactionInProgress;
|
PRBool mTransactionInProgress;
|
||||||
|
@ -167,6 +180,12 @@ private:
|
||||||
PRLock *mProgressHandlerMutex;
|
PRLock *mProgressHandlerMutex;
|
||||||
nsCOMPtr<mozIStorageProgressHandler> mProgressHandler;
|
nsCOMPtr<mozIStorageProgressHandler> mProgressHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* References the thread this database was opened on. This MUST be thread it
|
||||||
|
* is closed on.
|
||||||
|
*/
|
||||||
|
nsCOMPtr<nsIThread> mOpenedThread;
|
||||||
|
|
||||||
// This is here for two reasons: 1) It's used to make sure that the
|
// This is here for two reasons: 1) It's used to make sure that the
|
||||||
// connections do not outlive the service. 2) Our custom collating functions
|
// connections do not outlive the service. 2) Our custom collating functions
|
||||||
// call its localeCompareStrings() method.
|
// call its localeCompareStrings() method.
|
||||||
|
|
|
@ -46,9 +46,11 @@
|
||||||
#include "nsPrintfCString.h"
|
#include "nsPrintfCString.h"
|
||||||
#include "nsString.h"
|
#include "nsString.h"
|
||||||
#include "nsError.h"
|
#include "nsError.h"
|
||||||
|
#include "nsThreadUtils.h"
|
||||||
|
|
||||||
#include "mozStoragePrivateHelpers.h"
|
#include "mozStoragePrivateHelpers.h"
|
||||||
#include "mozIStorageStatement.h"
|
#include "mozIStorageStatement.h"
|
||||||
|
#include "mozIStorageCompletionCallback.h"
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace storage {
|
namespace storage {
|
||||||
|
@ -163,12 +165,12 @@ bindJSValue(JSContext *aCtx,
|
||||||
// some special things
|
// some special things
|
||||||
if (!::js_DateIsValid(aCtx, obj))
|
if (!::js_DateIsValid(aCtx, obj))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
double msecd = ::js_DateGetMsecSinceEpoch(aCtx, obj);
|
double msecd = ::js_DateGetMsecSinceEpoch(aCtx, obj);
|
||||||
msecd *= 1000.0;
|
msecd *= 1000.0;
|
||||||
PRInt64 msec;
|
PRInt64 msec;
|
||||||
LL_D2L(msec, msecd);
|
LL_D2L(msec, msecd);
|
||||||
|
|
||||||
(void)aStatement->BindInt64Parameter(aIdx, msec);
|
(void)aStatement->BindInt64Parameter(aIdx, msec);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -176,5 +178,31 @@ bindJSValue(JSContext *aCtx,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
class CallbackEvent : public nsRunnable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CallbackEvent(mozIStorageCompletionCallback *aCallback)
|
||||||
|
: mCallback(aCallback)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHOD Run()
|
||||||
|
{
|
||||||
|
(void)mCallback->Complete();
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
nsCOMPtr<mozIStorageCompletionCallback> mCallback;
|
||||||
|
};
|
||||||
|
} // anonymous namespace
|
||||||
|
already_AddRefed<nsIRunnable>
|
||||||
|
newCompletionEvent(mozIStorageCompletionCallback *aCallback)
|
||||||
|
{
|
||||||
|
NS_ASSERTION(aCallback, "Passing a null callback is a no-no!");
|
||||||
|
nsCOMPtr<nsIRunnable> event = new CallbackEvent(aCallback);
|
||||||
|
return event.forget();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace storage
|
} // namespace storage
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
|
|
@ -49,8 +49,11 @@
|
||||||
#include "nsIVariant.h"
|
#include "nsIVariant.h"
|
||||||
#include "mozStorage.h"
|
#include "mozStorage.h"
|
||||||
#include "jsapi.h"
|
#include "jsapi.h"
|
||||||
|
#include "nsAutoPtr.h"
|
||||||
|
|
||||||
|
class mozIStorageCompletionCallback;
|
||||||
class mozIStorageStatement;
|
class mozIStorageStatement;
|
||||||
|
class nsIRunnable;
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace storage {
|
namespace storage {
|
||||||
|
@ -85,13 +88,30 @@ nsresult convertResultCode(int aSQLiteResultCode);
|
||||||
void checkAndLogStatementPerformance(sqlite3_stmt *aStatement);
|
void checkAndLogStatementPerformance(sqlite3_stmt *aStatement);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Binds a jsval to a statement at the given index.
|
||||||
*
|
*
|
||||||
|
* @param aCtx
|
||||||
|
* The JSContext jsval is associated with.
|
||||||
|
* @param aStatement
|
||||||
|
* The statement to bind to.
|
||||||
|
* @param aIdx
|
||||||
|
* The one-based index to bind aValue to.
|
||||||
|
* @param aValue
|
||||||
|
* The value to bind to aStatement.
|
||||||
|
* @return true if we bound the value to the statement, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool
|
bool bindJSValue(JSContext *aCtx, mozIStorageStatement *aStatement, int aIdx,
|
||||||
bindJSValue(JSContext *aCtx,
|
jsval aValue);
|
||||||
mozIStorageStatement *aStatement,
|
|
||||||
int aIdx,
|
/**
|
||||||
jsval aValue);
|
* Obtains an event that will notify a completion callback about completion.
|
||||||
|
*
|
||||||
|
* @param aCallback
|
||||||
|
* The callback to be notified.
|
||||||
|
* @return an nsIRunnable that can be dispatched to the calling thread.
|
||||||
|
*/
|
||||||
|
already_AddRefed<nsIRunnable>
|
||||||
|
newCompletionEvent(mozIStorageCompletionCallback *aCallback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to convert an nsIVariant to the proper SQLite type.
|
* Used to convert an nsIVariant to the proper SQLite type.
|
||||||
|
|
|
@ -236,6 +236,66 @@ function test_multiple_bindings_on_statements()
|
||||||
stmts.forEach(function(stmt) stmt.finalize());
|
stmts.forEach(function(stmt) stmt.finalize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_asyncClose_does_not_complete_before_statements()
|
||||||
|
{
|
||||||
|
let stmt = createStatement("SELECT * FROM sqlite_master");
|
||||||
|
let executed = false;
|
||||||
|
stmt.executeAsync({
|
||||||
|
handleResult: function(aResultSet)
|
||||||
|
{
|
||||||
|
},
|
||||||
|
handleError: function(aError)
|
||||||
|
{
|
||||||
|
print("Error code " + aError.result + " with message '" +
|
||||||
|
aError.message + "' returned.");
|
||||||
|
do_throw("Unexpected error!");
|
||||||
|
},
|
||||||
|
handleCompletion: function(aReason)
|
||||||
|
{
|
||||||
|
print("handleCompletion(" + aReason +
|
||||||
|
") for test_asyncClose_does_not_complete_before_statements");
|
||||||
|
do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
|
||||||
|
executed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stmt.finalize();
|
||||||
|
|
||||||
|
getOpenedDatabase().asyncClose(function() {
|
||||||
|
// Ensure that the statement executed to completion.
|
||||||
|
do_check_true(executed);
|
||||||
|
|
||||||
|
// Reset gDBConn so that later tests will get a new connection object.
|
||||||
|
gDBConn = null;
|
||||||
|
run_next_test();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_asyncClose_does_not_throw_no_callback()
|
||||||
|
{
|
||||||
|
getOpenedDatabase().asyncClose();
|
||||||
|
|
||||||
|
// Reset gDBConn so that later tests will get a new connection object.
|
||||||
|
gDBConn = null;
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_double_asyncClose_throws()
|
||||||
|
{
|
||||||
|
let conn = getOpenedDatabase();
|
||||||
|
conn.asyncClose();
|
||||||
|
try {
|
||||||
|
conn.asyncClose();
|
||||||
|
do_throw("should have thrown");
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
do_check_eq(e.result, Cr.NS_ERROR_UNEXPECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset gDBConn so that later tests will get a new connection object.
|
||||||
|
gDBConn = null;
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//// Test Runner
|
//// Test Runner
|
||||||
|
|
||||||
|
@ -244,18 +304,33 @@ let tests =
|
||||||
test_create_and_add,
|
test_create_and_add,
|
||||||
test_transaction_created,
|
test_transaction_created,
|
||||||
test_multiple_bindings_on_statements,
|
test_multiple_bindings_on_statements,
|
||||||
|
test_asyncClose_does_not_complete_before_statements,
|
||||||
|
test_asyncClose_does_not_throw_no_callback,
|
||||||
|
test_double_asyncClose_throws,
|
||||||
];
|
];
|
||||||
let index = 0;
|
let index = 0;
|
||||||
|
|
||||||
function run_next_test()
|
function run_next_test()
|
||||||
{
|
{
|
||||||
if (index < tests.length) {
|
function _run_next_test() {
|
||||||
do_test_pending();
|
if (index < tests.length) {
|
||||||
print("Running the next test: " + tests[index].name);
|
do_test_pending();
|
||||||
tests[index++]();
|
print("Running the next test: " + tests[index].name);
|
||||||
|
|
||||||
|
// Asynchronous tests means that exceptions don't kill the test.
|
||||||
|
try {
|
||||||
|
tests[index++]();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
do_throw(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
do_test_finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
do_test_finished();
|
// For saner stacks, we execute this code RSN.
|
||||||
|
do_execute_soon(_run_next_test);
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test()
|
function run_test()
|
||||||
|
|
|
@ -49,6 +49,7 @@ function test_connectionReady_open()
|
||||||
|
|
||||||
var msc = getOpenedDatabase();
|
var msc = getOpenedDatabase();
|
||||||
do_check_true(msc.connectionReady);
|
do_check_true(msc.connectionReady);
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_connectionReady_closed()
|
function test_connectionReady_closed()
|
||||||
|
@ -59,24 +60,28 @@ function test_connectionReady_closed()
|
||||||
msc.close();
|
msc.close();
|
||||||
do_check_false(msc.connectionReady);
|
do_check_false(msc.connectionReady);
|
||||||
gDBConn = null; // this is so later tests don't start to fail.
|
gDBConn = null; // this is so later tests don't start to fail.
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_databaseFile()
|
function test_databaseFile()
|
||||||
{
|
{
|
||||||
var msc = getOpenedDatabase();
|
var msc = getOpenedDatabase();
|
||||||
do_check_true(getTestDB().equals(msc.databaseFile));
|
do_check_true(getTestDB().equals(msc.databaseFile));
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_tableExists_not_created()
|
function test_tableExists_not_created()
|
||||||
{
|
{
|
||||||
var msc = getOpenedDatabase();
|
var msc = getOpenedDatabase();
|
||||||
do_check_false(msc.tableExists("foo"));
|
do_check_false(msc.tableExists("foo"));
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_indexExists_not_created()
|
function test_indexExists_not_created()
|
||||||
{
|
{
|
||||||
var msc = getOpenedDatabase();
|
var msc = getOpenedDatabase();
|
||||||
do_check_false(msc.indexExists("foo"));
|
do_check_false(msc.indexExists("foo"));
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_createTable_not_created()
|
function test_createTable_not_created()
|
||||||
|
@ -84,6 +89,7 @@ function test_createTable_not_created()
|
||||||
var msc = getOpenedDatabase();
|
var msc = getOpenedDatabase();
|
||||||
msc.createTable("test", "id INTEGER PRIMARY KEY, name TEXT");
|
msc.createTable("test", "id INTEGER PRIMARY KEY, name TEXT");
|
||||||
do_check_true(msc.tableExists("test"));
|
do_check_true(msc.tableExists("test"));
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_indexExists_created()
|
function test_indexExists_created()
|
||||||
|
@ -91,6 +97,7 @@ function test_indexExists_created()
|
||||||
var msc = getOpenedDatabase();
|
var msc = getOpenedDatabase();
|
||||||
msc.executeSimpleSQL("CREATE INDEX name_ind ON test (name)");
|
msc.executeSimpleSQL("CREATE INDEX name_ind ON test (name)");
|
||||||
do_check_true(msc.indexExists("name_ind"));
|
do_check_true(msc.indexExists("name_ind"));
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_createTable_already_created()
|
function test_createTable_already_created()
|
||||||
|
@ -103,6 +110,7 @@ function test_createTable_already_created()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
do_check_eq(Cr.NS_ERROR_FAILURE, e.result);
|
do_check_eq(Cr.NS_ERROR_FAILURE, e.result);
|
||||||
}
|
}
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_lastInsertRowID()
|
function test_lastInsertRowID()
|
||||||
|
@ -110,12 +118,14 @@ function test_lastInsertRowID()
|
||||||
var msc = getOpenedDatabase();
|
var msc = getOpenedDatabase();
|
||||||
msc.executeSimpleSQL("INSERT INTO test (name) VALUES ('foo')");
|
msc.executeSimpleSQL("INSERT INTO test (name) VALUES ('foo')");
|
||||||
do_check_eq(1, msc.lastInsertRowID);
|
do_check_eq(1, msc.lastInsertRowID);
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_transactionInProgress_no()
|
function test_transactionInProgress_no()
|
||||||
{
|
{
|
||||||
var msc = getOpenedDatabase();
|
var msc = getOpenedDatabase();
|
||||||
do_check_false(msc.transactionInProgress);
|
do_check_false(msc.transactionInProgress);
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_transactionInProgress_yes()
|
function test_transactionInProgress_yes()
|
||||||
|
@ -130,6 +140,7 @@ function test_transactionInProgress_yes()
|
||||||
do_check_true(msc.transactionInProgress);
|
do_check_true(msc.transactionInProgress);
|
||||||
msc.rollbackTransaction();
|
msc.rollbackTransaction();
|
||||||
do_check_false(msc.transactionInProgress);
|
do_check_false(msc.transactionInProgress);
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_commitTransaction_no_transaction()
|
function test_commitTransaction_no_transaction()
|
||||||
|
@ -142,6 +153,7 @@ function test_commitTransaction_no_transaction()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
do_check_eq(Cr.NS_ERROR_FAILURE, e.result);
|
do_check_eq(Cr.NS_ERROR_FAILURE, e.result);
|
||||||
}
|
}
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_rollbackTransaction_no_transaction()
|
function test_rollbackTransaction_no_transaction()
|
||||||
|
@ -154,11 +166,13 @@ function test_rollbackTransaction_no_transaction()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
do_check_eq(Cr.NS_ERROR_FAILURE, e.result);
|
do_check_eq(Cr.NS_ERROR_FAILURE, e.result);
|
||||||
}
|
}
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_get_schemaVersion_not_set()
|
function test_get_schemaVersion_not_set()
|
||||||
{
|
{
|
||||||
do_check_eq(0, getOpenedDatabase().schemaVersion);
|
do_check_eq(0, getOpenedDatabase().schemaVersion);
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_set_schemaVersion()
|
function test_set_schemaVersion()
|
||||||
|
@ -167,6 +181,7 @@ function test_set_schemaVersion()
|
||||||
const version = 1;
|
const version = 1;
|
||||||
msc.schemaVersion = version;
|
msc.schemaVersion = version;
|
||||||
do_check_eq(version, msc.schemaVersion);
|
do_check_eq(version, msc.schemaVersion);
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_set_schemaVersion_same()
|
function test_set_schemaVersion_same()
|
||||||
|
@ -175,6 +190,7 @@ function test_set_schemaVersion_same()
|
||||||
const version = 1;
|
const version = 1;
|
||||||
msc.schemaVersion = version; // should still work ok
|
msc.schemaVersion = version; // should still work ok
|
||||||
do_check_eq(version, msc.schemaVersion);
|
do_check_eq(version, msc.schemaVersion);
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_set_schemaVersion_negative()
|
function test_set_schemaVersion_negative()
|
||||||
|
@ -183,6 +199,7 @@ function test_set_schemaVersion_negative()
|
||||||
const version = -1;
|
const version = -1;
|
||||||
msc.schemaVersion = version;
|
msc.schemaVersion = version;
|
||||||
do_check_eq(version, msc.schemaVersion);
|
do_check_eq(version, msc.schemaVersion);
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_createTable(){
|
function test_createTable(){
|
||||||
|
@ -198,6 +215,7 @@ function test_createTable(){
|
||||||
do_check_true(e.result==Cr.NS_ERROR_NOT_INITIALIZED ||
|
do_check_true(e.result==Cr.NS_ERROR_NOT_INITIALIZED ||
|
||||||
e.result==Cr.NS_ERROR_FAILURE);
|
e.result==Cr.NS_ERROR_FAILURE);
|
||||||
}
|
}
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_defaultSynchronousAtNormal()
|
function test_defaultSynchronousAtNormal()
|
||||||
|
@ -212,10 +230,44 @@ function test_defaultSynchronousAtNormal()
|
||||||
stmt.reset();
|
stmt.reset();
|
||||||
stmt.finalize();
|
stmt.finalize();
|
||||||
}
|
}
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_close_succeeds_with_finalized_async_statement()
|
function test_close_does_not_spin_event_loop()
|
||||||
{
|
{
|
||||||
|
// We want to make sure that the event loop on the calling thread does not
|
||||||
|
// spin when close is called.
|
||||||
|
let event = {
|
||||||
|
ran: false,
|
||||||
|
run: function()
|
||||||
|
{
|
||||||
|
this.ran = true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Post the event before we call close, so it would run if the event loop was
|
||||||
|
// spun during close.
|
||||||
|
let thread = Cc["@mozilla.org/thread-manager;1"].
|
||||||
|
getService(Ci.nsIThreadManager).
|
||||||
|
currentThread;
|
||||||
|
thread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
|
||||||
|
|
||||||
|
// Sanity check, then close the database. Afterwards, we should not have ran!
|
||||||
|
do_check_false(event.ran);
|
||||||
|
getOpenedDatabase().close();
|
||||||
|
do_check_false(event.ran);
|
||||||
|
|
||||||
|
// Reset gDBConn so that later tests will get a new connection object.
|
||||||
|
gDBConn = null;
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_asyncClose_succeeds_with_finalized_async_statement()
|
||||||
|
{
|
||||||
|
// XXX this test isn't perfect since we can't totally control when events will
|
||||||
|
// run. If this paticular function fails randomly, it means we have a
|
||||||
|
// real bug.
|
||||||
|
|
||||||
// We want to make sure we create a cached async statement to make sure that
|
// We want to make sure we create a cached async statement to make sure that
|
||||||
// when we finalize our statement, we end up finalizing the async one too so
|
// when we finalize our statement, we end up finalizing the async one too so
|
||||||
// close will succeed.
|
// close will succeed.
|
||||||
|
@ -223,8 +275,35 @@ function test_close_succeeds_with_finalized_async_statement()
|
||||||
stmt.executeAsync();
|
stmt.executeAsync();
|
||||||
stmt.finalize();
|
stmt.finalize();
|
||||||
|
|
||||||
// Cleanup calls close, as well as removes the database file.
|
getOpenedDatabase().asyncClose(function() {
|
||||||
cleanup();
|
// Reset gDBConn so that later tests will get a new connection object.
|
||||||
|
gDBConn = null;
|
||||||
|
run_next_test();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_close_fails_with_async_statement_ran()
|
||||||
|
{
|
||||||
|
let stmt = createStatement("SELECT * FROM test");
|
||||||
|
stmt.executeAsync();
|
||||||
|
stmt.finalize();
|
||||||
|
|
||||||
|
let db = getOpenedDatabase();
|
||||||
|
try {
|
||||||
|
db.close();
|
||||||
|
do_throw("should have thrown");
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
do_check_eq(e.result, Cr.NS_ERROR_UNEXPECTED);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// Clean up after ourselves.
|
||||||
|
db.asyncClose(function() {
|
||||||
|
// Reset gDBConn so that later tests will get a new connection object.
|
||||||
|
gDBConn = null;
|
||||||
|
run_next_test();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -250,13 +329,39 @@ var tests = [
|
||||||
test_set_schemaVersion_negative,
|
test_set_schemaVersion_negative,
|
||||||
test_createTable,
|
test_createTable,
|
||||||
test_defaultSynchronousAtNormal,
|
test_defaultSynchronousAtNormal,
|
||||||
test_close_succeeds_with_finalized_async_statement,
|
test_close_does_not_spin_event_loop, // must be ran before executeAsync tests
|
||||||
|
test_asyncClose_succeeds_with_finalized_async_statement,
|
||||||
|
test_close_fails_with_async_statement_ran,
|
||||||
];
|
];
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
function run_next_test()
|
||||||
|
{
|
||||||
|
function _run_next_test() {
|
||||||
|
if (index < tests.length) {
|
||||||
|
do_test_pending();
|
||||||
|
print("Running the next test: " + tests[index].name);
|
||||||
|
|
||||||
|
// Asynchronous tests means that exceptions don't kill the test.
|
||||||
|
try {
|
||||||
|
tests[index++]();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
do_throw(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
do_test_finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
// For saner stacks, we execute this code RSN.
|
||||||
|
do_execute_soon(_run_next_test);
|
||||||
|
}
|
||||||
|
|
||||||
function run_test()
|
function run_test()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < tests.length; i++)
|
|
||||||
tests[i]();
|
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
|
do_test_pending();
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче