--- a/db/sqlite3/src/Makefile.in
+++ b/db/sqlite3/src/Makefile.in
@@ -96,17 +96,18 @@ CSRCS = \
sqlite3.c \
$(NULL)
# -DSQLITE_SECURE_DELETE=1 will cause SQLITE to 0-fill delete data so we
# don't have to vacuum to make sure the data is not visible in the file.
# -DSQLITE_ENABLE_FTS3=1 enables the full-text index module.
# -DSQLITE_CORE=1 statically links that module into the SQLite library.
# -DSQLITE_DEFAULT_PAGE_SIZE=32768 and SQLITE_MAX_DEFAULT_PAGE_SIZE=32768
-# increases the page size from 1k, see bug 416330.
+# increases the page size from 1k, see bug 416330. The value must stay in sync
+# with mozIStorageConnection::DEFAULT_PAGE_SIZE.
# Note: Be sure to update the configure.in checks when these change!
DEFINES = \
-DSQLITE_SECURE_DELETE=1 \
-DSQLITE_THREADSAFE=1 \
-DSQLITE_CORE=1 \
-DSQLITE_ENABLE_FTS3=1 \
-DSQLITE_ENABLE_UNLOCK_NOTIFY=1 \
-DSQLITE_DEFAULT_PAGE_SIZE=32768 \
--- a/storage/build/mozStorageCID.h
+++ b/storage/build/mozStorageCID.h
@@ -55,9 +55,15 @@
#define MOZ_STORAGE_SERVICE_CONTRACTID MOZ_STORAGE_CONTRACTID_PREFIX "/service;1"
/* dab3a846-3a59-4fc2-9745-c6ff48776f00 */
#define MOZ_STORAGE_STATEMENT_WRAPPER_CID \
{ 0xdab3a846, 0x3a59, 0x4fc2, {0x97, 0x45, 0xc6, 0xff, 0x48, 0x77, 0x6f, 0x00} }
#define MOZ_STORAGE_STATEMENT_WRAPPER_CONTRACTID MOZ_STORAGE_CONTRACTID_PREFIX "/statement-wrapper;1"
+/* 3b667ee0-d2da-4ccc-9c3d-95f2ca6a8b4c */
+#define VACUUMMANAGER_CID \
+{ 0x3b667ee0, 0xd2da, 0x4ccc, { 0x9c, 0x3d, 0x95, 0xf2, 0xca, 0x6a, 0x8b, 0x4c } }
+
+#define VACUUMMANAGER_CONTRACTID MOZ_STORAGE_CONTRACTID_PREFIX "/vacuum;1"
+
#endif /* MOZSTORAGECID_H */
--- a/storage/build/mozStorageModule.cpp
+++ b/storage/build/mozStorageModule.cpp
@@ -38,44 +38,55 @@
* ***** END LICENSE BLOCK ***** */
#include "nsCOMPtr.h"
#include "mozilla/ModuleUtils.h"
#include "mozStorageService.h"
#include "mozStorageConnection.h"
#include "mozStorageStatementWrapper.h"
+#include "VacuumManager.h"
#include "mozStorageCID.h"
namespace mozilla {
namespace storage {
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(Service,
Service::getSingleton)
NS_GENERIC_FACTORY_CONSTRUCTOR(StatementWrapper)
-
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(VacuumManager,
+ VacuumManager::getSingleton)
} // namespace storage
} // namespace mozilla
NS_DEFINE_NAMED_CID(MOZ_STORAGE_SERVICE_CID);
NS_DEFINE_NAMED_CID(MOZ_STORAGE_STATEMENT_WRAPPER_CID);
+NS_DEFINE_NAMED_CID(VACUUMMANAGER_CID);
static const mozilla::Module::CIDEntry kStorageCIDs[] = {
{ &kMOZ_STORAGE_SERVICE_CID, false, NULL, mozilla::storage::ServiceConstructor },
{ &kMOZ_STORAGE_STATEMENT_WRAPPER_CID, false, NULL, mozilla::storage::StatementWrapperConstructor },
+ { &kVACUUMMANAGER_CID, false, NULL, mozilla::storage::VacuumManagerConstructor },
{ NULL }
};
static const mozilla::Module::ContractIDEntry kStorageContracts[] = {
{ MOZ_STORAGE_SERVICE_CONTRACTID, &kMOZ_STORAGE_SERVICE_CID },
{ MOZ_STORAGE_STATEMENT_WRAPPER_CONTRACTID, &kMOZ_STORAGE_STATEMENT_WRAPPER_CID },
+ { VACUUMMANAGER_CONTRACTID, &kVACUUMMANAGER_CID },
+ { NULL }
+};
+
+static const mozilla::Module::CategoryEntry kStorageCategories[] = {
+ { "profile-after-change", "MozStorage Vacuum Manager", VACUUMMANAGER_CONTRACTID },
{ NULL }
};
static const mozilla::Module kStorageModule = {
mozilla::Module::kVersion,
kStorageCIDs,
- kStorageContracts
+ kStorageContracts,
+ kStorageCategories
};
NSMODULE_DEFN(mozStorageModule) = &kStorageModule;
--- a/storage/public/Makefile.in
+++ b/storage/public/Makefile.in
@@ -63,16 +63,17 @@ XPIDLSRCS = \
mozIStorageStatementCallback.idl \
mozIStoragePendingStatement.idl \
mozIStorageBindingParamsArray.idl \
mozIStorageBindingParams.idl \
mozIStorageCompletionCallback.idl \
mozIStorageBaseStatement.idl \
mozIStorageAsyncStatement.idl \
mozIStorageServiceQuotaManagement.idl \
+ mozIStorageVacuumParticipant.idl \
$(NULL)
EXPORTS_NAMESPACES = mozilla
EXPORTS = \
mozStorageHelper.h \
mozStorage.h \
$(NULL)
--- a/storage/public/mozIStorageConnection.idl
+++ b/storage/public/mozIStorageConnection.idl
@@ -59,16 +59,24 @@ interface nsIFile;
* creating prepared statements, executing SQL, and examining database
* errors.
*
* @threadsafe
*/
[scriptable, uuid(ad035628-4ffb-42ff-a256-0ed9e410b859)]
interface mozIStorageConnection : nsISupports {
/**
+ * The default size for SQLite database pages used by mozStorage for new
+ * databases.
+ * This value must stay in sync with the SQLITE_DEFAULT_PAGE_SIZE define in
+ * /db/sqlite3/src/Makefile.in
+ */
+ const long DEFAULT_PAGE_SIZE = 32768;
+
+ /**
* Closes a database connection. Callers must finalize all statements created
* 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
new file mode 100644
--- /dev/null
+++ b/storage/public/mozIStorageVacuumParticipant.idl
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozStorage.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Marco Bonardo <mak77@bonardo.net> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface mozIStorageConnection;
+
+/**
+ * This interface contains the information that the Storage service needs to
+ * vacuum a database. This interface is created as a service through the
+ * category manager with the category "vacuum-participant".
+ * Please see https://developer.mozilla.org/en/mozIStorageVacuumParticipant for
+ * more information.
+ */
+[scriptable, uuid(8f367508-1d9a-4d3f-be0c-ac11b6dd7dbf)]
+interface mozIStorageVacuumParticipant : nsISupports {
+ /**
+ * The expected page size in bytes for the database. The vacuum manager will
+ * try to correct the page size during idle based on this value.
+ *
+ * @note If the database is using the WAL journal mode and the current page
+ * size is not the expected one, the journal mode will be changed to
+ * TRUNCATE because WAL does not allow page size changes.
+ * The vacuum manager will try to restore WAL mode, but for this to
+ * work reliably the participant must ensure to always reset statements.
+ * If restoring the journal mode should fail it will stick to TRUNCATE.
+ * @note Valid page size values are from 512 to 65536.
+ * The suggested value is mozIStorageConnection::DEFAULT_PAGE_SIZE.
+ */
+ readonly attribute long expectedDatabasePageSize;
+
+ /**
+ * Connection to the database file to be vacuumed.
+ */
+ readonly attribute mozIStorageConnection databaseConnection;
+
+ /**
+ * Notifies when a vacuum operation begins. Listeners should avoid using the
+ * database till onEndVacuum is received.
+ *
+ * @return true to proceed with the vacuum, false if the participant wants to
+ * opt-out for now, it will be retried later. Useful when participant
+ * is running some other heavy operation that can't be interrupted.
+ *
+ * @note When a vacuum operation starts or ends it will also dispatch a global
+ * "heavy-io-task" notification through the observer service with the
+ * data argument being either "vacuum-begin" or "vacuum-end".
+ */
+ boolean onBeginVacuum();
+
+ /**
+ * Notifies when a vacuum operation ends.
+ *
+ * @param aSucceeded
+ * reports if the vacuum succeeded or failed.
+ */
+ void onEndVacuum(in boolean aSucceeded);
+};
--- a/storage/public/storage.h
+++ b/storage/public/storage.h
@@ -53,16 +53,17 @@
#include "mozIStorageResultSet.h"
#include "mozIStorageRow.h"
#include "mozIStorageService.h"
#include "mozIStorageStatement.h"
#include "mozIStorageStatementCallback.h"
#include "mozIStorageBindingParamsArray.h"
#include "mozIStorageBindingParams.h"
#include "mozIStorageServiceQuotaManagement.h"
+#include "mozIStorageVacuumParticipant.h"
////////////////////////////////////////////////////////////////////////////////
//// Native Language Helpers
#include "mozStorageHelper.h"
#include "mozilla/storage/Variant.h"
--- a/storage/src/Makefile.in
+++ b/storage/src/Makefile.in
@@ -75,16 +75,17 @@ CPPSRCS = \
mozStoragePrivateHelpers.cpp \
mozStorageBindingParamsArray.cpp \
mozStorageBindingParams.cpp \
mozStorageAsyncStatement.cpp \
mozStorageAsyncStatementJSHelper.cpp \
mozStorageAsyncStatementParams.cpp \
StorageBaseStatementInternal.cpp \
SQLCollations.cpp \
+ VacuumManager.cpp \
$(NULL)
LOCAL_INCLUDES = \
$(SQLITE_CFLAGS) \
-I$(topsrcdir)/db/sqlite3/src \
$(NULL)
# This is the default value. If we ever change it when compiling sqlite, we
new file mode 100644
--- /dev/null
+++ b/storage/src/VacuumManager.cpp
@@ -0,0 +1,555 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozStorage.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Marco Bonardo <mak77@bonardo.net> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "VacuumManager.h"
+
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "nsPrintfCString.h"
+#include "nsIFile.h"
+#include "nsThreadUtils.h"
+#include "prlog.h"
+
+#include "mozStorageConnection.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageAsyncStatement.h"
+#include "mozIStoragePendingStatement.h"
+#include "mozIStorageError.h"
+
+#define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
+#define OBSERVER_TOPIC_XPCOM_SHUTDOWN "xpcom-shutdown"
+
+// Used to notify begin and end of a heavy IO task.
+#define OBSERVER_TOPIC_HEAVY_IO "heavy-io-task"
+#define OBSERVER_DATA_VACUUM_BEGIN NS_LITERAL_STRING("vacuum-begin")
+#define OBSERVER_DATA_VACUUM_END NS_LITERAL_STRING("vacuum-end")
+
+// This preferences root will contain last vacuum timestamps (in seconds) for
+// each database. The database filename is used as a key.
+#define PREF_VACUUM_BRANCH "storage.vacuum.last."
+
+// Time between subsequent vacuum calls for a certain database.
+#define VACUUM_INTERVAL_SECONDS 30 * 86400 // 30 days.
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo *gStorageLog;
+#endif
+
+namespace mozilla {
+namespace storage {
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+//// BaseCallback
+
+class BaseCallback : public mozIStorageStatementCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGESTATEMENTCALLBACK
+ BaseCallback() {}
+protected:
+ virtual ~BaseCallback() {}
+};
+
+NS_IMETHODIMP
+BaseCallback::HandleError(mozIStorageError *aError)
+{
+#ifdef DEBUG
+ PRInt32 result;
+ nsresult rv = aError->GetResult(&result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCAutoString message;
+ rv = aError->GetMessage(message);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCAutoString warnMsg;
+ warnMsg.AppendLiteral("An error occured during async execution: ");
+ warnMsg.AppendInt(result);
+ warnMsg.AppendLiteral(" ");
+ warnMsg.Append(message);
+ NS_WARNING(warnMsg.get());
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseCallback::HandleResult(mozIStorageResultSet *aResultSet)
+{
+ // We could get results from PRAGMA statements, but we don't mind them.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseCallback::HandleCompletion(PRUint16 aReason)
+{
+ // By default BaseCallback will just be silent on completion.
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS1(
+ BaseCallback
+, mozIStorageStatementCallback
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// Vacuumer declaration.
+
+class Vacuumer : public BaseCallback
+{
+public:
+ NS_DECL_MOZISTORAGESTATEMENTCALLBACK
+
+ Vacuumer(mozIStorageVacuumParticipant *aParticipant,
+ nsIPrefBranch *aPrefBranch);
+
+ bool execute();
+ nsresult notifyCompletion(bool aSucceeded);
+
+private:
+ nsCOMPtr<mozIStorageVacuumParticipant> mParticipant;
+ nsCOMPtr<nsIPrefBranch> mPrefBranch;
+ nsCString mDBFilename;
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+ bool mRestoreWAL;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// NotifyCallback
+/**
+ * This class handles an async statement execution and notifies completion
+ * through a passed in callback.
+ * Errors are handled through warnings in HandleError.
+ */
+class NotifyCallback : public BaseCallback
+{
+public:
+ NS_IMETHOD HandleCompletion(PRUint16 aReason);
+
+ NotifyCallback(Vacuumer *aVacuumer, bool aVacuumSucceeded);
+
+private:
+ nsCOMPtr<Vacuumer> mVacuumer;
+ bool mVacuumSucceeded;
+};
+
+NotifyCallback::NotifyCallback(Vacuumer *aVacuumer,
+ bool aVacuumSucceeded)
+ : mVacuumer(aVacuumer)
+ , mVacuumSucceeded(aVacuumSucceeded)
+{
+}
+
+NS_IMETHODIMP
+NotifyCallback::HandleCompletion(PRUint16 aReason)
+{
+ // We succeeded if both vacuum are WAL restoration succeeded.
+ nsresult rv = mVacuumer->notifyCompletion(mVacuumSucceeded);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Vacuumer implementation.
+
+Vacuumer::Vacuumer(mozIStorageVacuumParticipant *aParticipant,
+ nsIPrefBranch *aPrefBranch)
+ : mParticipant(aParticipant)
+ , mPrefBranch(aPrefBranch)
+ , mRestoreWAL(false)
+{
+}
+
+bool
+Vacuumer::execute()
+{
+ NS_PRECONDITION(NS_IsMainThread(), "Must be running on the main thread!");
+
+ // Get the connection and check its validity.
+ nsresult rv = mParticipant->GetDatabaseConnection(getter_AddRefs(mDBConn));
+ NS_ENSURE_SUCCESS(rv, false);
+ PRBool ready;
+ if (!mDBConn || NS_FAILED(mDBConn->GetConnectionReady(&ready)) || !ready) {
+ NS_WARNING(NS_LITERAL_CSTRING("Unable to get a connection to vacuum database").get());
+ return false;
+ }
+
+ // Compare current page size with the expected one. Vacuum can change the
+ // page size value if needed. Even if a vacuum happened recently, if the
+ // page size can be optimized we should do it.
+ PRInt32 expectedPageSize = 0;
+ rv = mParticipant->GetExpectedDatabasePageSize(&expectedPageSize);
+ if (NS_FAILED(rv) || expectedPageSize < 512 || expectedPageSize > 65536) {
+ NS_WARNING("Invalid page size requested for database, will use default ");
+ NS_WARNING(mDBFilename.get());
+ expectedPageSize = mozIStorageConnection::DEFAULT_PAGE_SIZE;
+ }
+
+ bool canOptimizePageSize;
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "PRAGMA page_size"
+ ), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, false);
+ PRBool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, false);
+ NS_ENSURE_TRUE(hasResult, false);
+ PRInt32 currentPageSize;
+ rv = stmt->GetInt32(0, ¤tPageSize);
+ NS_ENSURE_SUCCESS(rv, false);
+ NS_ASSERTION(currentPageSize > 0, "Got invalid page size value?");
+ canOptimizePageSize = currentPageSize != expectedPageSize;
+ }
+
+ // Get the database filename. Last vacuum time is stored under this name
+ // in PREF_VACUUM_BRANCH.
+ nsCOMPtr<nsIFile> databaseFile;
+ mDBConn->GetDatabaseFile(getter_AddRefs(databaseFile));
+ if (!databaseFile) {
+ NS_WARNING("Trying to vacuum a in-memory database!");
+ return false;
+ }
+ nsAutoString databaseFilename;
+ rv = databaseFile->GetLeafName(databaseFilename);
+ NS_ENSURE_SUCCESS(rv, false);
+ mDBFilename = NS_ConvertUTF16toUTF8(databaseFilename);
+ NS_ASSERTION(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
+
+ // Check interval from last vacuum.
+ PRInt32 now = static_cast<PRInt32>(PR_Now() / PR_USEC_PER_SEC);
+ PRInt32 lastVacuum;
+ rv = mPrefBranch->GetIntPref(mDBFilename.get(), &lastVacuum);
+ if (NS_SUCCEEDED(rv) && (now - lastVacuum) < VACUUM_INTERVAL_SECONDS &&
+ !canOptimizePageSize) {
+ // This database was vacuumed recently and has optimal page size, skip it.
+ return false;
+ }
+
+ // Notify that we are about to start vacuuming. The participant can opt-out
+ // if it cannot handle a vacuum at this time, and then we'll move to the next
+ // one.
+ PRBool vacuumGranted = PR_FALSE;
+ rv = mParticipant->OnBeginVacuum(&vacuumGranted);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (!vacuumGranted) {
+ return false;
+ }
+
+ // Notify a heavy IO task is about to start.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ (void)os->NotifyObservers(nsnull, OBSERVER_TOPIC_HEAVY_IO,
+ OBSERVER_DATA_VACUUM_BEGIN.get());
+ }
+
+ if (canOptimizePageSize) {
+ // Check journal mode. WAL journaling does not allow vacuum to change page
+ // size, thus we have to temporarily switch the journal mode to TRUNCATE.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "PRAGMA journal_mode"
+ ), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, false);
+ PRBool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, false);
+ NS_ENSURE_TRUE(hasResult, false);
+ nsCAutoString journalMode;
+ rv = stmt->GetUTF8String(0, journalMode);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = stmt->Reset();
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (journalMode.EqualsLiteral("wal")) {
+ mRestoreWAL = true;
+ // Set the journal mode to a backwards compatible one.
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "PRAGMA journal_mode = TRUNCATE"
+ ), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<BaseCallback> callback = new BaseCallback();
+ NS_ENSURE_TRUE(callback, false);
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = stmt->ExecuteAsync(callback, getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ // WARNING: any statement after we check for journal mode must be async
+ // to ensure the correct execution order.
+
+ nsCOMPtr<mozIStorageAsyncStatement> pageSizeStmt;
+ rv = mDBConn->CreateAsyncStatement(nsPrintfCString(
+ "PRAGMA page_size = %ld", expectedPageSize
+ ), getter_AddRefs(pageSizeStmt));
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<BaseCallback> callback = new BaseCallback();
+ NS_ENSURE_TRUE(callback, false);
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = pageSizeStmt->ExecuteAsync(callback, getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "VACUUM"
+ ), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = stmt->ExecuteAsync(this, getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageStatementCallback
+
+NS_IMETHODIMP
+Vacuumer::HandleError(mozIStorageError *aError)
+{
+#ifdef DEBUG
+ PRInt32 result;
+ nsresult rv = aError->GetResult(&result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCAutoString message;
+ rv = aError->GetMessage(message);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCAutoString warnMsg;
+ warnMsg.AppendLiteral("Unable to vacuum database: ");
+ warnMsg.Append(mDBFilename);
+ warnMsg.AppendLiteral(" - ");
+ warnMsg.AppendInt(result);
+ warnMsg.AppendLiteral(" ");
+ warnMsg.Append(message);
+ NS_WARNING(warnMsg.get());
+#endif
+
+#ifdef PR_LOGGING
+ {
+ PRInt32 result;
+ nsresult rv = aError->GetResult(&result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCAutoString message;
+ rv = aError->GetMessage(message);
+ NS_ENSURE_SUCCESS(rv, rv);
+ PR_LOG(gStorageLog, PR_LOG_ERROR,
+ ("Vacuum failed with error: %d '%s'. Database was: '%s'",
+ result, message.get(), mDBFilename.get()));
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Vacuumer::HandleResult(mozIStorageResultSet *aResultSet)
+{
+ NS_NOTREACHED("Got a resultset from a vacuum?");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Vacuumer::HandleCompletion(PRUint16 aReason)
+{
+ if (aReason == REASON_FINISHED) {
+ // Update last vacuum time.
+ PRInt32 now = static_cast<PRInt32>(PR_Now() / PR_USEC_PER_SEC);
+ NS_ASSERTION(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
+ (void)mPrefBranch->SetIntPref(mDBFilename.get(), now);
+ }
+
+ // Check if we should restore WAL journal mode.
+ if (mRestoreWAL) {
+ // Restoring WAL is expensive, so must be done async.
+ // End of vacuum will be notified once it finishes.
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ (void)mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "PRAGMA journal_mode = WAL"
+ ), getter_AddRefs(stmt));
+ nsCOMPtr<NotifyCallback> callback =
+ new NotifyCallback(this, aReason == REASON_FINISHED);
+ // Handle errors now, it's important to notify participant before throwing.
+ if (!stmt || !callback) {
+ notifyCompletion(false);
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ stmt->ExecuteAsync(callback, getter_AddRefs(ps));
+ }
+ else {
+ notifyCompletion(aReason == REASON_FINISHED);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Vacuumer::notifyCompletion(bool aSucceeded)
+{
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(nsnull, OBSERVER_TOPIC_HEAVY_IO,
+ OBSERVER_DATA_VACUUM_END.get());
+ }
+
+ nsresult rv = mParticipant->OnEndVacuum(aSucceeded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+} // Anonymous namespace.
+
+////////////////////////////////////////////////////////////////////////////////
+//// VacuumManager
+
+NS_IMPL_ISUPPORTS1(
+ VacuumManager
+, nsIObserver
+)
+
+VacuumManager *
+VacuumManager::gVacuumManager = nsnull;
+
+VacuumManager *
+VacuumManager::getSingleton()
+{
+ if (gVacuumManager) {
+ NS_ADDREF(gVacuumManager);
+ return gVacuumManager;
+ }
+ gVacuumManager = new VacuumManager();
+ if (gVacuumManager) {
+ NS_ADDREF(gVacuumManager);
+ if (NS_FAILED(gVacuumManager->initialize())) {
+ NS_RELEASE(gVacuumManager);
+ }
+ }
+ return gVacuumManager;
+}
+
+VacuumManager::VacuumManager()
+ : mParticipants("vacuum-participant")
+{
+ NS_ASSERTION(!gVacuumManager,
+ "Attempting to create two instances of the service!");
+ gVacuumManager = this;
+}
+
+nsresult
+VacuumManager::initialize()
+{
+ NS_PRECONDITION(NS_IsMainThread(), "Must be running on the main thread!");
+
+ // Observe idle-daily to run a single vacuum per day.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(os);
+ nsresult rv = os->AddObserver(this, OBSERVER_TOPIC_IDLE_DAILY, PR_FALSE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = os->AddObserver(this, OBSERVER_TOPIC_XPCOM_SHUTDOWN, PR_FALSE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Used to store last vacuum times.
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ NS_ENSURE_STATE(prefs);
+ prefs->GetBranch(PREF_VACUUM_BRANCH, getter_AddRefs(mPrefBranch));
+ NS_ENSURE_STATE(mPrefBranch);
+
+ return NS_OK;
+}
+
+VacuumManager::~VacuumManager()
+{
+ // Remove the static reference to the service. Check to make sure its us
+ // in case somebody creates an extra instance of the service.
+ NS_ASSERTION(gVacuumManager == this,
+ "Deleting a non-singleton instance of the service");
+ if (gVacuumManager == this) {
+ gVacuumManager = nsnull;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+VacuumManager::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const PRUnichar *aData)
+{
+ if (strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY) == 0) {
+ // Try to run vacuum on all registered entries. Will stop at the first
+ // successful one.
+ const nsCOMArray<mozIStorageVacuumParticipant> &entries =
+ mParticipants.GetEntries();
+ // If there are more entries than what a month can contain, we could end up
+ // skipping some, since we run daily. So we use a starting index.
+ PRInt32 startIndex = 0, index;
+ (void)mPrefBranch->GetIntPref(NS_LITERAL_CSTRING("index").get(), &startIndex);
+ if (startIndex >= entries.Count()) {
+ startIndex = 0;
+ }
+ for (index = startIndex; index < entries.Count(); ++index) {
+ nsCOMPtr<Vacuumer> vacuum = new Vacuumer(entries[index], mPrefBranch);
+ NS_ENSURE_STATE(vacuum);
+ // Only vacuum one database per day.
+ if (vacuum->execute()) {
+ break;
+ }
+ }
+ (void)mPrefBranch->SetIntPref(NS_LITERAL_CSTRING("index").get(), index);
+ return NS_OK;
+ }
+ else if (strcmp(aTopic, OBSERVER_TOPIC_XPCOM_SHUTDOWN) == 0) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ (void)os->RemoveObserver(this, OBSERVER_TOPIC_IDLE_DAILY);
+ (void)os->RemoveObserver(this, OBSERVER_TOPIC_XPCOM_SHUTDOWN);
+ }
+ }
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/storage/src/VacuumManager.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozStorage.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Marco Bonardo <mak77@bonardo.net> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_storage_VacuumManager_h__
+#define mozilla_storage_VacuumManager_h__
+
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "mozIStorageStatementCallback.h"
+#include "mozIStorageVacuumParticipant.h"
+#include "nsCategoryCache.h"
+#include "nsIPrefService.h"
+
+namespace mozilla {
+namespace storage {
+
+class VacuumManager : public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ VacuumManager();
+
+ /**
+ * Obtains the VacuumManager object.
+ */
+ static VacuumManager * getSingleton();
+
+ /**
+ * Initializes the VacuumManager object. Must be called just once.
+ */
+ nsresult initialize();
+
+private:
+ ~VacuumManager();
+
+ static VacuumManager *gVacuumManager;
+
+ // Cache of components registered in "vacuum-participant" category.
+ nsCategoryCache<mozIStorageVacuumParticipant> mParticipants;
+
+ // Pref branch for PREF_VACUUM_BRANCH.
+ nsCOMPtr<nsIPrefBranch> mPrefBranch;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -418,18 +418,19 @@ Connection::initialize(nsIFile *aDatabas
nsCAutoString leafName(":memory");
if (aDatabaseFile)
(void)aDatabaseFile->GetNativeLeafName(leafName);
PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Opening connection to '%s' (%p)",
leafName.get(), this));
#endif
// Switch db to preferred page size in case the user vacuums.
sqlite3_stmt *stmt;
- srv = prepareStmt(mDBConn, NS_LITERAL_CSTRING("PRAGMA page_size = 32768"),
- &stmt);
+ nsCAutoString pageSizeQuery(NS_LITERAL_CSTRING("PRAGMA page_size = "));
+ pageSizeQuery.AppendInt(DEFAULT_PAGE_SIZE);
+ srv = prepareStmt(mDBConn, pageSizeQuery, &stmt);
if (srv == SQLITE_OK) {
(void)stepStmt(stmt);
(void)::sqlite3_finalize(stmt);
}
// Register our built-in SQL functions.
srv = registerFunctions(mDBConn);
if (srv != SQLITE_OK) {
new file mode 100644
--- /dev/null
+++ b/storage/test/unit/test_vacuum.js
@@ -0,0 +1,333 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This file tests the Vacuum Manager.
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Loads a test component that will register as a vacuum-participant.
+ * If other participants are found they will be unregistered, to avoid conflicts
+ * with the test itself.
+ */
+function load_test_vacuum_component()
+{
+ const CATEGORY_NAME = "vacuum-participant";
+
+ do_load_manifest("vacuumParticipant.manifest");
+
+ // This is a lazy check, there could be more participants than just this test
+ // we just mind that the test exists though.
+ const EXPECTED_ENTRIES = ["vacuumParticipant"];
+ let catMan = Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager);
+ let found = false;
+ let entries = catMan.enumerateCategory(CATEGORY_NAME);
+ while (entries.hasMoreElements()) {
+ let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
+ print("Check if the found category entry (" + entry + ") is expected.");
+ if (EXPECTED_ENTRIES.indexOf(entry) != -1) {
+ print("Check that only one test entry exists.");
+ do_check_false(found);
+ found = true;
+ }
+ else {
+ // Temporary unregister other participants for this test.
+ catMan.deleteCategoryEntry("vacuum-participant", entry, false);
+ }
+ }
+ print("Check the test entry exists.");
+ do_check_true(found);
+}
+
+/**
+ * Sends a fake idle-daily notification to the VACUUM Manager.
+ */
+function synthesize_idle_daily()
+{
+ let vm = Cc["@mozilla.org/storage/vacuum;1"].getService(Ci.nsIObserver);
+ vm.observe(null, "idle-daily", null);
+}
+
+/**
+ * Returns a new nsIFile reference for a profile database.
+ * @param filename for the database, excluded the .sqlite extension.
+ */
+function new_db_file(name)
+{
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append(name + ".sqlite");
+ return file;
+}
+
+function run_test()
+{
+ do_test_pending();
+
+ // Change initial page size. Do it immediately since it would require an
+ // additional vacuum op to do it later. As a bonux this makes the page size
+ // change test really fast since it only has to check results.
+ let conn = getDatabase(new_db_file("testVacuum"));
+ conn.executeSimpleSQL("PRAGMA page_size = 1024");
+ print("Check current page size.");
+ let stmt = conn.createStatement("PRAGMA page_size");
+ try {
+ while (stmt.executeStep()) {
+ do_check_eq(stmt.row.page_size, 1024);
+ }
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ load_test_vacuum_component();
+
+ run_next_test();
+}
+
+function run_next_test()
+{
+ if (TESTS.length == 0) {
+ Services.obs.notifyObservers(null, "test-options", "dispose");
+ do_test_finished();
+ }
+ else {
+ // Set last VACUUM to a date in the past.
+ Services.prefs.setIntPref("storage.vacuum.last.testVacuum.sqlite",
+ parseInt(Date.now() / 1000 - 31 * 86400));
+ do_execute_soon(TESTS.shift());
+ }
+}
+
+const TESTS = [
+
+function test_common_vacuum()
+{
+ print("\n*** Test that a VACUUM correctly happens and all notifications are fired.");
+ // Wait for VACUUM begin.
+ let beginVacuumReceived = false;
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ Services.obs.removeObserver(arguments.callee, aTopic, false);
+ beginVacuumReceived = true;
+ }, "test-begin-vacuum", false);
+
+ // Wait for heavy IO notifications.
+ let heavyIOTaskBeginReceived = false;
+ let heavyIOTaskEndReceived = false;
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ if (heavyIOTaskBeginReceived && heavyIOTaskEndReceived) {
+ Services.obs.removeObserver(arguments.callee, aTopic, false);
+ }
+
+ if (aData == "vacuum-begin") {
+ heavyIOTaskBeginReceived = true;
+ }
+ else if (aData == "vacuum-end") {
+ heavyIOTaskEndReceived = true;
+ }
+ }, "heavy-io-task", false);
+
+ // Wait for VACUUM end.
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ Services.obs.removeObserver(arguments.callee, aTopic, false);
+ print("Check we received onBeginVacuum");
+ do_check_true(beginVacuumReceived);
+ print("Check we received heavy-io-task notifications");
+ do_check_true(heavyIOTaskBeginReceived);
+ do_check_true(heavyIOTaskEndReceived);
+ print("Received onEndVacuum");
+ run_next_test();
+ }, "test-end-vacuum", false);
+
+ synthesize_idle_daily();
+},
+
+function test_skipped_if_recent_vacuum()
+{
+ print("\n*** Test that a VACUUM is skipped if it was run recently.");
+ Services.prefs.setIntPref("storage.vacuum.last.testVacuum.sqlite",
+ parseInt(Date.now() / 1000));
+
+ // Wait for VACUUM begin.
+ let vacuumObserver = {
+ gotNotification: false,
+ observe: function VO_observe(aSubject, aTopic, aData) {
+ this.gotNotification = true;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+ }
+ Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false);
+
+ // Check after a couple seconds that no VACUUM has been run.
+ do_timeout(2000, function () {
+ print("Check VACUUM did not run.");
+ do_check_false(vacuumObserver.gotNotification);
+ Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum");
+ run_next_test();
+ });
+
+ synthesize_idle_daily();
+},
+
+function test_page_size_change()
+{
+ print("\n*** Test that a VACUUM changes page_size");
+
+ // We did setup the database with a small page size, the previous vacuum
+ // should have updated it.
+ print("Check that page size was updated.");
+ let conn = getDatabase(new_db_file("testVacuum"));
+ let stmt = conn.createStatement("PRAGMA page_size");
+ try {
+ while (stmt.executeStep()) {
+ do_check_eq(stmt.row.page_size, Ci.mozIStorageConnection.DEFAULT_PAGE_SIZE);
+ }
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ run_next_test();
+},
+
+function test_skipped_optout_vacuum()
+{
+ print("\n*** Test that a VACUUM is skipped if the participant wants to opt-out.");
+ Services.obs.notifyObservers(null, "test-options", "opt-out");
+
+ // Wait for VACUUM begin.
+ let vacuumObserver = {
+ gotNotification: false,
+ observe: function VO_observe(aSubject, aTopic, aData) {
+ this.gotNotification = true;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+ }
+ Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false);
+
+ // Check after a couple seconds that no VACUUM has been run.
+ do_timeout(2000, function () {
+ print("Check VACUUM did not run.");
+ do_check_false(vacuumObserver.gotNotification);
+ Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum");
+ run_next_test();
+ });
+
+ synthesize_idle_daily();
+},
+
+function test_page_size_change_with_wal()
+{
+ print("\n*** Test that a VACUUM changes page_size with WAL mode");
+ Services.obs.notifyObservers(null, "test-options", "wal");
+
+ // Set a small page size.
+ let conn = getDatabase(new_db_file("testVacuum2"));
+ conn.executeSimpleSQL("PRAGMA page_size = 1024");
+ let stmt = conn.createStatement("PRAGMA page_size");
+ try {
+ while (stmt.executeStep()) {
+ do_check_eq(stmt.row.page_size, 1024);
+ }
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ // Use WAL journal mode.
+ conn.executeSimpleSQL("PRAGMA journal_mode = WAL");
+ stmt = conn.createStatement("PRAGMA journal_mode");
+ try {
+ while (stmt.executeStep()) {
+ do_check_eq(stmt.row.journal_mode, "wal");
+ }
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ // Wait for VACUUM end.
+ let vacuumObserver = {
+ observe: function VO_observe(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(this, aTopic);
+ print("Check page size has been updated.");
+ let stmt = conn.createStatement("PRAGMA page_size");
+ try {
+ while (stmt.executeStep()) {
+ do_check_eq(stmt.row.page_size, Ci.mozIStorageConnection.DEFAULT_PAGE_SIZE);
+ }
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ print("Check journal mode has been restored.");
+ stmt = conn.createStatement("PRAGMA journal_mode");
+ try {
+ while (stmt.executeStep()) {
+ do_check_eq(stmt.row.journal_mode, "wal");
+ }
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ run_next_test();
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+ }
+ Services.obs.addObserver(vacuumObserver, "test-end-vacuum", false);
+
+ synthesize_idle_daily();
+},
+
+function test_memory_database_crash()
+{
+ print("\n*** Test that we don't crash trying to vacuum a memory database");
+ Services.obs.notifyObservers(null, "test-options", "memory");
+
+ // Wait for VACUUM begin.
+ let vacuumObserver = {
+ gotNotification: false,
+ observe: function VO_observe(aSubject, aTopic, aData) {
+ this.gotNotification = true;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+ }
+ Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false);
+
+ // Check after a couple seconds that no VACUUM has been run.
+ do_timeout(2000, function () {
+ print("Check VACUUM did not run.");
+ do_check_false(vacuumObserver.gotNotification);
+ Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum");
+ run_next_test();
+ });
+
+ synthesize_idle_daily();
+},
+
+/* Test temporarily disabled due to bug 599098.
+function test_wal_restore_fail()
+{
+ print("\n*** Test that a failing WAL restoration notifies failure");
+ Services.obs.notifyObservers(null, "test-options", "wal-fail");
+
+ // Wait for VACUUM end.
+ let vacuumObserver = {
+ observe: function VO_observe(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(vacuumObserver, "test-end-vacuum");
+ print("Check WAL restoration failed.");
+ do_check_false(aData);
+ run_next_test();
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+ }
+ Services.obs.addObserver(vacuumObserver, "test-end-vacuum", false);
+
+ synthesize_idle_daily();
+},
+*/
+];
new file mode 100644
--- /dev/null
+++ b/storage/test/unit/vacuumParticipant.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This testing component is used in test_vacuum* tests.
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Returns a new nsIFile reference for a profile database.
+ * @param filename for the database, excluded the .sqlite extension.
+ */
+function new_db_file(name)
+{
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append(name + ".sqlite");
+ return file;
+}
+
+/**
+ * Opens and returns a connection to the provided database file.
+ * @param nsIFile interface to the database file.
+ */
+function getDatabase(aFile)
+{
+ return Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService)
+ .openDatabase(aFile);
+}
+
+function vacuumParticipant()
+{
+ this._dbConn = getDatabase(new_db_file("testVacuum"));
+ Services.obs.addObserver(this, "test-options", false);
+}
+
+vacuumParticipant.prototype =
+{
+ classDescription: "vacuumParticipant",
+ classID: Components.ID("{52aa0b22-b82f-4e38-992a-c3675a3355d2}"),
+ contractID: "@unit.test.com/test-vacuum-participant;1",
+
+ get expectedDatabasePageSize() Ci.mozIStorageConnection.DEFAULT_PAGE_SIZE,
+ get databaseConnection() this._dbConn,
+
+ _grant: true,
+ onBeginVacuum: function TVP_onBeginVacuum()
+ {
+ if (!this._grant) {
+ this._grant = true;
+ return false;
+ }
+ Services.obs.notifyObservers(null, "test-begin-vacuum", null);
+ return true;
+ },
+ onEndVacuum: function TVP_EndVacuum(aSucceeded)
+ {
+ if (this._stmt) {
+ this._stmt.finalize();
+ }
+ Services.obs.notifyObservers(null, "test-end-vacuum", aSucceeded);
+ },
+
+ observe: function TVP_observe(aSubject, aTopic, aData)
+ {
+ if (aData == "opt-out") {
+ this._grant = false;
+ }
+ else if (aData == "wal") {
+ try {
+ this._dbConn.close();
+ }
+ catch(e) {}
+ this._dbConn = getDatabase(new_db_file("testVacuum2"));
+ }
+ else if (aData == "wal-fail") {
+ try {
+ this._dbConn.close();
+ }
+ catch(e) {}
+ this._dbConn = getDatabase(new_db_file("testVacuum3"));
+ // Use WAL journal mode.
+ this._dbConn.executeSimpleSQL("PRAGMA journal_mode = WAL");
+ // Create a not finalized statement.
+ this._stmt = this._dbConn.createStatement("SELECT :test");
+ this._stmt.params.test = 1;
+ this._stmt.executeStep();
+ }
+ else if (aData == "memory") {
+ try {
+ this._dbConn.close();
+ }
+ catch(e) {}
+ this._dbConn = Cc["@mozilla.org/storage/service;1"].
+ getService(Ci.mozIStorageService).
+ openSpecialDatabase("memory");
+ }
+ else if (aData == "dispose") {
+ Services.obs.removeObserver(this, "test-options");
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.mozIStorageVacuumParticipant
+ , Ci.nsIObserver
+ ])
+};
+
+let gComponentsArray = [vacuumParticipant];
+let NSGetFactory = XPCOMUtils.generateNSGetFactory(gComponentsArray);
new file mode 100644
--- /dev/null
+++ b/storage/test/unit/vacuumParticipant.manifest
@@ -0,0 +1,3 @@
+component {52aa0b22-b82f-4e38-992a-c3675a3355d2} vacuumParticipant.js
+contract @unit.test.com/test-vacuum-participant;1 {52aa0b22-b82f-4e38-992a-c3675a3355d2}
+category vacuum-participant vacuumParticipant @unit.test.com/test-vacuum-participant;1