From d4a641ff5617b7ac18f99a95aa75b1450512e295 Mon Sep 17 00:00:00 2001 From: David Rajchenbach-Teller Date: Thu, 27 Jun 2013 09:00:59 -0400 Subject: [PATCH] Bug 702559 - First implementation of mozIStorageAsyncConnection;r=mak * * * Bug 702559 - Implementation of transaction helper compatible with mozIStorageAsyncConnection;r=mak --- extensions/cookie/nsPermissionManager.cpp | 2 +- netwerk/cookie/nsCookieService.cpp | 2 +- storage/public/moz.build | 1 + storage/public/mozIStorageAsyncConnection.idl | 200 ++++++++++++++++++ .../public/mozIStorageCompletionCallback.idl | 13 +- storage/public/mozIStorageConnection.idl | 147 +------------ storage/public/mozIStorageService.idl | 70 ++++-- storage/public/mozStorageHelper.h | 28 ++- storage/public/storage.h | 1 + .../src/mozStorageAsyncStatementExecution.cpp | 5 +- .../src/mozStorageAsyncStatementExecution.h | 12 +- storage/src/mozStorageConnection.cpp | 175 +++++++++++++-- storage/src/mozStorageConnection.h | 64 +++++- storage/src/mozStoragePrivateHelpers.cpp | 2 +- storage/src/mozStorageService.cpp | 155 +++++++++++++- storage/test/storage_test_harness.h | 2 +- toolkit/components/places/Database.cpp | 2 +- 17 files changed, 689 insertions(+), 192 deletions(-) create mode 100644 storage/public/mozIStorageAsyncConnection.idl diff --git a/extensions/cookie/nsPermissionManager.cpp b/extensions/cookie/nsPermissionManager.cpp index b86ad07402c..74a06ea2fd6 100644 --- a/extensions/cookie/nsPermissionManager.cpp +++ b/extensions/cookie/nsPermissionManager.cpp @@ -242,7 +242,7 @@ CloseDatabaseListener::CloseDatabaseListener(nsPermissionManager* aManager, } NS_IMETHODIMP -CloseDatabaseListener::Complete() +CloseDatabaseListener::Complete(nsresult, nsISupports*) { // Help breaking cycles nsRefPtr manager = mManager.forget(); diff --git a/netwerk/cookie/nsCookieService.cpp b/netwerk/cookie/nsCookieService.cpp index 0c10162575b..4c3c1a25edd 100644 --- a/netwerk/cookie/nsCookieService.cpp +++ b/netwerk/cookie/nsCookieService.cpp @@ -559,7 +559,7 @@ public: nsRefPtr mDBState; NS_DECL_ISUPPORTS - NS_IMETHOD Complete() + NS_IMETHOD Complete(nsresult, nsISupports*) { gCookieService->HandleDBClosed(mDBState); return NS_OK; diff --git a/storage/public/moz.build b/storage/public/moz.build index 2cfd6d21497..6343f7a0473 100644 --- a/storage/public/moz.build +++ b/storage/public/moz.build @@ -6,6 +6,7 @@ XPIDL_SOURCES += [ 'mozIStorageAggregateFunction.idl', + 'mozIStorageAsyncConnection.idl', 'mozIStorageAsyncStatement.idl', 'mozIStorageBaseStatement.idl', 'mozIStorageBindingParams.idl', diff --git a/storage/public/mozIStorageAsyncConnection.idl b/storage/public/mozIStorageAsyncConnection.idl new file mode 100644 index 00000000000..9fb1ae4fb57 --- /dev/null +++ b/storage/public/mozIStorageAsyncConnection.idl @@ -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(); +}; diff --git a/storage/public/mozIStorageCompletionCallback.idl b/storage/public/mozIStorageCompletionCallback.idl index 67acfc3cbd5..1c31cc2c262 100644 --- a/storage/public/mozIStorageCompletionCallback.idl +++ b/storage/public/mozIStorageCompletionCallback.idl @@ -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); }; diff --git a/storage/public/mozIStorageConnection.idl b/storage/public/mozIStorageConnection.idl index 87358cc7678..df6b18ce014 100644 --- a/storage/public/mozIStorageConnection.idl +++ b/storage/public/mozIStorageConnection.idl @@ -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 diff --git a/storage/public/mozIStorageService.idl b/storage/public/mozIStorageService.idl index f417694826c..634b25e5905 100644 --- a/storage/public/mozIStorageService.idl +++ b/storage/public/mozIStorageService.idl @@ -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 diff --git a/storage/public/mozStorageHelper.h b/storage/public/mozStorageHelper.h index b2dfbc1c12d..e7322042327 100644 --- a/storage/public/mozStorageHelper.h +++ b/storage/public/mozStorageHelper.h @@ -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,13 +27,18 @@ * 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 +class mozStorageTransactionBase { public: - mozStorageTransaction(mozIStorageConnection* aConnection, - bool aCommitOnComplete, - int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED) + mozStorageTransactionBase(T* aConnection, + bool aCommitOnComplete, + int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED) : mConnection(aConnection), mHasTransaction(false), mCommitOnComplete(aCommitOnComplete), @@ -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 mConnection; + U mConnection; bool mHasTransaction; bool mCommitOnComplete; bool mCompleted; }; +/** + * An instance of the mozStorageTransaction<> family dedicated + * to |mozIStorageConnection|. + */ +typedef mozStorageTransactionBase > +mozStorageTransaction; + + /** * This class wraps a statement so that it is guaraneed to be reset when diff --git a/storage/public/storage.h b/storage/public/storage.h index 08f39f3d32e..ec8037983fb 100644 --- a/storage/public/storage.h +++ b/storage/public/storage.h @@ -27,6 +27,7 @@ #include "mozIStorageVacuumParticipant.h" #include "mozIStorageCompletionCallback.h" #include "mozIStorageAsyncStatement.h" +#include "mozIStorageAsyncConnection.h" //////////////////////////////////////////////////////////////////////////////// //// Native Language Helpers diff --git a/storage/src/mozStorageAsyncStatementExecution.cpp b/storage/src/mozStorageAsyncStatementExecution.cpp index d48737a4bbc..ef1a0fac603 100644 --- a/storage/src/mozStorageAsyncStatementExecution.cpp +++ b/storage/src/mozStorageAsyncStatementExecution.cpp @@ -577,8 +577,9 @@ AsyncExecuteStatements::Run() return notifyComplete(); if (statementsNeedTransaction()) { - mTransactionManager = new mozStorageTransaction(mConnection, false, - mozIStorageConnection::TRANSACTION_IMMEDIATE); + Connection* rawConnection = static_cast(mConnection.get()); + mTransactionManager = new mozStorageAsyncTransaction(rawConnection, false, + mozIStorageConnection::TRANSACTION_IMMEDIATE); } // Execute each statement, giving the callback results if it returns any. diff --git a/storage/src/mozStorageAsyncStatementExecution.h b/storage/src/mozStorageAsyncStatementExecution.h index c711e81e746..28dcf29e46d 100644 --- a/storage/src/mozStorageAsyncStatementExecution.h +++ b/storage/src/mozStorageAsyncStatementExecution.h @@ -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 > + mozStorageAsyncTransaction; + class AsyncExecuteStatements MOZ_FINAL : public nsIRunnable , public mozIStoragePendingStatement { @@ -179,7 +187,7 @@ private: StatementDataArray mStatements; nsRefPtr mConnection; - mozStorageTransaction *mTransactionManager; + mozStorageAsyncTransaction *mTransactionManager; mozIStorageStatementCallback *mCallback; nsCOMPtr mCallingThread; nsRefPtr mResultSet; diff --git a/storage/src/mozStorageConnection.cpp b/storage/src/mozStorageConnection.cpp index d2969922ea8..3911483782a 100644 --- a/storage/src/mozStorageConnection.cpp +++ b/storage/src/mozStorageConnection.cpp @@ -39,6 +39,7 @@ #include "prlog.h" #include "prprf.h" +#include "nsProxyRelease.h" #include #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 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 event = new CallbackComplete(aResult, + aValue, + mCallback.forget()); + return mClone->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL); + } + + ~AsyncInitializeClone() { + nsCOMPtr thread; + DebugOnly 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 mConnection; + nsRefPtr mClone; + const bool mReadOnly; + nsCOMPtr 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 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 clone = new Connection(mStorageService, flags, + mAsyncOnly); + + nsRefPtr initEvent = + new AsyncInitializeClone(this, clone, aReadOnly, aCallback); + nsCOMPtr 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 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; } diff --git a/storage/src/mozStorageConnection.h b/storage/src/mozStorageConnection.h index 97f5cf8aa93..2ea569e6eb5 100644 --- a/storage/src/mozStorageConnection.h +++ b/storage/src/mozStorageConnection.h @@ -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 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 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 mValue; + // This is a nsRefPtr and not a nsCOMPtr because + // nsCOMP would cause an off-main thread QI, which + // is not a good idea (and crashes XPConnect). + nsRefPtr mCallback; }; } // namespace storage diff --git a/storage/src/mozStoragePrivateHelpers.cpp b/storage/src/mozStoragePrivateHelpers.cpp index a45f2bc5c11..cedc3fceecc 100644 --- a/storage/src/mozStoragePrivateHelpers.cpp +++ b/storage/src/mozStoragePrivateHelpers.cpp @@ -161,7 +161,7 @@ public: NS_IMETHOD Run() { - (void)mCallback->Complete(); + (void)mCallback->Complete(NS_OK, nullptr); return NS_OK; } private: diff --git a/storage/src/mozStorageService.cpp b/storage/src/mozStorageService.cpp index b636bf4c6ab..55ba6cf04d3 100644 --- a/storage/src/mozStorageService.cpp +++ b/storage/src/mozStorageService.cpp @@ -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 msc = new Connection(this, SQLITE_OPEN_READWRITE); + nsRefPtr 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 event = + new CallbackComplete(aStatus, + aValue, + mCallback.forget()); + return NS_DispatchToMainThread(event); + } + + ~AsyncInitDatabase() + { + nsCOMPtr thread; + DebugOnly 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 mConnection; + nsCOMPtr mStorageFile; + int32_t mGrowthIncrement; + nsRefPtr 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 storageFile; + int flags = SQLITE_OPEN_READWRITE; + + nsCOMPtr 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 msc = new Connection(this, flags, true); + nsCOMPtr target = msc->getAsyncExecutionTarget(); + MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already"); + + nsRefPtr 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 msc = new Connection(this, flags); + nsRefPtr 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 msc = new Connection(this, flags); + nsRefPtr 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 msc = new Connection(this, flags); + nsRefPtr msc = new Connection(this, flags, false); nsresult rv = msc->initialize(aFileURL); NS_ENSURE_SUCCESS(rv, rv); diff --git a/storage/test/storage_test_harness.h b/storage/test/storage_test_harness.h index 40260ec39cc..ed654654b2e 100644 --- a/storage/test/storage_test_harness.h +++ b/storage/test/storage_test_harness.h @@ -176,7 +176,7 @@ AsyncStatementSpinner::HandleCompletion(uint16_t aReason) } NS_IMETHODIMP -AsyncStatementSpinner::Complete() +AsyncStatementSpinner::Complete(nsresult, nsISupports*) { mCompleted = true; return NS_OK; diff --git a/toolkit/components/places/Database.cpp b/toolkit/components/places/Database.cpp index 3f4093e109b..4e9ab4d7abe 100644 --- a/toolkit/components/places/Database.cpp +++ b/toolkit/components/places/Database.cpp @@ -222,7 +222,7 @@ public: }; NS_IMETHODIMP -BlockingConnectionCloseCallback::Complete() +BlockingConnectionCloseCallback::Complete(nsresult, nsISupports*) { mDone = true; nsCOMPtr os = mozilla::services::GetObserverService();