mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 14:52:16 +00:00
Backed out 2 changesets (bug 1813986) for causing build bustages in Logging.h
Backed out changeset 14879c9b6b3c (bug 1813986) Backed out changeset d90fa56c73e5 (bug 1813986)
This commit is contained in:
parent
2dbecf9ab6
commit
eac4250835
6
dom/cache/Connection.cpp
vendored
6
dom/cache/Connection.cpp
vendored
@ -49,12 +49,6 @@ Connection::Close() {
|
|||||||
|
|
||||||
// mozIStorageAsyncConnection methods
|
// mozIStorageAsyncConnection methods
|
||||||
|
|
||||||
NS_IMETHODIMP
|
|
||||||
Connection::AsyncVacuum(mozIStorageCompletionCallback*, bool, int32_t) {
|
|
||||||
// async methods are not supported
|
|
||||||
return NS_ERROR_NOT_IMPLEMENTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
Connection::AsyncClose(mozIStorageCompletionCallback*) {
|
Connection::AsyncClose(mozIStorageCompletionCallback*) {
|
||||||
// async methods are not supported
|
// async methods are not supported
|
||||||
|
@ -18,63 +18,104 @@
|
|||||||
#include "mozilla/StaticPrefs_storage.h"
|
#include "mozilla/StaticPrefs_storage.h"
|
||||||
|
|
||||||
#include "mozStorageConnection.h"
|
#include "mozStorageConnection.h"
|
||||||
#include "mozStoragePrivateHelpers.h"
|
|
||||||
#include "mozIStorageStatement.h"
|
#include "mozIStorageStatement.h"
|
||||||
#include "mozIStorageCompletionCallback.h"
|
#include "mozIStorageStatementCallback.h"
|
||||||
#include "mozIStorageAsyncStatement.h"
|
#include "mozIStorageAsyncStatement.h"
|
||||||
#include "mozIStoragePendingStatement.h"
|
#include "mozIStoragePendingStatement.h"
|
||||||
#include "mozIStorageError.h"
|
#include "mozIStorageError.h"
|
||||||
#include "mozStorageHelper.h"
|
#include "mozStorageHelper.h"
|
||||||
#include "nsXULAppAPI.h"
|
#include "nsXULAppAPI.h"
|
||||||
#include "xpcpublic.h"
|
|
||||||
|
|
||||||
#define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
|
#define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
|
||||||
|
#define OBSERVER_TOPIC_XPCOM_SHUTDOWN "xpcom-shutdown"
|
||||||
|
|
||||||
// Used to notify the begin and end of a vacuum operation.
|
// Used to notify begin and end of a heavy IO task.
|
||||||
#define OBSERVER_TOPIC_VACUUM_BEGIN "vacuum-begin"
|
#define OBSERVER_TOPIC_HEAVY_IO "heavy-io-task"
|
||||||
#define OBSERVER_TOPIC_VACUUM_END "vacuum-end"
|
#define OBSERVER_DATA_VACUUM_BEGIN u"vacuum-begin"
|
||||||
// This notification is sent only in automation when vacuum for a database is
|
#define OBSERVER_DATA_VACUUM_END u"vacuum-end"
|
||||||
// skipped, and can thus be used to verify that.
|
|
||||||
#define OBSERVER_TOPIC_VACUUM_SKIP "vacuum-skip"
|
|
||||||
|
|
||||||
// This preferences root will contain last vacuum timestamps (in seconds) for
|
// This preferences root will contain last vacuum timestamps (in seconds) for
|
||||||
// each database. The database filename is used as a key.
|
// each database. The database filename is used as a key.
|
||||||
#define PREF_VACUUM_BRANCH "storage.vacuum.last."
|
#define PREF_VACUUM_BRANCH "storage.vacuum.last."
|
||||||
|
|
||||||
// Time between subsequent vacuum calls for a certain database.
|
// Time between subsequent vacuum calls for a certain database.
|
||||||
#define VACUUM_INTERVAL_SECONDS (30 * 86400) // 30 days.
|
#define VACUUM_INTERVAL_SECONDS 30 * 86400 // 30 days.
|
||||||
|
|
||||||
extern mozilla::LazyLogModule gStorageLog;
|
extern mozilla::LazyLogModule gStorageLog;
|
||||||
|
|
||||||
namespace mozilla::storage {
|
namespace mozilla {
|
||||||
|
namespace storage {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//// Vacuumer declaration.
|
//// BaseCallback
|
||||||
|
|
||||||
class Vacuumer final : public mozIStorageCompletionCallback {
|
class BaseCallback : public mozIStorageStatementCallback {
|
||||||
public:
|
public:
|
||||||
NS_DECL_ISUPPORTS
|
NS_DECL_ISUPPORTS
|
||||||
NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
|
NS_DECL_MOZISTORAGESTATEMENTCALLBACK
|
||||||
|
BaseCallback() {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual ~BaseCallback() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
BaseCallback::HandleError(mozIStorageError* aError) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
int32_t result;
|
||||||
|
nsresult rv = aError->GetResult(&result);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
nsAutoCString message;
|
||||||
|
rv = aError->GetMessage(message);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
nsAutoCString warnMsg;
|
||||||
|
warnMsg.AppendLiteral("An error occured during async execution: ");
|
||||||
|
warnMsg.AppendInt(result);
|
||||||
|
warnMsg.Append(' ');
|
||||||
|
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(uint16_t aReason) {
|
||||||
|
// By default BaseCallback will just be silent on completion.
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMPL_ISUPPORTS(BaseCallback, mozIStorageStatementCallback)
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// Vacuumer declaration.
|
||||||
|
|
||||||
|
class Vacuumer : public BaseCallback {
|
||||||
|
public:
|
||||||
|
NS_DECL_MOZISTORAGESTATEMENTCALLBACK
|
||||||
|
|
||||||
explicit Vacuumer(mozIStorageVacuumParticipant* aParticipant);
|
explicit Vacuumer(mozIStorageVacuumParticipant* aParticipant);
|
||||||
|
|
||||||
bool execute();
|
bool execute();
|
||||||
|
nsresult notifyCompletion(bool aSucceeded);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
nsresult notifyCompletion(bool aSucceeded);
|
|
||||||
~Vacuumer() = default;
|
|
||||||
|
|
||||||
nsCOMPtr<mozIStorageVacuumParticipant> mParticipant;
|
nsCOMPtr<mozIStorageVacuumParticipant> mParticipant;
|
||||||
nsCString mDBFilename;
|
nsCString mDBFilename;
|
||||||
nsCOMPtr<mozIStorageAsyncConnection> mDBConn;
|
nsCOMPtr<mozIStorageConnection> mDBConn;
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//// Vacuumer implementation.
|
//// Vacuumer implementation.
|
||||||
|
|
||||||
NS_IMPL_ISUPPORTS(Vacuumer, mozIStorageCompletionCallback)
|
|
||||||
|
|
||||||
Vacuumer::Vacuumer(mozIStorageVacuumParticipant* aParticipant)
|
Vacuumer::Vacuumer(mozIStorageVacuumParticipant* aParticipant)
|
||||||
: mParticipant(aParticipant) {}
|
: mParticipant(aParticipant) {}
|
||||||
|
|
||||||
@ -83,21 +124,30 @@ bool Vacuumer::execute() {
|
|||||||
|
|
||||||
// Get the connection and check its validity.
|
// Get the connection and check its validity.
|
||||||
nsresult rv = mParticipant->GetDatabaseConnection(getter_AddRefs(mDBConn));
|
nsresult rv = mParticipant->GetDatabaseConnection(getter_AddRefs(mDBConn));
|
||||||
if (NS_FAILED(rv) || !mDBConn) return false;
|
NS_ENSURE_SUCCESS(rv, false);
|
||||||
|
bool ready = false;
|
||||||
|
if (!mDBConn || NS_FAILED(mDBConn->GetConnectionReady(&ready)) || !ready) {
|
||||||
|
NS_WARNING("Unable to get a connection to vacuum database");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
// Ask for the expected page size. Vacuum can change the page size, unless
|
||||||
|
// the database is using WAL journaling.
|
||||||
|
// TODO Bug 634374: figure out a strategy to fix page size with WAL.
|
||||||
|
int32_t expectedPageSize = 0;
|
||||||
|
rv = mParticipant->GetExpectedDatabasePageSize(&expectedPageSize);
|
||||||
|
if (NS_FAILED(rv) || !Service::pageSizeIsValid(expectedPageSize)) {
|
||||||
|
NS_WARNING("Invalid page size requested for database, will use default ");
|
||||||
|
NS_WARNING(mDBFilename.get());
|
||||||
|
expectedPageSize = Service::kDefaultPageSize;
|
||||||
|
}
|
||||||
|
|
||||||
bool inAutomation = xpc::IsInAutomation();
|
|
||||||
// Get the database filename. Last vacuum time is stored under this name
|
// Get the database filename. Last vacuum time is stored under this name
|
||||||
// in PREF_VACUUM_BRANCH.
|
// in PREF_VACUUM_BRANCH.
|
||||||
nsCOMPtr<nsIFile> databaseFile;
|
nsCOMPtr<nsIFile> databaseFile;
|
||||||
mDBConn->GetDatabaseFile(getter_AddRefs(databaseFile));
|
mDBConn->GetDatabaseFile(getter_AddRefs(databaseFile));
|
||||||
if (!databaseFile) {
|
if (!databaseFile) {
|
||||||
NS_WARNING("Trying to vacuum a in-memory database!");
|
NS_WARNING("Trying to vacuum a in-memory database!");
|
||||||
if (inAutomation && os) {
|
|
||||||
mozilla::Unused << os->NotifyObservers(
|
|
||||||
nullptr, OBSERVER_TOPIC_VACUUM_SKIP, u":memory:");
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
nsAutoString databaseFilename;
|
nsAutoString databaseFilename;
|
||||||
@ -114,11 +164,6 @@ bool Vacuumer::execute() {
|
|||||||
rv = Preferences::GetInt(prefName.get(), &lastVacuum);
|
rv = Preferences::GetInt(prefName.get(), &lastVacuum);
|
||||||
if (NS_SUCCEEDED(rv) && (now - lastVacuum) < VACUUM_INTERVAL_SECONDS) {
|
if (NS_SUCCEEDED(rv) && (now - lastVacuum) < VACUUM_INTERVAL_SECONDS) {
|
||||||
// This database was vacuumed recently, skip it.
|
// This database was vacuumed recently, skip it.
|
||||||
if (inAutomation && os) {
|
|
||||||
mozilla::Unused << os->NotifyObservers(
|
|
||||||
nullptr, OBSERVER_TOPIC_VACUUM_SKIP,
|
|
||||||
NS_ConvertUTF8toUTF16(mDBFilename).get());
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,48 +174,86 @@ bool Vacuumer::execute() {
|
|||||||
rv = mParticipant->OnBeginVacuum(&vacuumGranted);
|
rv = mParticipant->OnBeginVacuum(&vacuumGranted);
|
||||||
NS_ENSURE_SUCCESS(rv, false);
|
NS_ENSURE_SUCCESS(rv, false);
|
||||||
if (!vacuumGranted) {
|
if (!vacuumGranted) {
|
||||||
if (inAutomation && os) {
|
|
||||||
mozilla::Unused << os->NotifyObservers(
|
|
||||||
nullptr, OBSERVER_TOPIC_VACUUM_SKIP,
|
|
||||||
NS_ConvertUTF8toUTF16(mDBFilename).get());
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask for the expected page size. Vacuum can change the page size, unless
|
// Notify a heavy IO task is about to start.
|
||||||
// the database is using WAL journaling.
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||||
// TODO Bug 634374: figure out a strategy to fix page size with WAL.
|
|
||||||
int32_t expectedPageSize = 0;
|
|
||||||
rv = mParticipant->GetExpectedDatabasePageSize(&expectedPageSize);
|
|
||||||
if (NS_FAILED(rv) || !Service::pageSizeIsValid(expectedPageSize)) {
|
|
||||||
NS_WARNING("Invalid page size requested for database, won't set it. ");
|
|
||||||
NS_WARNING(mDBFilename.get());
|
|
||||||
expectedPageSize = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool incremental = false;
|
|
||||||
mozilla::Unused << mParticipant->GetUseIncrementalVacuum(&incremental);
|
|
||||||
|
|
||||||
// Notify vacuum is about to start.
|
|
||||||
if (os) {
|
if (os) {
|
||||||
mozilla::Unused << os->NotifyObservers(
|
rv = os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO,
|
||||||
nullptr, OBSERVER_TOPIC_VACUUM_BEGIN,
|
OBSERVER_DATA_VACUUM_BEGIN);
|
||||||
NS_ConvertUTF8toUTF16(mDBFilename).get());
|
MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to notify");
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = mDBConn->AsyncVacuum(this, incremental, expectedPageSize);
|
// Execute the statements separately, since the pragma may conflict with the
|
||||||
if (NS_FAILED(rv)) {
|
// vacuum, if they are executed in the same transaction.
|
||||||
// The connection is not ready.
|
nsCOMPtr<mozIStorageAsyncStatement> pageSizeStmt;
|
||||||
mozilla::Unused << Complete(rv, nullptr);
|
nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
|
||||||
return false;
|
"PRAGMA page_size = ");
|
||||||
}
|
pageSizeQuery.AppendInt(expectedPageSize);
|
||||||
|
rv = mDBConn->CreateAsyncStatement(pageSizeQuery,
|
||||||
|
getter_AddRefs(pageSizeStmt));
|
||||||
|
NS_ENSURE_SUCCESS(rv, false);
|
||||||
|
RefPtr<BaseCallback> callback = new BaseCallback();
|
||||||
|
nsCOMPtr<mozIStoragePendingStatement> ps;
|
||||||
|
rv = pageSizeStmt->ExecuteAsync(callback, getter_AddRefs(ps));
|
||||||
|
NS_ENSURE_SUCCESS(rv, false);
|
||||||
|
|
||||||
|
nsCOMPtr<mozIStorageAsyncStatement> stmt;
|
||||||
|
rv = mDBConn->CreateAsyncStatement("VACUUM"_ns, getter_AddRefs(stmt));
|
||||||
|
NS_ENSURE_SUCCESS(rv, false);
|
||||||
|
rv = stmt->ExecuteAsync(this, getter_AddRefs(ps));
|
||||||
|
NS_ENSURE_SUCCESS(rv, false);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// mozIStorageStatementCallback
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
Vacuumer::Complete(nsresult aStatus, nsISupports* aValue) {
|
Vacuumer::HandleError(mozIStorageError* aError) {
|
||||||
if (NS_SUCCEEDED(aStatus)) {
|
int32_t result;
|
||||||
|
nsresult rv;
|
||||||
|
nsAutoCString message;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
rv = aError->GetResult(&result);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
rv = aError->GetMessage(message);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
nsAutoCString warnMsg;
|
||||||
|
warnMsg.AppendLiteral("Unable to vacuum database: ");
|
||||||
|
warnMsg.Append(mDBFilename);
|
||||||
|
warnMsg.AppendLiteral(" - ");
|
||||||
|
warnMsg.AppendInt(result);
|
||||||
|
warnMsg.Append(' ');
|
||||||
|
warnMsg.Append(message);
|
||||||
|
NS_WARNING(warnMsg.get());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (MOZ_LOG_TEST(gStorageLog, LogLevel::Error)) {
|
||||||
|
rv = aError->GetResult(&result);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
rv = aError->GetMessage(message);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
MOZ_LOG(gStorageLog, LogLevel::Error,
|
||||||
|
("Vacuum failed with error: %d '%s'. Database was: '%s'", result,
|
||||||
|
message.get(), mDBFilename.get()));
|
||||||
|
}
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
Vacuumer::HandleResult(mozIStorageResultSet* aResultSet) {
|
||||||
|
MOZ_ASSERT_UNREACHABLE("Got a resultset from a vacuum?");
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
Vacuumer::HandleCompletion(uint16_t aReason) {
|
||||||
|
if (aReason == REASON_FINISHED) {
|
||||||
// Update last vacuum time.
|
// Update last vacuum time.
|
||||||
int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
|
int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
|
||||||
MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
|
MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
|
||||||
@ -178,35 +261,18 @@ Vacuumer::Complete(nsresult aStatus, nsISupports* aValue) {
|
|||||||
prefName += mDBFilename;
|
prefName += mDBFilename;
|
||||||
DebugOnly<nsresult> rv = Preferences::SetInt(prefName.get(), now);
|
DebugOnly<nsresult> rv = Preferences::SetInt(prefName.get(), now);
|
||||||
MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference");
|
MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference");
|
||||||
notifyCompletion(true);
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
notifyCompletion(aReason == REASON_FINISHED);
|
||||||
nsAutoCString warnMsg;
|
|
||||||
warnMsg.AppendLiteral("Unable to vacuum database: ");
|
|
||||||
warnMsg.Append(mDBFilename);
|
|
||||||
warnMsg.AppendLiteral(" - ");
|
|
||||||
warnMsg.AppendInt(NS_ERROR_GET_CODE(aStatus));
|
|
||||||
NS_WARNING(warnMsg.get());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (MOZ_LOG_TEST(gStorageLog, LogLevel::Error)) {
|
|
||||||
MOZ_LOG(gStorageLog, LogLevel::Error,
|
|
||||||
("Vacuum failed with error: %d. Database was: '%s'", aStatus,
|
|
||||||
mDBFilename.get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyCompletion(false);
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult Vacuumer::notifyCompletion(bool aSucceeded) {
|
nsresult Vacuumer::notifyCompletion(bool aSucceeded) {
|
||||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||||
if (os) {
|
if (os) {
|
||||||
mozilla::Unused << os->NotifyObservers(
|
os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO,
|
||||||
nullptr, OBSERVER_TOPIC_VACUUM_END,
|
OBSERVER_DATA_VACUUM_END);
|
||||||
NS_ConvertUTF8toUTF16(mDBFilename).get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult rv = mParticipant->OnEndVacuum(aSucceeded);
|
nsresult rv = mParticipant->OnEndVacuum(aSucceeded);
|
||||||
@ -287,4 +353,5 @@ VacuumManager::Observe(nsISupports* aSubject, const char* aTopic,
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mozilla::storage
|
} // namespace storage
|
||||||
|
} // namespace mozilla
|
||||||
|
@ -193,38 +193,6 @@ interface mozIStorageAsyncConnection : nsISupports {
|
|||||||
*/
|
*/
|
||||||
void interrupt();
|
void interrupt();
|
||||||
|
|
||||||
/**
|
|
||||||
* Vacuum the main database plus all the attached one.
|
|
||||||
* If the database is in auto_vacuum = INCREMENTAL mode, this executes an
|
|
||||||
* incremental_vacuum, otherwise it will always execute a full vacuum.
|
|
||||||
*
|
|
||||||
* While it's possible to invoke this method directly, it's suggested, when
|
|
||||||
* possible, to use the VacuumManager instead.
|
|
||||||
* That means registering your component for the "vacuum-participant" XPCOM
|
|
||||||
* category, and implement the mozIStorageVacuumParticipant interface.
|
|
||||||
*
|
|
||||||
* @param [aCallback] Completion callback invoked once the operation is
|
|
||||||
* complete.
|
|
||||||
* @param [aUseIncremental] When set to true, this will try to convert the
|
|
||||||
* main schema to auto_vacuum = INCREMENTAL mode, if it's not set yet.
|
|
||||||
* When set to false, it will try to set auto_vacuum = NONE.
|
|
||||||
* Note a full vacuum will be executed if the auto_vacuum mode must be
|
|
||||||
* changed, otherwise an incremental vacuum will happen if the database
|
|
||||||
* is already in INCREMENTAL mode.
|
|
||||||
* @param [aSetPageSize] This can be used to change the database page_size, a
|
|
||||||
* full vacuum will be executed to persist the change. If the page
|
|
||||||
* size is already correct, or you pass 0, this will be a no-op.
|
|
||||||
* @throws If it's not possible to start the async vacuum operation, note in
|
|
||||||
* this case the callback won't be invoked.
|
|
||||||
* @note Vacuum will fail inside a transaction, or if there is an ongoing
|
|
||||||
* read statement.
|
|
||||||
*/
|
|
||||||
void asyncVacuum(
|
|
||||||
[optional] in mozIStorageCompletionCallback aCallback,
|
|
||||||
[optional] in boolean aUseIncremental,
|
|
||||||
[optional] in long aSetPageSize
|
|
||||||
);
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
//// Statement creation
|
//// Statement creation
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include "nsISupports.idl"
|
#include "nsISupports.idl"
|
||||||
|
|
||||||
interface mozIStorageAsyncConnection;
|
interface mozIStorageConnection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface contains the information that the Storage service needs to
|
* This interface contains the information that the Storage service needs to
|
||||||
@ -19,27 +19,19 @@ interface mozIStorageAsyncConnection;
|
|||||||
interface mozIStorageVacuumParticipant : nsISupports {
|
interface mozIStorageVacuumParticipant : nsISupports {
|
||||||
/**
|
/**
|
||||||
* The expected page size in bytes for the database. The vacuum manager will
|
* The expected page size in bytes for the database. The vacuum manager will
|
||||||
* try to correct the page size by executing a full vacuum.
|
* try to correct the page size during idle based on this value.
|
||||||
*
|
*
|
||||||
* @note If the database is using the WAL journal mode, the page size won't
|
* @note If the database is using the WAL journal mode, the page size won't
|
||||||
* be changed to the requested value. See bug 634374.
|
* be changed to the requested value. See bug 634374.
|
||||||
* @note Valid page size values are powers of 2 between 512 and 65536.
|
* @note Valid page size values are powers of 2 between 512 and 65536.
|
||||||
* The suggested value is mozIStorageConnection::defaultPageSize.
|
* The suggested value is mozIStorageConnection::defaultPageSize.
|
||||||
*/
|
*/
|
||||||
readonly attribute long expectedDatabasePageSize;
|
readonly attribute long expectedDatabasePageSize;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the main schema should be using auto_vacuum = INCREMENTAL.
|
|
||||||
* This will cause auto_vacuum to change to INCREMENTAL if it's not set yet.
|
|
||||||
* It is not possible to change mode of any attached databases through this,
|
|
||||||
* to do that you must open a separate connection and use asyncVacuum() on it.
|
|
||||||
*/
|
|
||||||
readonly attribute boolean useIncrementalVacuum;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection to the database file to be vacuumed.
|
* Connection to the database file to be vacuumed.
|
||||||
*/
|
*/
|
||||||
readonly attribute mozIStorageAsyncConnection databaseConnection;
|
readonly attribute mozIStorageConnection databaseConnection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies when a vacuum operation begins. Listeners should avoid using the
|
* Notifies when a vacuum operation begins. Listeners should avoid using the
|
||||||
@ -49,9 +41,9 @@ interface mozIStorageVacuumParticipant : nsISupports {
|
|||||||
* opt-out for now, it will be retried later. Useful when participant
|
* opt-out for now, it will be retried later. Useful when participant
|
||||||
* is running some other heavy operation that can't be interrupted.
|
* is running some other heavy operation that can't be interrupted.
|
||||||
*
|
*
|
||||||
* @note When a vacuum operation starts or ends it will also dispatch global
|
* @note When a vacuum operation starts or ends it will also dispatch a global
|
||||||
* "vacuum-begin" and "vacuum-end" notifications through the observer
|
* "heavy-io-task" notification through the observer service with the
|
||||||
* service with the data argument being the database filename.
|
* data argument being either "vacuum-begin" or "vacuum-end".
|
||||||
*/
|
*/
|
||||||
boolean onBeginVacuum();
|
boolean onBeginVacuum();
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
#include "nsIFile.h"
|
#include "nsIFile.h"
|
||||||
#include "nsIFileURL.h"
|
#include "nsIFileURL.h"
|
||||||
#include "nsIXPConnect.h"
|
#include "nsIXPConnect.h"
|
||||||
#include "mozilla/AppShutdown.h"
|
|
||||||
#include "mozilla/Telemetry.h"
|
#include "mozilla/Telemetry.h"
|
||||||
#include "mozilla/Mutex.h"
|
#include "mozilla/Mutex.h"
|
||||||
#include "mozilla/CondVar.h"
|
#include "mozilla/CondVar.h"
|
||||||
@ -420,151 +419,6 @@ class CloseListener final : public mozIStorageCompletionCallback {
|
|||||||
|
|
||||||
NS_IMPL_ISUPPORTS(CloseListener, mozIStorageCompletionCallback)
|
NS_IMPL_ISUPPORTS(CloseListener, mozIStorageCompletionCallback)
|
||||||
|
|
||||||
class AsyncVacuumEvent final : public Runnable {
|
|
||||||
public:
|
|
||||||
AsyncVacuumEvent(Connection* aConnection,
|
|
||||||
mozIStorageCompletionCallback* aCallback,
|
|
||||||
bool aUseIncremental, int32_t aSetPageSize)
|
|
||||||
: Runnable("storage::AsyncVacuum"),
|
|
||||||
mConnection(aConnection),
|
|
||||||
mCallback(aCallback),
|
|
||||||
mUseIncremental(aUseIncremental),
|
|
||||||
mSetPageSize(aSetPageSize),
|
|
||||||
mStatus(NS_ERROR_UNEXPECTED) {}
|
|
||||||
|
|
||||||
NS_IMETHOD Run() override {
|
|
||||||
// This is initially dispatched to the helper thread, then re-dispatched
|
|
||||||
// to the opener thread, where it will callback.
|
|
||||||
if (IsOnCurrentSerialEventTarget(mConnection->eventTargetOpenedOn)) {
|
|
||||||
// Send the completion event.
|
|
||||||
if (mCallback) {
|
|
||||||
mozilla::Unused << mCallback->Complete(mStatus, nullptr);
|
|
||||||
}
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure to invoke the callback regardless of errors.
|
|
||||||
auto guard = MakeScopeExit([&]() {
|
|
||||||
mConnection->mIsStatementOnHelperThreadInterruptible = false;
|
|
||||||
mozilla::Unused << mConnection->eventTargetOpenedOn->Dispatch(
|
|
||||||
this, NS_DISPATCH_NORMAL);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get list of attached databases.
|
|
||||||
nsCOMPtr<mozIStorageStatement> stmt;
|
|
||||||
nsresult rv = mConnection->CreateStatement(MOZ_STORAGE_UNIQUIFY_QUERY_STR
|
|
||||||
"PRAGMA database_list"_ns,
|
|
||||||
getter_AddRefs(stmt));
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
// We must accumulate names and loop through them later, otherwise VACUUM
|
|
||||||
// will see an ongoing statement and bail out.
|
|
||||||
nsTArray<nsCString> schemaNames;
|
|
||||||
bool hasResult = false;
|
|
||||||
while (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
|
|
||||||
nsAutoCString name;
|
|
||||||
rv = stmt->GetUTF8String(1, name);
|
|
||||||
if (NS_SUCCEEDED(rv) && !name.EqualsLiteral("temp")) {
|
|
||||||
schemaNames.AppendElement(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mStatus = NS_OK;
|
|
||||||
// Mark this vacuum as an interruptible operation, so it can be interrupted
|
|
||||||
// if the connection closes during shutdown.
|
|
||||||
mConnection->mIsStatementOnHelperThreadInterruptible = true;
|
|
||||||
for (const nsCString& schemaName : schemaNames) {
|
|
||||||
rv = this->Vacuum(schemaName);
|
|
||||||
if (NS_FAILED(rv)) {
|
|
||||||
// This is sub-optimal since it's only keeping the last error reason,
|
|
||||||
// but it will do for now.
|
|
||||||
mStatus = rv;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsresult Vacuum(const nsACString& aSchemaName) {
|
|
||||||
// Abort if we're in shutdown.
|
|
||||||
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
|
|
||||||
return NS_ERROR_ABORT;
|
|
||||||
}
|
|
||||||
int32_t removablePages = mConnection->RemovablePagesInFreeList(aSchemaName);
|
|
||||||
if (!removablePages) {
|
|
||||||
// There's no empty pages to remove, so skip this vacuum for now.
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
nsresult rv;
|
|
||||||
bool needsFullVacuum = true;
|
|
||||||
|
|
||||||
if (mSetPageSize) {
|
|
||||||
nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA ");
|
|
||||||
query.Append(aSchemaName);
|
|
||||||
query.AppendLiteral(".page_size = ");
|
|
||||||
query.AppendInt(mSetPageSize);
|
|
||||||
nsCOMPtr<mozIStorageStatement> stmt;
|
|
||||||
rv = mConnection->ExecuteSimpleSQL(query);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check auto_vacuum.
|
|
||||||
{
|
|
||||||
nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA ");
|
|
||||||
query.Append(aSchemaName);
|
|
||||||
query.AppendLiteral(".auto_vacuum");
|
|
||||||
nsCOMPtr<mozIStorageStatement> stmt;
|
|
||||||
rv = mConnection->CreateStatement(query, getter_AddRefs(stmt));
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
bool hasResult = false;
|
|
||||||
bool changeAutoVacuum = false;
|
|
||||||
if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
|
|
||||||
bool isIncrementalVacuum = stmt->AsInt32(0) == 2;
|
|
||||||
changeAutoVacuum = isIncrementalVacuum != mUseIncremental;
|
|
||||||
if (isIncrementalVacuum && !changeAutoVacuum) {
|
|
||||||
needsFullVacuum = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Changing auto_vacuum is only supported on the main schema.
|
|
||||||
if (aSchemaName.EqualsLiteral("main") && changeAutoVacuum) {
|
|
||||||
nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA ");
|
|
||||||
query.Append(aSchemaName);
|
|
||||||
query.AppendLiteral(".auto_vacuum = ");
|
|
||||||
query.AppendInt(mUseIncremental ? 2 : 0);
|
|
||||||
rv = mConnection->ExecuteSimpleSQL(query);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsFullVacuum) {
|
|
||||||
nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "VACUUM ");
|
|
||||||
query.Append(aSchemaName);
|
|
||||||
rv = mConnection->ExecuteSimpleSQL(query);
|
|
||||||
// TODO (Bug 1818039): Report failed vacuum telemetry.
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
} else {
|
|
||||||
nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA ");
|
|
||||||
query.Append(aSchemaName);
|
|
||||||
query.AppendLiteral(".incremental_vacuum(");
|
|
||||||
query.AppendInt(removablePages);
|
|
||||||
query.AppendLiteral(")");
|
|
||||||
rv = mConnection->ExecuteSimpleSQL(query);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
~AsyncVacuumEvent() override {
|
|
||||||
NS_ReleaseOnMainThread("AsyncVacuum::mConnection", mConnection.forget());
|
|
||||||
NS_ReleaseOnMainThread("AsyncVacuum::mCallback", mCallback.forget());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
RefPtr<Connection> mConnection;
|
|
||||||
nsCOMPtr<mozIStorageCompletionCallback> mCallback;
|
|
||||||
bool mUseIncremental;
|
|
||||||
int32_t mSetPageSize;
|
|
||||||
Atomic<nsresult> mStatus;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -576,7 +430,6 @@ Connection::Connection(Service* aService, int aFlags,
|
|||||||
: sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex"),
|
: sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex"),
|
||||||
sharedDBMutex("Connection::sharedDBMutex"),
|
sharedDBMutex("Connection::sharedDBMutex"),
|
||||||
eventTargetOpenedOn(WrapNotNull(GetCurrentSerialEventTarget())),
|
eventTargetOpenedOn(WrapNotNull(GetCurrentSerialEventTarget())),
|
||||||
mIsStatementOnHelperThreadInterruptible(false),
|
|
||||||
mDBConn(nullptr),
|
mDBConn(nullptr),
|
||||||
mDefaultTransactionType(mozIStorageConnection::TRANSACTION_DEFERRED),
|
mDefaultTransactionType(mozIStorageConnection::TRANSACTION_DEFERRED),
|
||||||
mDestroying(false),
|
mDestroying(false),
|
||||||
@ -589,8 +442,7 @@ Connection::Connection(Service* aService, int aFlags,
|
|||||||
aInterruptible),
|
aInterruptible),
|
||||||
mIgnoreLockingMode(aIgnoreLockingMode),
|
mIgnoreLockingMode(aIgnoreLockingMode),
|
||||||
mAsyncExecutionThreadShuttingDown(false),
|
mAsyncExecutionThreadShuttingDown(false),
|
||||||
mConnectionClosed(false),
|
mConnectionClosed(false) {
|
||||||
mGrowthChunkSize(0) {
|
|
||||||
MOZ_ASSERT(!mIgnoreLockingMode || mFlags & SQLITE_OPEN_READONLY,
|
MOZ_ASSERT(!mIgnoreLockingMode || mFlags & SQLITE_OPEN_READONLY,
|
||||||
"Can't ignore locking for a non-readonly connection!");
|
"Can't ignore locking for a non-readonly connection!");
|
||||||
mStorageService->registerConnection(this);
|
mStorageService->registerConnection(this);
|
||||||
@ -1662,17 +1514,6 @@ Connection::AsyncClose(mozIStorageCompletionCallback* aCallback) {
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're closing the connection during shutdown, and there is an
|
|
||||||
// interruptible statement running on the helper thread, issue a
|
|
||||||
// sqlite3_interrupt() to avoid crashing when that statement takes a long
|
|
||||||
// time (for example a vacuum).
|
|
||||||
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed) &&
|
|
||||||
mInterruptible && mIsStatementOnHelperThreadInterruptible) {
|
|
||||||
MOZ_ASSERT(!isClosing(), "Must not be closing, see Interrupt()");
|
|
||||||
DebugOnly<nsresult> rv2 = Interrupt();
|
|
||||||
MOZ_ASSERT(NS_SUCCEEDED(rv2));
|
|
||||||
}
|
|
||||||
|
|
||||||
// setClosedState nullifies our connection pointer, so we take a raw pointer
|
// setClosedState nullifies our connection pointer, so we take a raw pointer
|
||||||
// off it, to pass it through the close procedure.
|
// off it, to pass it through the close procedure.
|
||||||
sqlite3* nativeConn = mDBConn;
|
sqlite3* nativeConn = mDBConn;
|
||||||
@ -1921,36 +1762,6 @@ Connection::Interrupt() {
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
|
||||||
Connection::AsyncVacuum(mozIStorageCompletionCallback* aCallback,
|
|
||||||
bool aUseIncremental, int32_t aSetPageSize) {
|
|
||||||
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
|
|
||||||
// Abort if we're shutting down.
|
|
||||||
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
|
|
||||||
return NS_ERROR_ABORT;
|
|
||||||
}
|
|
||||||
// Check if AsyncClose or Close were already invoked.
|
|
||||||
if (!connectionReady()) {
|
|
||||||
return NS_ERROR_NOT_INITIALIZED;
|
|
||||||
}
|
|
||||||
nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
|
|
||||||
if (NS_FAILED(rv)) {
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
nsIEventTarget* asyncThread = getAsyncExecutionTarget();
|
|
||||||
if (!asyncThread) {
|
|
||||||
return NS_ERROR_NOT_INITIALIZED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and dispatch our vacuum event to the background thread.
|
|
||||||
nsCOMPtr<nsIRunnable> vacuumEvent =
|
|
||||||
new AsyncVacuumEvent(this, aCallback, aUseIncremental, aSetPageSize);
|
|
||||||
rv = asyncThread->Dispatch(vacuumEvent, NS_DISPATCH_NORMAL);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
Connection::GetDefaultPageSize(int32_t* _defaultPageSize) {
|
Connection::GetDefaultPageSize(int32_t* _defaultPageSize) {
|
||||||
*_defaultPageSize = Service::kDefaultPageSize;
|
*_defaultPageSize = Service::kDefaultPageSize;
|
||||||
@ -2469,59 +2280,15 @@ Connection::SetGrowthIncrement(int32_t aChunkSize,
|
|||||||
return NS_ERROR_FILE_TOO_BIG;
|
return NS_ERROR_FILE_TOO_BIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
int srv = ::sqlite3_file_control(
|
(void)::sqlite3_file_control(mDBConn,
|
||||||
mDBConn,
|
aDatabaseName.Length()
|
||||||
aDatabaseName.Length() ? nsPromiseFlatCString(aDatabaseName).get()
|
? nsPromiseFlatCString(aDatabaseName).get()
|
||||||
: nullptr,
|
: nullptr,
|
||||||
SQLITE_FCNTL_CHUNK_SIZE, &aChunkSize);
|
SQLITE_FCNTL_CHUNK_SIZE, &aChunkSize);
|
||||||
if (srv == SQLITE_OK) {
|
|
||||||
mGrowthChunkSize = aChunkSize;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t Connection::RemovablePagesInFreeList(const nsACString& aSchemaName) {
|
|
||||||
int32_t freeListPagesCount = 0;
|
|
||||||
if (!isConnectionReadyOnThisThread()) {
|
|
||||||
MOZ_ASSERT(false, "Database connection is not ready");
|
|
||||||
return freeListPagesCount;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA ");
|
|
||||||
query.Append(aSchemaName);
|
|
||||||
query.AppendLiteral(".freelist_count");
|
|
||||||
nsCOMPtr<mozIStorageStatement> stmt;
|
|
||||||
DebugOnly<nsresult> rv = CreateStatement(query, getter_AddRefs(stmt));
|
|
||||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
||||||
bool hasResult = false;
|
|
||||||
if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
|
|
||||||
freeListPagesCount = stmt->AsInt32(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If there's no chunk size set, any page is good to be removed.
|
|
||||||
if (mGrowthChunkSize == 0 || freeListPagesCount == 0) {
|
|
||||||
return freeListPagesCount;
|
|
||||||
}
|
|
||||||
int32_t pageSize;
|
|
||||||
{
|
|
||||||
nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA ");
|
|
||||||
query.Append(aSchemaName);
|
|
||||||
query.AppendLiteral(".page_size");
|
|
||||||
nsCOMPtr<mozIStorageStatement> stmt;
|
|
||||||
DebugOnly<nsresult> rv = CreateStatement(query, getter_AddRefs(stmt));
|
|
||||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
||||||
bool hasResult = false;
|
|
||||||
if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
|
|
||||||
pageSize = stmt->AsInt32(0);
|
|
||||||
} else {
|
|
||||||
MOZ_ASSERT(false, "Couldn't get page_size");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::max(0, freeListPagesCount - (mGrowthChunkSize / pageSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
Connection::EnableModule(const nsACString& aModuleName) {
|
Connection::EnableModule(const nsACString& aModuleName) {
|
||||||
if (!connectionReady()) {
|
if (!connectionReady()) {
|
||||||
|
@ -317,25 +317,6 @@ class Connection final : public mozIStorageConnection,
|
|||||||
*/
|
*/
|
||||||
void RecordQueryStatus(int srv);
|
void RecordQueryStatus(int srv);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of pages in the free list that can be removed.
|
|
||||||
*
|
|
||||||
* A database may use chunked growth to reduce filesystem fragmentation, then
|
|
||||||
* Sqlite will allocate and release multiple pages in chunks. We want to
|
|
||||||
* preserve the chunked space to reduce the likelihood of fragmentation,
|
|
||||||
* releasing free pages only when there's a large amount of them. This can be
|
|
||||||
* used to decide if it's worth vacuuming the database and how many pages can
|
|
||||||
* be vacuumed in case of incremental vacuum.
|
|
||||||
* Note this returns 0, and asserts, in case of errors.
|
|
||||||
*/
|
|
||||||
int32_t RemovablePagesInFreeList(const nsACString& aSchemaName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the statement currently running on the helper thread can be
|
|
||||||
* interrupted.
|
|
||||||
*/
|
|
||||||
Atomic<bool> mIsStatementOnHelperThreadInterruptible;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~Connection();
|
~Connection();
|
||||||
nsresult initializeInternal();
|
nsresult initializeInternal();
|
||||||
@ -502,11 +483,6 @@ class Connection final : public mozIStorageConnection,
|
|||||||
* sharedAsyncExecutionMutex.
|
* sharedAsyncExecutionMutex.
|
||||||
*/
|
*/
|
||||||
bool mConnectionClosed;
|
bool mConnectionClosed;
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the growth increment chunk size, set through SetGrowthIncrement().
|
|
||||||
*/
|
|
||||||
Atomic<int32_t> mGrowthChunkSize;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,7 +7,3 @@
|
|||||||
XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.ini"]
|
XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.ini"]
|
||||||
|
|
||||||
TEST_DIRS += ["gtest"]
|
TEST_DIRS += ["gtest"]
|
||||||
|
|
||||||
TESTING_JS_MODULES += [
|
|
||||||
"unit/VacuumParticipant.sys.mjs",
|
|
||||||
]
|
|
||||||
|
@ -4,106 +4,95 @@
|
|||||||
|
|
||||||
// This testing component is used in test_vacuum* tests.
|
// This testing component is used in test_vacuum* tests.
|
||||||
|
|
||||||
const CAT_NAME = "vacuum-participant";
|
/**
|
||||||
const CONTRACT_ID = "@unit.test.com/test-vacuum-participant;1";
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
import { MockRegistrar } from "resource://testing-common/MockRegistrar.sys.mjs";
|
/**
|
||||||
import { TestUtils } from "resource://testing-common/TestUtils.sys.mjs";
|
* Opens and returns a connection to the provided database file.
|
||||||
|
* @param nsIFile interface to the database file.
|
||||||
|
*/
|
||||||
|
function getDatabase(aFile) {
|
||||||
|
return Services.storage.openDatabase(aFile);
|
||||||
|
}
|
||||||
|
|
||||||
export class VacuumParticipant {
|
export function VacuumParticipant() {
|
||||||
#dbConn;
|
this._dbConn = getDatabase(new_db_file("testVacuum"));
|
||||||
#expectedPageSize = 0;
|
Services.obs.addObserver(this, "test-options");
|
||||||
#useIncrementalVacuum = false;
|
}
|
||||||
#grant = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a VacuumParticipant instance.
|
|
||||||
* Note: After creation you must await instance.promiseRegistered() to ensure
|
|
||||||
* Category Caches have been updated.
|
|
||||||
*
|
|
||||||
* @param {mozIStorageAsyncConnection} databaseConnection
|
|
||||||
* The connection to be vacuumed.
|
|
||||||
* @param {Number} [expectedPageSize]
|
|
||||||
* Used to change the database page size.
|
|
||||||
* @param {boolean} [useIncrementalVacuum]
|
|
||||||
* Whether to enable incremental vacuum on the database.
|
|
||||||
* @param {boolean} [grant]
|
|
||||||
* Whether the vacuum operation should be granted.
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
databaseConnection,
|
|
||||||
{ expectedPageSize = 0, useIncrementalVacuum = false, grant = true } = {}
|
|
||||||
) {
|
|
||||||
this.#dbConn = databaseConnection;
|
|
||||||
|
|
||||||
// Register as the only participant.
|
|
||||||
this.#unregisterAllParticipants();
|
|
||||||
this.#registerAsParticipant();
|
|
||||||
|
|
||||||
this.#expectedPageSize = expectedPageSize;
|
|
||||||
this.#useIncrementalVacuum = useIncrementalVacuum;
|
|
||||||
this.#grant = grant;
|
|
||||||
|
|
||||||
this.QueryInterface = ChromeUtils.generateQI([
|
|
||||||
"mozIStorageVacuumParticipant",
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
promiseRegistered() {
|
|
||||||
// The category manager dispatches change notifications to the main thread,
|
|
||||||
// so we must wait one tick.
|
|
||||||
return TestUtils.waitForTick();
|
|
||||||
}
|
|
||||||
|
|
||||||
#registerAsParticipant() {
|
|
||||||
MockRegistrar.register(CONTRACT_ID, this);
|
|
||||||
Services.catMan.addCategoryEntry(
|
|
||||||
CAT_NAME,
|
|
||||||
"vacuumParticipant",
|
|
||||||
CONTRACT_ID,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#unregisterAllParticipants() {
|
|
||||||
// First unregister other participants.
|
|
||||||
for (let { data: entry } of Services.catMan.enumerateCategory(CAT_NAME)) {
|
|
||||||
Services.catMan.deleteCategoryEntry("vacuum-participant", entry, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async dispose() {
|
|
||||||
this.#unregisterAllParticipants();
|
|
||||||
MockRegistrar.unregister(CONTRACT_ID);
|
|
||||||
await new Promise(resolve => {
|
|
||||||
this.#dbConn.asyncClose(resolve);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
VacuumParticipant.prototype = {
|
||||||
get expectedDatabasePageSize() {
|
get expectedDatabasePageSize() {
|
||||||
return this.#expectedPageSize;
|
return this._dbConn.defaultPageSize;
|
||||||
}
|
},
|
||||||
|
|
||||||
get useIncrementalVacuum() {
|
|
||||||
return this.#useIncrementalVacuum;
|
|
||||||
}
|
|
||||||
|
|
||||||
get databaseConnection() {
|
get databaseConnection() {
|
||||||
return this.#dbConn;
|
return this._dbConn;
|
||||||
}
|
},
|
||||||
|
|
||||||
onBeginVacuum() {
|
_grant: true,
|
||||||
if (!this.#grant) {
|
onBeginVacuum: function TVP_onBeginVacuum() {
|
||||||
|
if (!this._grant) {
|
||||||
|
this._grant = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Services.obs.notifyObservers(null, "test-begin-vacuum");
|
Services.obs.notifyObservers(null, "test-begin-vacuum");
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
onEndVacuum(succeeded) {
|
onEndVacuum: function TVP_EndVacuum(aSucceeded) {
|
||||||
Services.obs.notifyObservers(
|
if (this._stmt) {
|
||||||
null,
|
this._stmt.finalize();
|
||||||
succeeded ? "test-end-vacuum-success" : "test-end-vacuum-failure"
|
}
|
||||||
);
|
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) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
this._dbConn = getDatabase(new_db_file("testVacuum2"));
|
||||||
|
} else if (aData == "wal-fail") {
|
||||||
|
try {
|
||||||
|
this._dbConn.close();
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
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.asyncClose();
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
this._dbConn = Services.storage.openSpecialDatabase("memory");
|
||||||
|
} else if (aData == "dispose") {
|
||||||
|
Services.obs.removeObserver(this, "test-options");
|
||||||
|
try {
|
||||||
|
this._dbConn.asyncClose();
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
QueryInterface: ChromeUtils.generateQI([
|
||||||
|
"mozIStorageVacuumParticipant",
|
||||||
|
"nsIObserver",
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
@ -11,10 +11,12 @@ var { AppConstants } = ChromeUtils.importESModule(
|
|||||||
|
|
||||||
ChromeUtils.defineESModuleGetters(this, {
|
ChromeUtils.defineESModuleGetters(this, {
|
||||||
Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
|
Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
|
||||||
TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
|
|
||||||
TestUtils: "resource://testing-common/TestUtils.sys.mjs",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
||||||
|
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
const OPEN_HISTOGRAM = "SQLITE_STORE_OPEN";
|
const OPEN_HISTOGRAM = "SQLITE_STORE_OPEN";
|
||||||
const QUERY_HISTOGRAM = "SQLITE_STORE_QUERY";
|
const QUERY_HISTOGRAM = "SQLITE_STORE_QUERY";
|
||||||
|
|
||||||
|
@ -2,367 +2,325 @@
|
|||||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// This file tests the Vacuum Manager and asyncVacuum().
|
// This file tests the Vacuum Manager.
|
||||||
|
|
||||||
const { VacuumParticipant } = ChromeUtils.importESModule(
|
const { MockRegistrar } = ChromeUtils.importESModule(
|
||||||
"resource://testing-common/VacuumParticipant.sys.mjs"
|
"resource://testing-common/MockRegistrar.sys.mjs"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
const CONTRACT_ID = "@unit.test.com/test-vacuum-participant;1";
|
||||||
|
|
||||||
|
MockRegistrar.registerESM(
|
||||||
|
CONTRACT_ID,
|
||||||
|
"resource://test/VacuumParticipant.sys.mjs",
|
||||||
|
"VacuumParticipant"
|
||||||
|
);
|
||||||
|
|
||||||
|
let { catMan } = Services;
|
||||||
|
// Temporary unregister other participants for this test.
|
||||||
|
for (let { data: entry } of catMan.enumerateCategory(CATEGORY_NAME)) {
|
||||||
|
print("Check if the found category entry (" + entry + ") is expected.");
|
||||||
|
catMan.deleteCategoryEntry("vacuum-participant", entry, false);
|
||||||
|
}
|
||||||
|
catMan.addCategoryEntry(
|
||||||
|
CATEGORY_NAME,
|
||||||
|
"vacuumParticipant",
|
||||||
|
CONTRACT_ID,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
print("Check the test entry exists.");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a fake idle-daily notification to the VACUUM Manager.
|
* Sends a fake idle-daily notification to the VACUUM Manager.
|
||||||
*/
|
*/
|
||||||
function synthesize_idle_daily() {
|
function synthesize_idle_daily() {
|
||||||
Cc["@mozilla.org/storage/vacuum;1"]
|
let vm = Cc["@mozilla.org/storage/vacuum;1"].getService(Ci.nsIObserver);
|
||||||
.getService(Ci.nsIObserver)
|
vm.observe(null, "idle-daily", null);
|
||||||
.observe(null, "idle-daily", null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new nsIFile reference for a profile database.
|
* Returns a new nsIFile reference for a profile database.
|
||||||
* @param filename for the database, excluded the .sqlite extension.
|
* @param filename for the database, excluded the .sqlite extension.
|
||||||
*/
|
*/
|
||||||
function new_db_file(name = "testVacuum") {
|
function new_db_file(name) {
|
||||||
let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||||
file.append(name + ".sqlite");
|
file.append(name + ".sqlite");
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset_vacuum_date(name = "testVacuum") {
|
function run_test() {
|
||||||
let date = parseInt(Date.now() / 1000 - 31 * 86400);
|
do_test_pending();
|
||||||
// Set last VACUUM to a date in the past.
|
|
||||||
Services.prefs.setIntPref(`storage.vacuum.last.${name}.sqlite`, date);
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_vacuum_date(name = "testVacuum") {
|
// Change initial page size. Do it immediately since it would require an
|
||||||
return Services.prefs.getIntPref(`storage.vacuum.last.${name}.sqlite`, 0);
|
// additional vacuum op to do it later. As a bonus this makes the page size
|
||||||
}
|
// change test really fast since it only has to check results.
|
||||||
|
let conn = getDatabase(new_db_file("testVacuum"));
|
||||||
add_setup(async function() {
|
conn.executeSimpleSQL("PRAGMA page_size = 1024");
|
||||||
// turn on Cu.isInAutomation
|
print("Check current page size.");
|
||||||
Services.prefs.setBoolPref(
|
|
||||||
"security.turn_off_all_security_so_that_viruses_can_take_over_this_computer",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_common_vacuum() {
|
|
||||||
let last_vacuum_date = reset_vacuum_date();
|
|
||||||
info("Test that a VACUUM correctly happens and all notifications are fired.");
|
|
||||||
let promiseTestVacuumBegin = TestUtils.topicObserved("test-begin-vacuum");
|
|
||||||
let promiseTestVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success");
|
|
||||||
let promiseVacuumBegin = TestUtils.topicObserved("vacuum-begin");
|
|
||||||
let promiseVacuumEnd = TestUtils.topicObserved("vacuum-end");
|
|
||||||
|
|
||||||
let participant = new VacuumParticipant(
|
|
||||||
Services.storage.openDatabase(new_db_file())
|
|
||||||
);
|
|
||||||
await participant.promiseRegistered();
|
|
||||||
synthesize_idle_daily();
|
|
||||||
// Wait for notifications.
|
|
||||||
await Promise.all([
|
|
||||||
promiseTestVacuumBegin,
|
|
||||||
promiseTestVacuumEnd,
|
|
||||||
promiseVacuumBegin,
|
|
||||||
promiseVacuumEnd,
|
|
||||||
]);
|
|
||||||
Assert.greater(get_vacuum_date(), last_vacuum_date);
|
|
||||||
await participant.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_skipped_if_recent_vacuum() {
|
|
||||||
info("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 skipped notification.
|
|
||||||
let promiseSkipped = TestUtils.topicObserved("vacuum-skip");
|
|
||||||
|
|
||||||
let participant = new VacuumParticipant(
|
|
||||||
Services.storage.openDatabase(new_db_file())
|
|
||||||
);
|
|
||||||
await participant.promiseRegistered();
|
|
||||||
synthesize_idle_daily();
|
|
||||||
|
|
||||||
// Check that VACUUM has been skipped.
|
|
||||||
await promiseSkipped;
|
|
||||||
|
|
||||||
await participant.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_page_size_change() {
|
|
||||||
info("Test that a VACUUM changes page_size");
|
|
||||||
reset_vacuum_date();
|
|
||||||
|
|
||||||
let conn = Services.storage.openDatabase(new_db_file());
|
|
||||||
info("Check initial page size.");
|
|
||||||
let stmt = conn.createStatement("PRAGMA page_size");
|
let stmt = conn.createStatement("PRAGMA page_size");
|
||||||
Assert.ok(stmt.executeStep());
|
try {
|
||||||
Assert.equal(stmt.row.page_size, conn.defaultPageSize);
|
while (stmt.executeStep()) {
|
||||||
stmt.finalize();
|
Assert.equal(stmt.row.page_size, 1024);
|
||||||
await populateFreeList(conn);
|
}
|
||||||
|
} finally {
|
||||||
let participant = new VacuumParticipant(conn, { expectedPageSize: 1024 });
|
stmt.finalize();
|
||||||
await participant.promiseRegistered();
|
|
||||||
let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success");
|
|
||||||
synthesize_idle_daily();
|
|
||||||
await promiseVacuumEnd;
|
|
||||||
|
|
||||||
info("Check that page size was updated.");
|
|
||||||
stmt = conn.createStatement("PRAGMA page_size");
|
|
||||||
Assert.ok(stmt.executeStep());
|
|
||||||
Assert.equal(stmt.row.page_size, 1024);
|
|
||||||
stmt.finalize();
|
|
||||||
|
|
||||||
await participant.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_skipped_optout_vacuum() {
|
|
||||||
info("Test that a VACUUM is skipped if the participant wants to opt-out.");
|
|
||||||
reset_vacuum_date();
|
|
||||||
|
|
||||||
let participant = new VacuumParticipant(
|
|
||||||
Services.storage.openDatabase(new_db_file()),
|
|
||||||
{ grant: false }
|
|
||||||
);
|
|
||||||
await participant.promiseRegistered();
|
|
||||||
// Wait for VACUUM skipped notification.
|
|
||||||
let promiseSkipped = TestUtils.topicObserved("vacuum-skip");
|
|
||||||
|
|
||||||
synthesize_idle_daily();
|
|
||||||
|
|
||||||
// Check that VACUUM has been skipped.
|
|
||||||
await promiseSkipped;
|
|
||||||
|
|
||||||
await participant.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_memory_database_crash() {
|
|
||||||
info("Test that we don't crash trying to vacuum a memory database");
|
|
||||||
reset_vacuum_date();
|
|
||||||
|
|
||||||
let participant = new VacuumParticipant(
|
|
||||||
Services.storage.openSpecialDatabase("memory")
|
|
||||||
);
|
|
||||||
await participant.promiseRegistered();
|
|
||||||
// Wait for VACUUM skipped notification.
|
|
||||||
let promiseSkipped = TestUtils.topicObserved("vacuum-skip");
|
|
||||||
|
|
||||||
synthesize_idle_daily();
|
|
||||||
|
|
||||||
// Check that VACUUM has been skipped.
|
|
||||||
await promiseSkipped;
|
|
||||||
|
|
||||||
await participant.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_async_connection() {
|
|
||||||
info("Test we can vacuum an async connection");
|
|
||||||
reset_vacuum_date();
|
|
||||||
|
|
||||||
let conn = await openAsyncDatabase(new_db_file());
|
|
||||||
await populateFreeList(conn);
|
|
||||||
let participant = new VacuumParticipant(conn);
|
|
||||||
await participant.promiseRegistered();
|
|
||||||
|
|
||||||
let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success");
|
|
||||||
synthesize_idle_daily();
|
|
||||||
await promiseVacuumEnd;
|
|
||||||
|
|
||||||
await participant.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_change_to_incremental_vacuum() {
|
|
||||||
info("Test we can change to incremental vacuum");
|
|
||||||
reset_vacuum_date();
|
|
||||||
|
|
||||||
let conn = Services.storage.openDatabase(new_db_file());
|
|
||||||
info("Check initial vacuum.");
|
|
||||||
let stmt = conn.createStatement("PRAGMA auto_vacuum");
|
|
||||||
Assert.ok(stmt.executeStep());
|
|
||||||
Assert.equal(stmt.row.auto_vacuum, 0);
|
|
||||||
stmt.finalize();
|
|
||||||
await populateFreeList(conn);
|
|
||||||
|
|
||||||
let participant = new VacuumParticipant(conn, { useIncrementalVacuum: true });
|
|
||||||
await participant.promiseRegistered();
|
|
||||||
let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success");
|
|
||||||
synthesize_idle_daily();
|
|
||||||
await promiseVacuumEnd;
|
|
||||||
|
|
||||||
info("Check that auto_vacuum was updated.");
|
|
||||||
stmt = conn.createStatement("PRAGMA auto_vacuum");
|
|
||||||
Assert.ok(stmt.executeStep());
|
|
||||||
Assert.equal(stmt.row.auto_vacuum, 2);
|
|
||||||
stmt.finalize();
|
|
||||||
|
|
||||||
await participant.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_change_from_incremental_vacuum() {
|
|
||||||
info("Test we can change from incremental vacuum");
|
|
||||||
reset_vacuum_date();
|
|
||||||
|
|
||||||
let conn = Services.storage.openDatabase(new_db_file());
|
|
||||||
conn.executeSimpleSQL("PRAGMA auto_vacuum = 2");
|
|
||||||
info("Check initial vacuum.");
|
|
||||||
let stmt = conn.createStatement("PRAGMA auto_vacuum");
|
|
||||||
Assert.ok(stmt.executeStep());
|
|
||||||
Assert.equal(stmt.row.auto_vacuum, 2);
|
|
||||||
stmt.finalize();
|
|
||||||
await populateFreeList(conn);
|
|
||||||
|
|
||||||
let participant = new VacuumParticipant(conn, {
|
|
||||||
useIncrementalVacuum: false,
|
|
||||||
});
|
|
||||||
await participant.promiseRegistered();
|
|
||||||
let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success");
|
|
||||||
synthesize_idle_daily();
|
|
||||||
await promiseVacuumEnd;
|
|
||||||
|
|
||||||
info("Check that auto_vacuum was updated.");
|
|
||||||
stmt = conn.createStatement("PRAGMA auto_vacuum");
|
|
||||||
Assert.ok(stmt.executeStep());
|
|
||||||
Assert.equal(stmt.row.auto_vacuum, 0);
|
|
||||||
stmt.finalize();
|
|
||||||
|
|
||||||
await participant.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_attached_vacuum() {
|
|
||||||
info("Test attached database is not a problem");
|
|
||||||
reset_vacuum_date();
|
|
||||||
|
|
||||||
let conn = Services.storage.openDatabase(new_db_file());
|
|
||||||
let conn2 = Services.storage.openDatabase(new_db_file("attached"));
|
|
||||||
|
|
||||||
info("Attach " + conn2.databaseFile.path);
|
|
||||||
conn.executeSimpleSQL(
|
|
||||||
`ATTACH DATABASE '${conn2.databaseFile.path}' AS attached`
|
|
||||||
);
|
|
||||||
await asyncClose(conn2);
|
|
||||||
let stmt = conn.createStatement("PRAGMA database_list");
|
|
||||||
let schemas = [];
|
|
||||||
while (stmt.executeStep()) {
|
|
||||||
schemas.push(stmt.row.name);
|
|
||||||
}
|
}
|
||||||
Assert.deepEqual(schemas, ["main", "attached"]);
|
|
||||||
stmt.finalize();
|
|
||||||
|
|
||||||
await populateFreeList(conn);
|
load_test_vacuum_component();
|
||||||
await populateFreeList(conn, "attached");
|
|
||||||
|
|
||||||
let participant = new VacuumParticipant(conn);
|
run_next_test();
|
||||||
await participant.promiseRegistered();
|
}
|
||||||
let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success");
|
|
||||||
synthesize_idle_daily();
|
const TESTS = [
|
||||||
await promiseVacuumEnd;
|
function test_common_vacuum() {
|
||||||
|
print(
|
||||||
await participant.dispose();
|
"\n*** Test that a VACUUM correctly happens and all notifications are fired."
|
||||||
});
|
);
|
||||||
|
// Wait for VACUUM begin.
|
||||||
add_task(async function test_vacuum_fail() {
|
let beginVacuumReceived = false;
|
||||||
info("Test a failed vacuum");
|
Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) {
|
||||||
reset_vacuum_date();
|
Services.obs.removeObserver(onVacuum, aTopic);
|
||||||
|
beginVacuumReceived = true;
|
||||||
let conn = Services.storage.openDatabase(new_db_file());
|
}, "test-begin-vacuum");
|
||||||
// Cannot vacuum in a transaction.
|
|
||||||
conn.beginTransaction();
|
// Wait for heavy IO notifications.
|
||||||
await populateFreeList(conn);
|
let heavyIOTaskBeginReceived = false;
|
||||||
|
let heavyIOTaskEndReceived = false;
|
||||||
let participant = new VacuumParticipant(conn);
|
Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) {
|
||||||
await participant.promiseRegistered();
|
if (heavyIOTaskBeginReceived && heavyIOTaskEndReceived) {
|
||||||
let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-failure");
|
Services.obs.removeObserver(onVacuum, aTopic);
|
||||||
synthesize_idle_daily();
|
}
|
||||||
await promiseVacuumEnd;
|
|
||||||
|
if (aData == "vacuum-begin") {
|
||||||
conn.commitTransaction();
|
heavyIOTaskBeginReceived = true;
|
||||||
await participant.dispose();
|
} else if (aData == "vacuum-end") {
|
||||||
});
|
heavyIOTaskEndReceived = true;
|
||||||
|
}
|
||||||
add_task(async function test_async_vacuum() {
|
}, "heavy-io-task");
|
||||||
// Since previous tests already go through most cases, this only checks
|
|
||||||
// the basics of directly calling asyncVacuum().
|
// Wait for VACUUM end.
|
||||||
info("Test synchronous connection");
|
Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) {
|
||||||
let conn = Services.storage.openDatabase(new_db_file());
|
Services.obs.removeObserver(onVacuum, aTopic);
|
||||||
await populateFreeList(conn);
|
print("Check we received onBeginVacuum");
|
||||||
let rv = await new Promise(resolve => {
|
Assert.ok(beginVacuumReceived);
|
||||||
conn.asyncVacuum(status => {
|
print("Check we received heavy-io-task notifications");
|
||||||
resolve(status);
|
Assert.ok(heavyIOTaskBeginReceived);
|
||||||
});
|
Assert.ok(heavyIOTaskEndReceived);
|
||||||
});
|
print("Received onEndVacuum");
|
||||||
Assert.ok(Components.isSuccessCode(rv));
|
run_next_test();
|
||||||
await asyncClose(conn);
|
}, "test-end-vacuum");
|
||||||
|
|
||||||
info("Test asynchronous connection");
|
synthesize_idle_daily();
|
||||||
conn = await openAsyncDatabase(new_db_file());
|
},
|
||||||
await populateFreeList(conn);
|
|
||||||
rv = await new Promise(resolve => {
|
function test_skipped_if_recent_vacuum() {
|
||||||
conn.asyncVacuum(status => {
|
print("\n*** Test that a VACUUM is skipped if it was run recently.");
|
||||||
resolve(status);
|
Services.prefs.setIntPref(
|
||||||
});
|
"storage.vacuum.last.testVacuum.sqlite",
|
||||||
});
|
parseInt(Date.now() / 1000)
|
||||||
Assert.ok(Components.isSuccessCode(rv));
|
);
|
||||||
await asyncClose(conn);
|
|
||||||
});
|
// Wait for VACUUM begin.
|
||||||
|
let vacuumObserver = {
|
||||||
add_task(async function test_vacuum_growth() {
|
gotNotification: false,
|
||||||
// Tests vacuum doesn't nullify chunked growth.
|
observe: function VO_observe(aSubject, aTopic, aData) {
|
||||||
let conn = Services.storage.openDatabase(new_db_file("incremental"));
|
this.gotNotification = true;
|
||||||
conn.executeSimpleSQL("PRAGMA auto_vacuum = INCREMENTAL");
|
},
|
||||||
conn.setGrowthIncrement(2 * conn.defaultPageSize, "");
|
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
|
||||||
await populateFreeList(conn);
|
};
|
||||||
let stmt = conn.createStatement("PRAGMA freelist_count");
|
Services.obs.addObserver(vacuumObserver, "test-begin-vacuum");
|
||||||
let count = 0;
|
|
||||||
Assert.ok(stmt.executeStep());
|
// Check after a couple seconds that no VACUUM has been run.
|
||||||
count = stmt.row.freelist_count;
|
do_timeout(2000, function() {
|
||||||
stmt.reset();
|
print("Check VACUUM did not run.");
|
||||||
Assert.greater(count, 2, "There's more than 2 page in freelist");
|
Assert.ok(!vacuumObserver.gotNotification);
|
||||||
|
Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum");
|
||||||
let rv = await new Promise(resolve => {
|
run_next_test();
|
||||||
conn.asyncVacuum(status => {
|
});
|
||||||
resolve(status);
|
|
||||||
}, true);
|
synthesize_idle_daily();
|
||||||
});
|
},
|
||||||
Assert.ok(Components.isSuccessCode(rv));
|
|
||||||
|
function test_page_size_change() {
|
||||||
Assert.ok(stmt.executeStep());
|
print("\n*** Test that a VACUUM changes page_size");
|
||||||
Assert.equal(
|
|
||||||
stmt.row.freelist_count,
|
// We did setup the database with a small page size, the previous vacuum
|
||||||
2,
|
// should have updated it.
|
||||||
"chunked growth space was preserved"
|
print("Check that page size was updated.");
|
||||||
);
|
let conn = getDatabase(new_db_file("testVacuum"));
|
||||||
stmt.reset();
|
let stmt = conn.createStatement("PRAGMA page_size");
|
||||||
|
try {
|
||||||
// A full vacuuum should not be executed if there's less free pages than
|
while (stmt.executeStep()) {
|
||||||
// chunked growth.
|
Assert.equal(stmt.row.page_size, conn.defaultPageSize);
|
||||||
rv = await new Promise(resolve => {
|
}
|
||||||
conn.asyncVacuum(status => {
|
} finally {
|
||||||
resolve(status);
|
stmt.finalize();
|
||||||
});
|
}
|
||||||
});
|
|
||||||
Assert.ok(Components.isSuccessCode(rv));
|
run_next_test();
|
||||||
|
},
|
||||||
Assert.ok(stmt.executeStep());
|
|
||||||
Assert.equal(
|
function test_skipped_optout_vacuum() {
|
||||||
stmt.row.freelist_count,
|
print(
|
||||||
2,
|
"\n*** Test that a VACUUM is skipped if the participant wants to opt-out."
|
||||||
"chunked growth space was preserved"
|
);
|
||||||
);
|
Services.obs.notifyObservers(null, "test-options", "opt-out");
|
||||||
stmt.finalize();
|
|
||||||
|
// Wait for VACUUM begin.
|
||||||
await asyncClose(conn);
|
let vacuumObserver = {
|
||||||
});
|
gotNotification: false,
|
||||||
|
observe: function VO_observe(aSubject, aTopic, aData) {
|
||||||
async function populateFreeList(conn, schema = "main") {
|
this.gotNotification = true;
|
||||||
await executeSimpleSQLAsync(conn, `CREATE TABLE ${schema}.test (id TEXT)`);
|
},
|
||||||
await executeSimpleSQLAsync(
|
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
|
||||||
conn,
|
};
|
||||||
`INSERT INTO ${schema}.test
|
Services.obs.addObserver(vacuumObserver, "test-begin-vacuum");
|
||||||
VALUES ${Array.from({ length: 3000 }, () => Math.random()).map(
|
|
||||||
v => "('" + v + "')"
|
// Check after a couple seconds that no VACUUM has been run.
|
||||||
)}`
|
do_timeout(2000, function() {
|
||||||
);
|
print("Check VACUUM did not run.");
|
||||||
await executeSimpleSQLAsync(conn, `DROP TABLE ${schema}.test`);
|
Assert.ok(!vacuumObserver.gotNotification);
|
||||||
|
Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum");
|
||||||
|
run_next_test();
|
||||||
|
});
|
||||||
|
|
||||||
|
synthesize_idle_daily();
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Changing page size on WAL is not supported till Bug 634374 is properly fixed.
|
||||||
|
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: ChromeUtils.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: ChromeUtils.generateQI(["nsIObserver"]),
|
||||||
|
};
|
||||||
|
Services.obs.addObserver(vacuumObserver, "test-begin-vacuum");
|
||||||
|
|
||||||
|
// Check after a couple seconds that no VACUUM has been run.
|
||||||
|
do_timeout(2000, function() {
|
||||||
|
print("Check VACUUM did not run.");
|
||||||
|
Assert.ok(!vacuumObserver.gotNotification);
|
||||||
|
Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum");
|
||||||
|
run_next_test();
|
||||||
|
});
|
||||||
|
|
||||||
|
synthesize_idle_daily();
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Changing page size on WAL is not supported till Bug 634374 is properly fixed.
|
||||||
|
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: ChromeUtils.generateQI([Ci.nsIObserver])
|
||||||
|
}
|
||||||
|
Services.obs.addObserver(vacuumObserver, "test-end-vacuum", false);
|
||||||
|
|
||||||
|
synthesize_idle_daily();
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
];
|
||||||
|
|
||||||
|
function run_next_test() {
|
||||||
|
if (!TESTS.length) {
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
executeSoon(TESTS.shift());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,13 @@ export var MockRegistrar = Object.freeze({
|
|||||||
return cid;
|
return cid;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
registerESM(contractID, esmPath, symbol) {
|
||||||
|
return this.register(contractID, () => {
|
||||||
|
let exports = ChromeUtils.importESModule(esmPath);
|
||||||
|
return new exports[symbol]();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregister the mock.
|
* Unregister the mock.
|
||||||
*
|
*
|
||||||
|
@ -1825,18 +1825,8 @@ nsNavHistory::SetShouldStartFrecencyRecalculation(bool aVal) {
|
|||||||
//// mozIStorageVacuumParticipant
|
//// mozIStorageVacuumParticipant
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsNavHistory::GetDatabaseConnection(
|
nsNavHistory::GetDatabaseConnection(mozIStorageConnection** _DBConnection) {
|
||||||
mozIStorageAsyncConnection** _DBConnection) {
|
return GetDBConnection(_DBConnection);
|
||||||
NS_ENSURE_ARG_POINTER(_DBConnection);
|
|
||||||
nsCOMPtr<mozIStorageAsyncConnection> connection = mDB->MainConn();
|
|
||||||
connection.forget(_DBConnection);
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
|
||||||
nsNavHistory::GetUseIncrementalVacuum(bool* _useIncremental) {
|
|
||||||
*_useIncremental = false;
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
|
@ -255,43 +255,6 @@ XPCOMUtils.defineLazyGetter(lazy, "Barriers", () => {
|
|||||||
return Barriers;
|
return Barriers;
|
||||||
});
|
});
|
||||||
|
|
||||||
const VACUUM_CATEGORY = "vacuum-participant";
|
|
||||||
const VACUUM_CONTRACTID = "@sqlite.module.js/vacuum-participant;";
|
|
||||||
var registeredVacuumParticipants = new Map();
|
|
||||||
|
|
||||||
function registerVacuumParticipant(connectionData) {
|
|
||||||
let contractId = VACUUM_CONTRACTID + connectionData._identifier;
|
|
||||||
let factory = {
|
|
||||||
createInstance(iid) {
|
|
||||||
return connectionData.QueryInterface(iid);
|
|
||||||
},
|
|
||||||
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
|
||||||
};
|
|
||||||
let cid = Services.uuid.generateUUID();
|
|
||||||
Components.manager
|
|
||||||
.QueryInterface(Ci.nsIComponentRegistrar)
|
|
||||||
.registerFactory(cid, contractId, contractId, factory);
|
|
||||||
Services.catMan.addCategoryEntry(
|
|
||||||
VACUUM_CATEGORY,
|
|
||||||
contractId,
|
|
||||||
contractId,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
registeredVacuumParticipants.set(contractId, { cid, factory });
|
|
||||||
}
|
|
||||||
|
|
||||||
function unregisterVacuumParticipant(connectionData) {
|
|
||||||
let contractId = VACUUM_CONTRACTID + connectionData._identifier;
|
|
||||||
let component = registeredVacuumParticipants.get(contractId);
|
|
||||||
if (component) {
|
|
||||||
Components.manager
|
|
||||||
.QueryInterface(Ci.nsIComponentRegistrar)
|
|
||||||
.unregisterFactory(component.cid, component.factory);
|
|
||||||
Services.catMan.deleteCategoryEntry(VACUUM_CATEGORY, contractId, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection data with methods necessary for closing the connection.
|
* Connection data with methods necessary for closing the connection.
|
||||||
*
|
*
|
||||||
@ -393,33 +356,6 @@ function ConnectionData(connection, identifier, options = {}) {
|
|||||||
this._timeoutPromise = null;
|
this._timeoutPromise = null;
|
||||||
// The last timestamp when we should consider using `this._timeoutPromise`.
|
// The last timestamp when we should consider using `this._timeoutPromise`.
|
||||||
this._timeoutPromiseExpires = 0;
|
this._timeoutPromiseExpires = 0;
|
||||||
|
|
||||||
this._useIncrementalVacuum = !!options.incrementalVacuum;
|
|
||||||
if (this._useIncrementalVacuum) {
|
|
||||||
this._log.debug("Set auto_vacuum INCREMENTAL");
|
|
||||||
this.execute("PRAGMA auto_vacuum = 2").catch(ex => {
|
|
||||||
this._log.error("Setting auto_vacuum to INCREMENTAL failed.");
|
|
||||||
console.error(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this._expectedPageSize = options.pageSize ?? 0;
|
|
||||||
if (this._expectedPageSize) {
|
|
||||||
this._log.debug("Set page_size to " + this._expectedPageSize);
|
|
||||||
this.execute("PRAGMA page_size = " + this._expectedPageSize).catch(ex => {
|
|
||||||
this._log.error(`Setting page_size to ${this._expectedPageSize} failed.`);
|
|
||||||
console.error(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this._vacuumOnIdle = options.vacuumOnIdle;
|
|
||||||
if (this._vacuumOnIdle) {
|
|
||||||
this._log.debug("Register as vacuum participant");
|
|
||||||
this.QueryInterface = ChromeUtils.generateQI([
|
|
||||||
Ci.mozIStorageVacuumParticipant,
|
|
||||||
]);
|
|
||||||
registerVacuumParticipant(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -435,35 +371,6 @@ function ConnectionData(connection, identifier, options = {}) {
|
|||||||
ConnectionData.byId = new Map();
|
ConnectionData.byId = new Map();
|
||||||
|
|
||||||
ConnectionData.prototype = Object.freeze({
|
ConnectionData.prototype = Object.freeze({
|
||||||
get expectedDatabasePageSize() {
|
|
||||||
return this._expectedPageSize;
|
|
||||||
},
|
|
||||||
|
|
||||||
get useIncrementalVacuum() {
|
|
||||||
return this._useIncrementalVacuum;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This should only be used by the VacuumManager component.
|
|
||||||
* @see unsafeRawConnection for an official (but still unsafe) API.
|
|
||||||
*/
|
|
||||||
get databaseConnection() {
|
|
||||||
if (this._vacuumOnIdle) {
|
|
||||||
return this._dbConn;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
onBeginVacuum() {
|
|
||||||
let granted = !this.transactionInProgress;
|
|
||||||
this._log.debug("Begin Vacuum - " + granted ? "granted" : "denied");
|
|
||||||
return granted;
|
|
||||||
},
|
|
||||||
|
|
||||||
onEndVacuum(succeeded) {
|
|
||||||
this._log.debug("End Vacuum - " + succeeded ? "success" : "failure");
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a task, ensuring that its execution will not be interrupted by shutdown.
|
* Run a task, ensuring that its execution will not be interrupted by shutdown.
|
||||||
*
|
*
|
||||||
@ -586,11 +493,6 @@ ConnectionData.prototype = Object.freeze({
|
|||||||
this._log.debug("Request to close connection.");
|
this._log.debug("Request to close connection.");
|
||||||
this._clearIdleShrinkTimer();
|
this._clearIdleShrinkTimer();
|
||||||
|
|
||||||
if (this._vacuumOnIdle) {
|
|
||||||
this._log.debug("Unregister as vacuum participant");
|
|
||||||
unregisterVacuumParticipant(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._barrier.wait().then(() => {
|
return this._barrier.wait().then(() => {
|
||||||
if (!this._dbConn) {
|
if (!this._dbConn) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -901,9 +803,8 @@ ConnectionData.prototype = Object.freeze({
|
|||||||
|
|
||||||
shrinkMemory() {
|
shrinkMemory() {
|
||||||
this._log.debug("Shrinking memory usage.");
|
this._log.debug("Shrinking memory usage.");
|
||||||
return this.execute("PRAGMA shrink_memory").finally(() => {
|
let onShrunk = this._clearIdleShrinkTimer.bind(this);
|
||||||
this._clearIdleShrinkTimer();
|
return this.execute("PRAGMA shrink_memory").then(onShrunk, onShrunk);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
discardCachedStatements() {
|
discardCachedStatements() {
|
||||||
@ -1181,24 +1082,6 @@ ConnectionData.prototype = Object.freeze({
|
|||||||
* USE WITH EXTREME CAUTION. This mode WILL produce incorrect results or
|
* USE WITH EXTREME CAUTION. This mode WILL produce incorrect results or
|
||||||
* return "false positive" corruption errors if other connections write
|
* return "false positive" corruption errors if other connections write
|
||||||
* to the DB at the same time.
|
* to the DB at the same time.
|
||||||
*
|
|
||||||
* vacuumOnIdle -- (bool) Whether to register this connection to be vacuumed
|
|
||||||
* on idle by the VacuumManager component.
|
|
||||||
* If you're vacuum-ing an incremental vacuum database, ensure to also
|
|
||||||
* set incrementalVacuum to true, otherwise this will try to change it
|
|
||||||
* to full vacuum mode.
|
|
||||||
*
|
|
||||||
* incrementalVacuum -- (bool) if set to true auto_vacuum = INCREMENTAL will
|
|
||||||
* be enabled for the database.
|
|
||||||
* Changing auto vacuum of an already populated database requires a full
|
|
||||||
* VACUUM. You can evaluate to enable vacuumOnIdle for that.
|
|
||||||
*
|
|
||||||
* pageSize -- (integer) This allows to set a custom page size for the
|
|
||||||
* database. It is usually not necessary to set it, since the default
|
|
||||||
* value should be good for most consumers.
|
|
||||||
* Changing the page size of an already populated database requires a full
|
|
||||||
* VACUUM. You can evaluate to enable vacuumOnIdle for that.
|
|
||||||
*
|
|
||||||
* testDelayedOpenPromise -- (promise) Used by tests to delay the open
|
* testDelayedOpenPromise -- (promise) Used by tests to delay the open
|
||||||
* callback handling and execute code between asyncOpen and its callback.
|
* callback handling and execute code between asyncOpen and its callback.
|
||||||
*
|
*
|
||||||
@ -1280,33 +1163,6 @@ function openConnection(options) {
|
|||||||
openedOptions.defaultTransactionType = defaultTransactionType;
|
openedOptions.defaultTransactionType = defaultTransactionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("vacuumOnIdle" in options) {
|
|
||||||
if (typeof options.vacuumOnIdle != "boolean") {
|
|
||||||
throw new Error("Invalid vacuumOnIdle: " + options.vacuumOnIdle);
|
|
||||||
}
|
|
||||||
openedOptions.vacuumOnIdle = options.vacuumOnIdle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("incrementalVacuum" in options) {
|
|
||||||
if (typeof options.incrementalVacuum != "boolean") {
|
|
||||||
throw new Error(
|
|
||||||
"Invalid incrementalVacuum: " + options.incrementalVacuum
|
|
||||||
);
|
|
||||||
}
|
|
||||||
openedOptions.incrementalVacuum = options.incrementalVacuum;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("pageSize" in options) {
|
|
||||||
if (
|
|
||||||
![512, 1024, 2048, 4096, 8192, 16384, 32768, 65536].includes(
|
|
||||||
options.pageSize
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new Error("Invalid pageSize: " + options.pageSize);
|
|
||||||
}
|
|
||||||
openedOptions.pageSize = options.pageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
let identifier = getIdentifierByFileName(PathUtils.filename(path));
|
let identifier = getIdentifierByFileName(PathUtils.filename(path));
|
||||||
|
|
||||||
log.debug("Opening database: " + path + " (" + identifier + ")");
|
log.debug("Opening database: " + path + " (" + identifier + ")");
|
||||||
|
@ -1381,19 +1381,3 @@ add_task(async function test_interrupt() {
|
|||||||
"Sqlite.interrupt() should throw on a closed connection"
|
"Sqlite.interrupt() should throw on a closed connection"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(async function test_pageSize() {
|
|
||||||
// Testing the possibility to set the page size on database creation.
|
|
||||||
await Assert.rejects(
|
|
||||||
getDummyDatabase("pagesize", { pageSize: 1234 }),
|
|
||||||
/Invalid pageSize/,
|
|
||||||
"Check invalid pageSize value"
|
|
||||||
);
|
|
||||||
let c = await getDummyDatabase("pagesize", { pageSize: 8192 });
|
|
||||||
Assert.equal(
|
|
||||||
(await c.execute("PRAGMA page_size"))[0].getResultByIndex(0),
|
|
||||||
8192,
|
|
||||||
"Check page size was set"
|
|
||||||
);
|
|
||||||
await c.close();
|
|
||||||
});
|
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const { Sqlite } = ChromeUtils.importESModule(
|
|
||||||
"resource://gre/modules/Sqlite.sys.mjs"
|
|
||||||
);
|
|
||||||
const { TestUtils } = ChromeUtils.importESModule(
|
|
||||||
"resource://testing-common/TestUtils.sys.mjs"
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a fake idle-daily notification to the VACUUM Manager.
|
|
||||||
*/
|
|
||||||
function synthesize_idle_daily() {
|
|
||||||
Cc["@mozilla.org/storage/vacuum;1"]
|
|
||||||
.getService(Ci.nsIObserver)
|
|
||||||
.observe(null, "idle-daily", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
function unregister_vacuum_participants() {
|
|
||||||
// First unregister other participants.
|
|
||||||
for (let { data: entry } of Services.catMan.enumerateCategory(
|
|
||||||
"vacuum-participant"
|
|
||||||
)) {
|
|
||||||
Services.catMan.deleteCategoryEntry("vacuum-participant", entry, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset_vacuum_date(dbname) {
|
|
||||||
let date = parseInt(Date.now() / 1000 - 31 * 86400);
|
|
||||||
// Set last VACUUM to a date in the past.
|
|
||||||
Services.prefs.setIntPref(`storage.vacuum.last.${dbname}`, date);
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_vacuum_date(dbname) {
|
|
||||||
return Services.prefs.getIntPref(`storage.vacuum.last.${dbname}`, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function get_freelist_count(conn) {
|
|
||||||
return (await conn.execute("PRAGMA freelist_count"))[0].getResultByIndex(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function get_auto_vacuum(conn) {
|
|
||||||
return (await conn.execute("PRAGMA auto_vacuum"))[0].getResultByIndex(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function test_vacuum(options = {}) {
|
|
||||||
unregister_vacuum_participants();
|
|
||||||
const dbName = "testVacuum.sqlite";
|
|
||||||
const dbFile = PathUtils.join(PathUtils.profileDir, dbName);
|
|
||||||
let lastVacuumDate = reset_vacuum_date(dbName);
|
|
||||||
let conn = await Sqlite.openConnection(
|
|
||||||
Object.assign(
|
|
||||||
{
|
|
||||||
path: dbFile,
|
|
||||||
vacuumOnIdle: true,
|
|
||||||
},
|
|
||||||
options
|
|
||||||
)
|
|
||||||
);
|
|
||||||
// Ensure the category manager is up-to-date.
|
|
||||||
await TestUtils.waitForTick();
|
|
||||||
|
|
||||||
Assert.equal(
|
|
||||||
await get_auto_vacuum(conn),
|
|
||||||
options.incrementalVacuum ? 2 : 0,
|
|
||||||
"Check auto_vacuum"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Generate some freelist page.
|
|
||||||
await conn.execute("CREATE TABLE test (id INTEGER)");
|
|
||||||
await conn.execute("DROP TABLE test");
|
|
||||||
Assert.greater(await get_freelist_count(conn), 0, "Check freelist_count");
|
|
||||||
|
|
||||||
let promiseVacuumEnd = TestUtils.topicObserved(
|
|
||||||
"vacuum-end",
|
|
||||||
(_, d) => d == dbName
|
|
||||||
);
|
|
||||||
synthesize_idle_daily();
|
|
||||||
info("Await vacuum end");
|
|
||||||
await promiseVacuumEnd;
|
|
||||||
|
|
||||||
Assert.greater(get_vacuum_date(dbName), lastVacuumDate);
|
|
||||||
|
|
||||||
Assert.equal(await get_freelist_count(conn), 0, "Check freelist_count");
|
|
||||||
|
|
||||||
await conn.close();
|
|
||||||
await IOUtils.remove(dbFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
add_task(async function test_vacuumOnIdle() {
|
|
||||||
info("Test full vacuum");
|
|
||||||
await test_vacuum();
|
|
||||||
info("Test incremental vacuum");
|
|
||||||
await test_vacuum({ incrementalVacuum: true });
|
|
||||||
});
|
|
@ -60,8 +60,6 @@ run-sequentially = very high failure rate in parallel
|
|||||||
[test_Services.js]
|
[test_Services.js]
|
||||||
[test_sqlite.js]
|
[test_sqlite.js]
|
||||||
skip-if = toolkit == 'android'
|
skip-if = toolkit == 'android'
|
||||||
[test_sqlite_autoVacuum.js]
|
|
||||||
skip-if = toolkit == 'android'
|
|
||||||
[test_sqlite_shutdown.js]
|
[test_sqlite_shutdown.js]
|
||||||
[test_timer.js]
|
[test_timer.js]
|
||||||
[test_UpdateUtils_url.js]
|
[test_UpdateUtils_url.js]
|
||||||
|
Loading…
Reference in New Issue
Block a user