Bug 702559 - First implementation of mozIStorageAsyncConnection;r=mak

* * *
Bug 702559 - Implementation of transaction helper compatible with mozIStorageAsyncConnection;r=mak
This commit is contained in:
David Rajchenbach-Teller 2013-06-27 09:00:59 -04:00
parent af2068ecf9
commit d4a641ff56
17 changed files with 689 additions and 192 deletions

View File

@ -242,7 +242,7 @@ CloseDatabaseListener::CloseDatabaseListener(nsPermissionManager* aManager,
}
NS_IMETHODIMP
CloseDatabaseListener::Complete()
CloseDatabaseListener::Complete(nsresult, nsISupports*)
{
// Help breaking cycles
nsRefPtr<nsPermissionManager> manager = mManager.forget();

View File

@ -559,7 +559,7 @@ public:
nsRefPtr<DBState> mDBState;
NS_DECL_ISUPPORTS
NS_IMETHOD Complete()
NS_IMETHOD Complete(nsresult, nsISupports*)
{
gCookieService->HandleDBClosed(mDBState);
return NS_OK;

View File

@ -6,6 +6,7 @@
XPIDL_SOURCES += [
'mozIStorageAggregateFunction.idl',
'mozIStorageAsyncConnection.idl',
'mozIStorageAsyncStatement.idl',
'mozIStorageBaseStatement.idl',
'mozIStorageBindingParams.idl',

View File

@ -0,0 +1,200 @@
/* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface mozIStorageAggregateFunction;
interface mozIStorageCompletionCallback;
interface mozIStorageFunction;
interface mozIStorageProgressHandler;
interface mozIStorageBaseStatement;
interface mozIStorageStatement;
interface mozIStorageAsyncStatement;
interface mozIStorageStatementCallback;
interface mozIStoragePendingStatement;
interface nsIFile;
/**
* mozIStorageAsyncConnection represents an asynchronous database
* connection attached to a specific file or to an in-memory data
* storage. It is the primary interface for interacting with a
* database from the main thread, including creating prepared
* statements, executing SQL, and examining database errors.
*/
[scriptable, uuid(0e661a1d-27ff-4e6b-ac5a-126314cef61a)]
interface mozIStorageAsyncConnection : nsISupports {
/**
* Close this database connection, allowing all pending statements
* to complete first.
*
* @param aCallback [optional]
* A callback that will be notified when the close is completed,
* with the following arguments:
* - status: the status of the call
* - value: |null|
*
* @throws NS_ERROR_NOT_SAME_THREAD
* If is called on a thread other than the one that opened it.
*/
void asyncClose([optional] in mozIStorageCompletionCallback aCallback);
/**
* Clone a database and make the clone read only if needed.
*
* @param aReadOnly
* If true, the returned database should be put into read-only mode.
*
* @param aCallback
* A callback that will be notified when the operation is complete,
* with the following arguments:
* - status: the status of the operation
* - value: in case of success, an intance of
* mozIStorageAsyncConnection cloned from this one.
*
* @throws NS_ERROR_NOT_SAME_THREAD
* If is called on a thread other than the one that opened it.
* @throws NS_ERROR_UNEXPECTED
* If this connection is a memory database.
*
* @note If your connection is already read-only, you will get a read-only
* clone.
* @note Due to a bug in SQLite, if you use the shared cache
* (see mozIStorageService), you end up with the same privileges as the
* first connection opened regardless of what is specified in aReadOnly.
* @note The following pragmas are copied over to a read-only clone:
* - cache_size
* - temp_store
* The following pragmas are copied over to a writeable clone:
* - cache_size
* - temp_store
* - foreign_keys
* - journal_size_limit
* - synchronous
* - wal_autocheckpoint
*/
void asyncClone(in boolean aReadOnly,
in mozIStorageCompletionCallback aCallback);
/**
* The current database nsIFile. Null if the database
* connection refers to an in-memory database.
*/
readonly attribute nsIFile databaseFile;
//////////////////////////////////////////////////////////////////////////////
//// Statement creation
/**
* Create an asynchronous statement for the given SQL. An
* asynchronous statement can only be used to dispatch asynchronous
* requests to the asynchronous execution thread and cannot be used
* to take any synchronous actions on the database.
*
* The expression may use ? to indicate sequential numbered arguments,
* ?1, ?2 etc. to indicate specific numbered arguments or :name and
* $var to indicate named arguments.
*
* @param aSQLStatement
* The SQL statement to execute.
* @return a new mozIStorageAsyncStatement
* @note The statement is created lazily on first execution.
*/
mozIStorageAsyncStatement createAsyncStatement(in AUTF8String aSQLStatement);
/**
* Execute an array of statements created with this connection using
* any currently bound parameters. When the array contains multiple
* statements, the execution is wrapped in a single
* transaction. These statements can be reused immediately, and
* reset does not need to be called.
*
* @param aStatements
* The array of statements to execute asynchronously, in the order they
* are given in the array.
* @param aNumStatements
* The number of statements in aStatements.
* @param aCallback [optional]
* The callback object that will be notified of progress, errors, and
* completion.
* @return an object that can be used to cancel the statements execution.
*
* @note If you have any custom defined functions, they must be
* re-entrant since they can be called on multiple threads.
*/
mozIStoragePendingStatement executeAsync(
[array, size_is(aNumStatements)] in mozIStorageBaseStatement aStatements,
in unsigned long aNumStatements,
[optional] in mozIStorageStatementCallback aCallback
);
//////////////////////////////////////////////////////////////////////////////
//// Functions
/**
* Create a new SQL function. If you use your connection on multiple threads,
* your function needs to be threadsafe, or it should only be called on one
* thread.
*
* @param aFunctionName
* The name of function to create, as seen in SQL.
* @param aNumArguments
* The number of arguments the function takes. Pass -1 for
* variable-argument functions.
* @param aFunction
* The instance of mozIStorageFunction, which implements the function
* in question.
*/
void createFunction(in AUTF8String aFunctionName,
in long aNumArguments,
in mozIStorageFunction aFunction);
/**
* Create a new SQL aggregate function. If you use your connection on
* multiple threads, your function needs to be threadsafe, or it should only
* be called on one thread.
*
* @param aFunctionName
* The name of aggregate function to create, as seen in SQL.
* @param aNumArguments
* The number of arguments the function takes. Pass -1 for
* variable-argument functions.
* @param aFunction
* The instance of mozIStorageAggreagteFunction, which implements the
* function in question.
*/
void createAggregateFunction(in AUTF8String aFunctionName,
in long aNumArguments,
in mozIStorageAggregateFunction aFunction);
/**
* Delete custom SQL function (simple or aggregate one).
*
* @param aFunctionName
* The name of function to remove.
*/
void removeFunction(in AUTF8String aFunctionName);
/**
* Sets a progress handler. Only one handler can be registered at a time.
* If you need more than one, you need to chain them yourself. This progress
* handler should be threadsafe if you use this connection object on more than
* one thread.
*
* @param aGranularity
* The number of SQL virtual machine steps between progress handler
* callbacks.
* @param aHandler
* The instance of mozIStorageProgressHandler.
* @return previous registered handler.
*/
mozIStorageProgressHandler setProgressHandler(in int32_t aGranularity,
in mozIStorageProgressHandler aHandler);
/**
* Remove a progress handler.
*
* @return previous registered handler.
*/
mozIStorageProgressHandler removeProgressHandler();
};

View File

@ -6,10 +6,19 @@
#include "nsISupports.idl"
[scriptable,function, uuid(0bfee0c4-2c24-400e-b18e-b5bb41a032c8)]
[scriptable, function, uuid(8cbf2dc2-91e0-44bc-984f-553638412071)]
interface mozIStorageCompletionCallback : nsISupports {
/**
* Indicates that the event this callback was passed in for has completed.
*
* @param status
* The status of the call. Generally NS_OK if the operation
* completed successfully.
* @param value
* If the operation produces a result, the result. Otherwise,
* |null|.
*
* @see The calling method for expected values.
*/
void complete();
void complete(in nsresult status, [optional] in nsISupports value);
};

View File

@ -4,6 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
#include "mozIStorageAsyncConnection.idl"
interface mozIStorageAggregateFunction;
interface mozIStorageCompletionCallback;
@ -23,10 +24,12 @@ interface nsIFile;
* creating prepared statements, executing SQL, and examining database
* errors.
*
* @note From the main thread, you should rather use mozIStorageAsyncConnection.
*
* @threadsafe
*/
[scriptable, uuid(c8646e4b-3e2d-4df3-98a9-e2bbf74f279c)]
interface mozIStorageConnection : nsISupports {
[scriptable, uuid(4aa2ac47-8d24-4004-9b31-ec0bd85f0cc3)]
interface mozIStorageConnection : mozIStorageAsyncConnection {
/**
* Closes a database connection. Callers must finalize all statements created
* for this connection prior to calling this method. It is illegal to use
@ -41,20 +44,15 @@ interface mozIStorageConnection : nsISupports {
void close();
/**
* Asynchronously closes a database connection, allowing all pending
* asynchronous statements to complete first.
* Clones a database connection and makes the clone read only if needed.
*
* @param aCallback [optional]
* A callback that will be notified when the close is completed.
* @param aReadOnly
* If true, the returned database should be put into read-only mode.
* Defaults to false.
* @return the cloned database connection.
*
* @throws NS_ERROR_UNEXPECTED
* If is called on a thread other than the one that opened it.
*/
void asyncClose([optional] in mozIStorageCompletionCallback aCallback);
/**
* Clones a database and makes the clone read only if needed.
*
* If this connection is a memory database.
* @note If your connection is already read-only, you will get a read-only
* clone.
* @note Due to a bug in SQLite, if you use the shared cache (openDatabase),
@ -71,13 +69,6 @@ interface mozIStorageConnection : nsISupports {
* - synchronous
* - wal_autocheckpoint
*
* @throws NS_ERROR_UNEXPECTED
* If this connection is a memory database.
*
* @param aReadOnly
* If true, the returned database should be put into read-only mode.
* Defaults to false.
* @return the cloned database connection.
*/
mozIStorageConnection clone([optional] in boolean aReadOnly);
@ -93,12 +84,6 @@ interface mozIStorageConnection : nsISupports {
*/
readonly attribute boolean connectionReady;
/**
* The current database nsIFile. Null if the database
* connection refers to an in-memory database.
*/
readonly attribute nsIFile databaseFile;
/**
* lastInsertRowID returns the row ID from the last INSERT
* operation.
@ -143,22 +128,6 @@ interface mozIStorageConnection : nsISupports {
*/
mozIStorageStatement createStatement(in AUTF8String aSQLStatement);
/**
* Create an asynchronous statement (mozIStorageAsyncStatement) for the given
* SQL expression. An asynchronous statement can only be used to dispatch
* asynchronous requests to the asynchronous execution thread and cannot be
* used to take any synchronous actions on the database.
*
* The expression may use ? to indicate sequential numbered arguments,
* ?1, ?2 etc. to indicate specific numbered arguments or :name and
* $var to indicate named arguments.
*
* @param aSQLStatement
* The SQL statement to execute.
* @return a new mozIStorageAsyncStatement
*/
mozIStorageAsyncStatement createAsyncStatement(in AUTF8String aSQLStatement);
/**
* Execute a SQL expression, expecting no arguments.
*
@ -166,31 +135,6 @@ interface mozIStorageConnection : nsISupports {
*/
void executeSimpleSQL(in AUTF8String aSQLStatement);
/**
* Execute an array of queries created with this connection asynchronously
* using any currently bound parameters. The statements are ran wrapped in a
* transaction. These statements can be reused immediately, and reset does
* not need to be called.
*
* Note: If you have any custom defined functions, they must be re-entrant
* since they can be called on multiple threads.
*
* @param aStatements
* The array of statements to execute asynchronously, in the order they
* are given in the array.
* @param aNumStatements
* The number of statements in aStatements.
* @param aCallback [optional]
* The callback object that will be notified of progress, errors, and
* completion.
* @return an object that can be used to cancel the statements execution.
*/
mozIStoragePendingStatement executeAsync(
[array, size_is(aNumStatements)] in mozIStorageBaseStatement aStatements,
in unsigned long aNumStatements,
[optional] in mozIStorageStatementCallback aCallback
);
/**
* Check if the given table exists.
*
@ -268,75 +212,6 @@ interface mozIStorageConnection : nsISupports {
void createTable(in string aTableName,
in string aTableSchema);
//////////////////////////////////////////////////////////////////////////////
//// Functions
/**
* Create a new SQL function. If you use your connection on multiple threads,
* your function needs to be threadsafe, or it should only be called on one
* thread.
*
* @param aFunctionName
* The name of function to create, as seen in SQL.
* @param aNumArguments
* The number of arguments the function takes. Pass -1 for
* variable-argument functions.
* @param aFunction
* The instance of mozIStorageFunction, which implements the function
* in question.
*/
void createFunction(in AUTF8String aFunctionName,
in long aNumArguments,
in mozIStorageFunction aFunction);
/**
* Create a new SQL aggregate function. If you use your connection on
* multiple threads, your function needs to be threadsafe, or it should only
* be called on one thread.
*
* @param aFunctionName
* The name of aggregate function to create, as seen in SQL.
* @param aNumArguments
* The number of arguments the function takes. Pass -1 for
* variable-argument functions.
* @param aFunction
* The instance of mozIStorageAggreagteFunction, which implements the
* function in question.
*/
void createAggregateFunction(in AUTF8String aFunctionName,
in long aNumArguments,
in mozIStorageAggregateFunction aFunction);
/**
* Delete custom SQL function (simple or aggregate one).
*
* @param aFunctionName
* The name of function to remove.
*/
void removeFunction(in AUTF8String aFunctionName);
/**
* Sets a progress handler. Only one handler can be registered at a time.
* If you need more than one, you need to chain them yourself. This progress
* handler should be threadsafe if you use this connection object on more than
* one thread.
*
* @param aGranularity
* The number of SQL virtual machine steps between progress handler
* callbacks.
* @param aHandler
* The instance of mozIStorageProgressHandler.
* @return previous registered handler.
*/
mozIStorageProgressHandler setProgressHandler(in int32_t aGranularity,
in mozIStorageProgressHandler aHandler);
/**
* Remove a progress handler.
*
* @return previous registered handler.
*/
mozIStorageProgressHandler removeProgressHandler();
/**
* Controls SQLITE_FCNTL_CHUNK_SIZE setting in sqlite. This helps avoid fragmentation
* by growing/shrinking the database file in SQLITE_FCNTL_CHUNK_SIZE increments. To

View File

@ -8,6 +8,9 @@
interface mozIStorageConnection;
interface nsIFile;
interface nsIFileURL;
interface nsIPropertyBag2;
interface nsIVariant;
interface mozIStorageCompletionCallback;
/**
* The mozIStorageService interface is intended to be implemented by
@ -19,8 +22,56 @@ interface nsIFileURL;
* @note The first reference to mozIStorageService must be made on the main
* thread.
*/
[scriptable, uuid(12bfad34-cca3-40fb-8736-d8bf9db61a27)]
[scriptable, uuid(07b6b2f5-6d97-47b4-9584-e65bc467fe9e)]
interface mozIStorageService : nsISupports {
/**
* Open an asynchronous connection to a database.
*
* This method MUST be called from the main thread. The connection object
* returned by this function is not threadsafe. You MUST use it only from
* the main thread.
*
* If you have more than one connection to a file, you MUST use the EXACT
* SAME NAME for the file each time, including case. The sqlite code uses
* a simple string compare to see if there is already a connection. Opening
* a connection to "Foo.sqlite" and "foo.sqlite" will CORRUPT YOUR DATABASE.
*
* @param aDatabaseStore Either a nsIFile representing the file that contains
* the database or a special string to open a special database. The special
* string may be:
* - "memory" to open an in-memory database.
*
* @param aOptions A set of options (may be null). Options may contain:
* - bool shared (defaults to |false|).
* -- If |true|, opens the database with a shared-cache. The
* shared-cache mode is more memory-efficient when many
* connections to the same database are expected, though, the
* connections will contend the cache resource. In any cases
* where performance matter, working without a shared-cache will
* improve concurrency. @see openUnsharedDatabase
*
* - int growthIncrement (defaults to none).
* -- Set the growth increment for the main database. This hints SQLite to
* grow the database file by a given chunk size and may reduce
* filesystem fragmentation on large databases.
* @see mozIStorageConnection::setGrowthIncrement
*
* @param aCallback A callback that will receive the result of the operation.
* In case of error, it may receive as status:
* - NS_ERROR_OUT_OF_MEMORY if allocating a new storage object fails.
* - NS_ERROR_FILE_CORRUPTED if the database file is corrupted.
* In case of success, it receives as argument the new database
* connection, as an instance of |mozIStorageAsyncConnection|.
*
* @throws NS_ERROR_INVALID_ARG if |aDatabaseStore| is neither a file nor
* one of the special strings understood by this method, or if one of
* the options passed through |aOptions| does not have the right type.
* @throws NS_ERROR_NOT_SAME_THREAD if called from a thread other than the
* main thread.
*/
void openAsyncDatabase(in nsIVariant aDatabaseStore,
[optional] in nsIPropertyBag2 aOptions,
in mozIStorageCompletionCallback aCallback);
/**
* Get a connection to a named special database storage.
*
@ -58,12 +109,6 @@ interface mozIStorageService : nsISupports {
* The connection object returned by this function is not threadsafe. You must
* use it only from the thread you created it from.
*
* If your database contains virtual tables (f.e. for full-text indexes), you
* must open it with openUnsharedDatabase, as those tables are incompatible
* with a shared cache. If you attempt to use this method to open a database
* containing virtual tables, it will think the database is corrupted and
* throw NS_ERROR_FILE_CORRUPTED.
*
* @param aDatabaseFile
* A nsIFile that represents the database that is to be opened..
*
@ -79,12 +124,11 @@ interface mozIStorageService : nsISupports {
/**
* Open a connection to the specified file that doesn't share a sqlite cache.
*
* Each connection uses its own sqlite cache, which is inefficient, so you
* should use openDatabase instead of this method unless you need a feature
* of SQLite that is incompatible with a shared cache, like virtual table
* and full text indexing support. If cache contention is expected, for
* instance when operating on a database from multiple threads, using
* unshared connections may be a performance win.
* Without a shared-cache, each connection uses its own pages cache, which
* may be memory inefficient with a large number of connections, in such a
* case so you should use openDatabase instead. On the other side, if cache
* contention may be an issue, for instance when concurrency is important to
* ensure responsiveness, using unshared connections may be a performance win.
*
* ==========
* DANGER

View File

@ -8,11 +8,11 @@
#include "nsAutoPtr.h"
#include "mozIStorageAsyncConnection.h"
#include "mozIStorageConnection.h"
#include "mozIStorageStatement.h"
#include "nsError.h"
/**
* This class wraps a transaction inside a given C++ scope, guaranteeing that
* the transaction will be completed even if you have an exception or
@ -27,11 +27,16 @@
* is already in progress, this object does nothing. Note that in this case,
* you may not get the transaction type you ask for, and you won't be able
* to rollback.
*
* Note: This class is templatized to be also usable with internal data
* structures. External users of this class should generally use
* |mozStorageTransaction| instead.
*/
class mozStorageTransaction
template<typename T, typename U>
class mozStorageTransactionBase
{
public:
mozStorageTransaction(mozIStorageConnection* aConnection,
mozStorageTransactionBase(T* aConnection,
bool aCommitOnComplete,
int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED)
: mConnection(aConnection),
@ -43,7 +48,7 @@ public:
if (mConnection)
mHasTransaction = NS_SUCCEEDED(mConnection->BeginTransactionAs(aType));
}
~mozStorageTransaction()
~mozStorageTransactionBase()
{
if (mConnection && mHasTransaction && ! mCompleted) {
if (mCommitOnComplete)
@ -119,12 +124,21 @@ public:
}
protected:
nsCOMPtr<mozIStorageConnection> mConnection;
U mConnection;
bool mHasTransaction;
bool mCommitOnComplete;
bool mCompleted;
};
/**
* An instance of the mozStorageTransaction<> family dedicated
* to |mozIStorageConnection|.
*/
typedef mozStorageTransactionBase<mozIStorageConnection,
nsCOMPtr<mozIStorageConnection> >
mozStorageTransaction;
/**
* This class wraps a statement so that it is guaraneed to be reset when

View File

@ -27,6 +27,7 @@
#include "mozIStorageVacuumParticipant.h"
#include "mozIStorageCompletionCallback.h"
#include "mozIStorageAsyncStatement.h"
#include "mozIStorageAsyncConnection.h"
////////////////////////////////////////////////////////////////////////////////
//// Native Language Helpers

View File

@ -577,7 +577,8 @@ AsyncExecuteStatements::Run()
return notifyComplete();
if (statementsNeedTransaction()) {
mTransactionManager = new mozStorageTransaction(mConnection, false,
Connection* rawConnection = static_cast<Connection*>(mConnection.get());
mTransactionManager = new mozStorageAsyncTransaction(rawConnection, false,
mozIStorageConnection::TRANSACTION_IMMEDIATE);
}

View File

@ -18,9 +18,9 @@
#include "SQLiteMutex.h"
#include "mozIStoragePendingStatement.h"
#include "mozIStorageStatementCallback.h"
#include "mozStorageHelper.h"
struct sqlite3_stmt;
class mozStorageTransaction;
namespace mozilla {
namespace storage {
@ -29,6 +29,14 @@ class Connection;
class ResultSet;
class StatementData;
/**
* An instance of the mozStorageTransaction<> family dedicated
* to concrete class |Connection|.
*/
typedef mozStorageTransactionBase<mozilla::storage::Connection,
nsRefPtr<mozilla::storage::Connection> >
mozStorageAsyncTransaction;
class AsyncExecuteStatements MOZ_FINAL : public nsIRunnable
, public mozIStoragePendingStatement
{
@ -179,7 +187,7 @@ private:
StatementDataArray mStatements;
nsRefPtr<Connection> mConnection;
mozStorageTransaction *mTransactionManager;
mozStorageAsyncTransaction *mTransactionManager;
mozIStorageStatementCallback *mCallback;
nsCOMPtr<nsIThread> mCallingThread;
nsRefPtr<ResultSet> mResultSet;

View File

@ -39,6 +39,7 @@
#include "prlog.h"
#include "prprf.h"
#include "nsProxyRelease.h"
#include <algorithm>
#define MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH 524288000 // 500 MiB
@ -364,6 +365,7 @@ public:
(void)mConnection->internalClose();
if (mCallbackEvent)
(void)mCallingThread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL);
(void)mAsyncExecutionThread->Shutdown();
// Because we have no guarantee that the invocation of this method on the
@ -387,13 +389,91 @@ private:
nsCOMPtr<nsIThread> mAsyncExecutionThread;
};
/**
* An event used to initialize the clone of a connection.
*
* Must be executed on the clone's async execution thread.
*/
class AsyncInitializeClone MOZ_FINAL: public nsRunnable
{
public:
/**
* @param aConnection The connection being cloned.
* @param aClone The clone.
* @param aReadOnly If |true|, the clone is read only.
* @param aCallback A callback to trigger once initialization
* is complete. This event will be called on
* aClone->threadOpenedOn.
*/
AsyncInitializeClone(Connection* aConnection,
Connection* aClone,
const bool aReadOnly,
mozIStorageCompletionCallback* aCallback)
: mConnection(aConnection)
, mClone(aClone)
, mReadOnly(aReadOnly)
, mCallback(aCallback)
{
MOZ_ASSERT(NS_IsMainThread());
}
NS_IMETHOD Run() {
MOZ_ASSERT (NS_GetCurrentThread() == mClone->getAsyncExecutionTarget());
nsresult rv = mConnection->initializeClone(mClone, mReadOnly);
if (NS_FAILED(rv)) {
return Dispatch(rv, nullptr);
}
return Dispatch(NS_OK,
NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mClone));
}
private:
nsresult Dispatch(nsresult aResult, nsISupports* aValue) {
nsRefPtr<CallbackComplete> event = new CallbackComplete(aResult,
aValue,
mCallback.forget());
return mClone->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
}
~AsyncInitializeClone() {
nsCOMPtr<nsIThread> thread;
DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
MOZ_ASSERT(NS_SUCCEEDED(rv));
// Handle ambiguous nsISupports inheritance.
Connection *rawConnection = nullptr;
mConnection.swap(rawConnection);
(void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *,
rawConnection));
Connection *rawClone = nullptr;
mClone.swap(rawClone);
(void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *,
rawClone));
// Generally, the callback will be released by CallbackComplete.
// However, if for some reason Run() is not executed, we still
// need to ensure that it is released here.
mozIStorageCompletionCallback *rawCallback = nullptr;
mCallback.swap(rawCallback);
(void)NS_ProxyRelease(thread, rawCallback);
}
nsRefPtr<Connection> mConnection;
nsRefPtr<Connection> mClone;
const bool mReadOnly;
nsCOMPtr<mozIStorageCompletionCallback> mCallback;
};
} // anonymous namespace
////////////////////////////////////////////////////////////////////////////////
//// Connection
Connection::Connection(Service *aService,
int aFlags)
int aFlags,
bool aAsyncOnly)
: sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex")
, sharedDBMutex("Connection::sharedDBMutex")
, threadOpenedOn(do_GetCurrentThread())
@ -403,6 +483,7 @@ Connection::Connection(Service *aService,
, mProgressHandler(nullptr)
, mFlags(aFlags)
, mStorageService(aService)
, mAsyncOnly(aAsyncOnly)
{
mFunctions.Init();
mStorageService->registerConnection(this);
@ -412,15 +493,18 @@ Connection::~Connection()
{
(void)Close();
MOZ_ASSERT(!mAsyncExecutionThread);
MOZ_ASSERT(!mAsyncExecutionThread,
"AsyncClose has not been invoked on this connection!");
}
NS_IMPL_THREADSAFE_ADDREF(Connection)
NS_IMPL_THREADSAFE_QUERY_INTERFACE2(
Connection,
mozIStorageConnection,
nsIInterfaceRequestor
)
NS_INTERFACE_MAP_BEGIN(Connection)
NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncConnection)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(mozIStorageConnection, !mAsyncOnly)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageConnection)
NS_INTERFACE_MAP_END
// This is identical to what NS_IMPL_THREADSAFE_RELEASE provides, but with the
// extra |1 == count| case.
@ -436,8 +520,9 @@ NS_IMETHODIMP_(nsrefcnt) Connection::Release(void)
mStorageService->unregisterConnection(this);
} else if (0 == count) {
mRefCnt = 1; /* stabilize */
/* enable this to find non-threadsafe destructors: */
/* NS_ASSERT_OWNINGTHREAD(Connection); */
#if 0 /* enable this to find non-threadsafe destructors: */
NS_ASSERT_OWNINGTHREAD(Connection);
#endif
delete (this);
return 0;
}
@ -952,6 +1037,9 @@ Connection::Close()
NS_IMETHODIMP
Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
{
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
if (!mDBConn)
return NS_ERROR_NOT_INITIALIZED;
@ -984,10 +1072,13 @@ Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
}
NS_IMETHODIMP
Connection::Clone(bool aReadOnly,
mozIStorageConnection **_connection)
Connection::AsyncClone(bool aReadOnly,
mozIStorageCompletionCallback *aCallback)
{
PROFILER_LABEL("storage", "Connection::Clone");
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
if (!mDBConn)
return NS_ERROR_NOT_INITIALIZED;
if (!mDatabaseFile)
@ -1000,12 +1091,27 @@ Connection::Clone(bool aReadOnly,
// Turn off SQLITE_OPEN_CREATE.
flags = (~SQLITE_OPEN_CREATE & flags);
}
nsRefPtr<Connection> clone = new Connection(mStorageService, flags);
NS_ENSURE_TRUE(clone, NS_ERROR_OUT_OF_MEMORY);
nsresult rv = mFileURL ? clone->initialize(mFileURL)
: clone->initialize(mDatabaseFile);
NS_ENSURE_SUCCESS(rv, rv);
nsRefPtr<Connection> clone = new Connection(mStorageService, flags,
mAsyncOnly);
nsRefPtr<AsyncInitializeClone> initEvent =
new AsyncInitializeClone(this, clone, aReadOnly, aCallback);
nsCOMPtr<nsIEventTarget> target = clone->getAsyncExecutionTarget();
if (!target) {
return NS_ERROR_UNEXPECTED;
}
return target->Dispatch(initEvent, NS_DISPATCH_NORMAL);
}
nsresult
Connection::initializeClone(Connection* aClone, bool aReadOnly)
{
nsresult rv = mFileURL ? aClone->initialize(mFileURL)
: aClone->initialize(mDatabaseFile);
if (NS_FAILED(rv)) {
return rv;
}
// Copy over pragmas from the original connection.
static const char * pragmas[] = {
@ -1032,16 +1138,47 @@ Connection::Clone(bool aReadOnly,
if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
pragmaQuery.AppendLiteral(" = ");
pragmaQuery.AppendInt(stmt->AsInt32(0));
rv = clone->ExecuteSimpleSQL(pragmaQuery);
rv = aClone->ExecuteSimpleSQL(pragmaQuery);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
// Copy any functions that have been added to this connection.
SQLiteMutexAutoLock lockedScope(sharedDBMutex);
(void)mFunctions.EnumerateRead(copyFunctionEnumerator, clone);
(void)mFunctions.EnumerateRead(copyFunctionEnumerator, aClone);
NS_ADDREF(*_connection = clone);
return NS_OK;
}
NS_IMETHODIMP
Connection::Clone(bool aReadOnly,
mozIStorageConnection **_connection)
{
MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
PROFILER_LABEL("storage", "Connection::Clone");
if (!mDBConn)
return NS_ERROR_NOT_INITIALIZED;
if (!mDatabaseFile)
return NS_ERROR_UNEXPECTED;
int flags = mFlags;
if (aReadOnly) {
// Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY.
flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY;
// Turn off SQLITE_OPEN_CREATE.
flags = (~SQLITE_OPEN_CREATE & flags);
}
nsRefPtr<Connection> clone = new Connection(mStorageService, flags,
mAsyncOnly);
nsresult rv = initializeClone(clone, aReadOnly);
if (NS_FAILED(rv)) {
return rv;
}
NS_IF_ADDREF(*_connection = clone);
return NS_OK;
}

View File

@ -10,6 +10,8 @@
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "mozilla/Mutex.h"
#include "nsProxyRelease.h"
#include "nsThreadUtils.h"
#include "nsIInterfaceRequestor.h"
#include "nsDataHashtable.h"
@ -17,6 +19,8 @@
#include "SQLiteMutex.h"
#include "mozIStorageConnection.h"
#include "mozStorageService.h"
#include "mozIStorageAsyncConnection.h"
#include "mozIStorageCompletionCallback.h"
#include "nsIMutableArray.h"
#include "mozilla/Attributes.h"
@ -37,6 +41,7 @@ class Connection MOZ_FINAL : public mozIStorageConnection
{
public:
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGEASYNCCONNECTION
NS_DECL_MOZISTORAGECONNECTION
NS_DECL_NSIINTERFACEREQUESTOR
@ -60,8 +65,13 @@ public:
* connection.
* @param aFlags
* The flags to pass to sqlite3_open_v2.
* @param aAsyncOnly
* If |true|, the Connection only implements asynchronous interface:
* - |mozIStorageAsyncConnection|;
* If |false|, the result also implements synchronous interface:
* - |mozIStorageConnection|.
*/
Connection(Service *aService, int aFlags);
Connection(Service *aService, int aFlags, bool aAsyncOnly);
/**
* Creates the connection to an in-memory database.
@ -162,9 +172,11 @@ public:
*/
bool isAsyncClosing();
nsresult initializeClone(Connection *aClone, bool aReadOnly);
private:
~Connection();
nsresult initializeInternal(nsIFile *aDatabaseFile);
/**
@ -262,6 +274,54 @@ private:
// connections do not outlive the service. 2) Our custom collating functions
// call its localeCompareStrings() method.
nsRefPtr<Service> mStorageService;
/**
* If |false|, this instance supports synchronous operations
* and it can be cast to |mozIStorageConnection|.
*/
const bool mAsyncOnly;
};
/**
* A Runnable designed to call a mozIStorageCompletionCallback on
* the appropriate thread.
*/
class CallbackComplete MOZ_FINAL : public nsRunnable
{
public:
/**
* @param aValue The result to pass to the callback. It must
* already be owned by the main thread.
* @param aCallback The callback. It must already be owned by the
* main thread.
*/
CallbackComplete(nsresult aStatus,
nsISupports* aValue,
already_AddRefed<mozIStorageCompletionCallback> aCallback)
: mStatus(aStatus)
, mValue(aValue)
, mCallback(aCallback)
{
}
NS_IMETHOD Run() {
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = mCallback->Complete(mStatus, mValue);
// Ensure that we release on the main thread
mValue = nullptr;
mCallback = nullptr;
return rv;
}
private:
nsresult mStatus;
nsCOMPtr<nsISupports> mValue;
// This is a nsRefPtr<T> and not a nsCOMPtr<T> because
// nsCOMP<T> would cause an off-main thread QI, which
// is not a good idea (and crashes XPConnect).
nsRefPtr<mozIStorageCompletionCallback> mCallback;
};
} // namespace storage

View File

@ -161,7 +161,7 @@ public:
NS_IMETHOD Run()
{
(void)mCallback->Complete();
(void)mCallback->Complete(NS_OK, nullptr);
return NS_OK;
}
private:

View File

@ -20,9 +20,11 @@
#include "nsILocaleService.h"
#include "nsIXPConnect.h"
#include "nsIObserverService.h"
#include "nsIPropertyBag2.h"
#include "mozilla/Services.h"
#include "mozilla/Preferences.h"
#include "mozilla/mozPoisonWrite.h"
#include "mozIStorageCompletionCallback.h"
#include "sqlite3.h"
@ -644,7 +646,7 @@ Service::OpenSpecialDatabase(const char *aStorageKey,
return NS_ERROR_INVALID_ARG;
}
nsRefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE);
nsRefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false);
rv = storageFile ? msc->initialize(storageFile) : msc->initialize();
NS_ENSURE_SUCCESS(rv, rv);
@ -654,6 +656,151 @@ Service::OpenSpecialDatabase(const char *aStorageKey,
}
namespace {
class AsyncInitDatabase MOZ_FINAL : public nsRunnable
{
public:
AsyncInitDatabase(Connection* aConnection,
nsIFile* aStorageFile,
int32_t aGrowthIncrement,
mozIStorageCompletionCallback* aCallback)
: mConnection(aConnection)
, mStorageFile(aStorageFile)
, mGrowthIncrement(aGrowthIncrement)
, mCallback(aCallback)
{
MOZ_ASSERT(NS_IsMainThread());
}
NS_IMETHOD Run()
{
MOZ_ASSERT(!NS_IsMainThread());
nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile)
: mConnection->initialize();
if (NS_FAILED(rv)) {
return DispatchResult(rv, nullptr);
}
if (mGrowthIncrement >= 0) {
// Ignore errors. In the future, we might wish to log them.
(void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString());
}
return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*,
mConnection));
}
private:
nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
nsRefPtr<CallbackComplete> event =
new CallbackComplete(aStatus,
aValue,
mCallback.forget());
return NS_DispatchToMainThread(event);
}
~AsyncInitDatabase()
{
nsCOMPtr<nsIThread> thread;
DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
MOZ_ASSERT(NS_SUCCEEDED(rv));
(void)NS_ProxyRelease(thread, mStorageFile);
// Handle ambiguous nsISupports inheritance.
Connection *rawConnection = nullptr;
mConnection.swap(rawConnection);
(void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *,
rawConnection));
// Generally, the callback will be released by CallbackComplete.
// However, if for some reason Run() is not executed, we still
// need to ensure that it is released here.
mozIStorageCompletionCallback *rawCallback = nullptr;
mCallback.swap(rawCallback);
(void)NS_ProxyRelease(thread, rawCallback);
}
nsRefPtr<Connection> mConnection;
nsCOMPtr<nsIFile> mStorageFile;
int32_t mGrowthIncrement;
nsRefPtr<mozIStorageCompletionCallback> mCallback;
};
} // anonymous namespace
NS_IMETHODIMP
Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore,
nsIPropertyBag2 *aOptions,
mozIStorageCompletionCallback *aCallback)
{
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
NS_ENSURE_ARG(aDatabaseStore);
NS_ENSURE_ARG(aCallback);
nsCOMPtr<nsIFile> storageFile;
int flags = SQLITE_OPEN_READWRITE;
nsCOMPtr<nsISupports> dbStore;
nsresult rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
if (NS_SUCCEEDED(rv)) {
// Generally, aDatabaseStore holds the database nsIFile.
storageFile = do_QueryInterface(dbStore, &rv);
if (NS_FAILED(rv)) {
return NS_ERROR_INVALID_ARG;
}
rv = storageFile->Clone(getter_AddRefs(storageFile));
MOZ_ASSERT(NS_SUCCEEDED(rv));
// Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
flags |= SQLITE_OPEN_CREATE;
// Extract and apply the shared-cache option.
bool shared = false;
if (aOptions) {
rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared);
if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
return NS_ERROR_INVALID_ARG;
}
}
flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
} else {
// Sometimes, however, it's a special database name.
nsAutoCString keyString;
rv = aDatabaseStore->GetAsACString(keyString);
if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) {
return NS_ERROR_INVALID_ARG;
}
// Just fall through with NULL storageFile, this will cause the storage
// connection to use a memory DB.
}
int32_t growthIncrement = -1;
if (aOptions && storageFile) {
rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"),
&growthIncrement);
if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
return NS_ERROR_INVALID_ARG;
}
}
// Create connection on this thread, but initialize it on its helper thread.
nsRefPtr<Connection> msc = new Connection(this, flags, true);
nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already");
nsRefPtr<AsyncInitDatabase> asyncInit =
new AsyncInitDatabase(msc,
storageFile,
growthIncrement,
aCallback);
return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
}
NS_IMETHODIMP
Service::OpenDatabase(nsIFile *aDatabaseFile,
mozIStorageConnection **_connection)
@ -664,7 +811,7 @@ Service::OpenDatabase(nsIFile *aDatabaseFile,
// reasons.
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
SQLITE_OPEN_CREATE;
nsRefPtr<Connection> msc = new Connection(this, flags);
nsRefPtr<Connection> msc = new Connection(this, flags, false);
nsresult rv = msc->initialize(aDatabaseFile);
NS_ENSURE_SUCCESS(rv, rv);
@ -683,7 +830,7 @@ Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile,
// reasons.
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE |
SQLITE_OPEN_CREATE;
nsRefPtr<Connection> msc = new Connection(this, flags);
nsRefPtr<Connection> msc = new Connection(this, flags, false);
nsresult rv = msc->initialize(aDatabaseFile);
NS_ENSURE_SUCCESS(rv, rv);
@ -702,7 +849,7 @@ Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL,
// reasons.
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
nsRefPtr<Connection> msc = new Connection(this, flags);
nsRefPtr<Connection> msc = new Connection(this, flags, false);
nsresult rv = msc->initialize(aFileURL);
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -176,7 +176,7 @@ AsyncStatementSpinner::HandleCompletion(uint16_t aReason)
}
NS_IMETHODIMP
AsyncStatementSpinner::Complete()
AsyncStatementSpinner::Complete(nsresult, nsISupports*)
{
mCompleted = true;
return NS_OK;

View File

@ -222,7 +222,7 @@ public:
};
NS_IMETHODIMP
BlockingConnectionCloseCallback::Complete()
BlockingConnectionCloseCallback::Complete(nsresult, nsISupports*)
{
mDone = true;
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();