Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ActorsParent.h"
#include <inttypes.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <cstdint>
#include <functional>
#include <iterator>
#include <new>
#include <numeric>
#include <tuple>
#include <type_traits>
#include <utility>
#include "ActorsParentCommon.h"
#include "CrashAnnotations.h"
#include "DBSchema.h"
#include "ErrorList.h"
#include "FileInfoFwd.h"
#include "FileInfoT.h"
#include "FileManager.h"
#include "FileManagerBase.h"
#include "GeckoProfiler.h"
#include "IDBCursorType.h"
#include "IDBObjectStore.h"
#include "IDBTransaction.h"
#include "IndexedDBCommon.h"
#include "IndexedDatabaseInlines.h"
#include "IndexedDatabaseManager.h"
#include "KeyPath.h"
#include "MainThreadUtils.h"
#include "PermissionRequestBase.h"
#include "ProfilerHelpers.h"
#include "ReportInternalError.h"
#include "SafeRefPtr.h"
#include "SchemaUpgrades.h"
#include "chrome/common/ipc_channel.h"
#include "ipc/IPCMessageUtils.h"
#include "js/RootingAPI.h"
#include "js/StructuredClone.h"
#include "js/Value.h"
#include "jsapi.h"
#include "mozIStorageAsyncConnection.h"
#include "mozIStorageConnection.h"
#include "mozIStorageFunction.h"
#include "mozIStorageProgressHandler.h"
#include "mozIStorageService.h"
#include "mozIStorageStatement.h"
#include "mozIStorageValueArray.h"
#include "mozStorageCID.h"
#include "mozStorageHelper.h"
#include "mozilla/Algorithm.h"
#include "mozilla/ArrayAlgorithm.h"
#include "mozilla/ArrayIterator.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/Casting.h"
#include "mozilla/CondVar.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/InitializedOnce.h"
#include "mozilla/Logging.h"
#include "mozilla/MacroForEach.h"
#include "mozilla/Maybe.h"
#include "mozilla/Monitor.h"
#include "mozilla/Mutex.h"
#include "mozilla/NotNull.h"
#include "mozilla/Preferences.h"
#include "mozilla/RefCountType.h"
#include "mozilla/RefCounted.h"
#include "mozilla/RemoteLazyInputStreamParent.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/Scoped.h"
#include "mozilla/SnappyCompressOutputStream.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TaskCategory.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/Variant.h"
#include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/FileBlobImpl.h"
#include "mozilla/dom/FileHandleStorage.h"
#include "mozilla/dom/FlippedOnce.h"
#include "mozilla/dom/IDBCursorBinding.h"
#include "mozilla/dom/IPCBlob.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/IndexedDatabase.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/PBackgroundMutableFileParent.h"
#include "mozilla/dom/PContentParent.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/filehandle/ActorsParent.h"
#include "mozilla/dom/indexedDB/IDBResult.h"
#include "mozilla/dom/indexedDB/Key.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBCursor.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabase.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseRequestParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactory.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBRequest.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsParent.h"
#include "mozilla/dom/ipc/IdType.h"
#include "mozilla/dom/quota/CheckedUnsafePtr.h"
#include "mozilla/dom/quota/Client.h"
#include "mozilla/dom/quota/DecryptingInputStream_impl.h"
#include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
#include "mozilla/dom/quota/FileStreams.h"
#include "mozilla/dom/quota/OriginScope.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/QuotaObject.h"
#include "mozilla/dom/quota/UsageInfo.h"
#include "mozilla/fallible.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/InputStreamParams.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/mozalloc.h"
#include "mozilla/PRemoteLazyInputStreamParent.h"
#include "mozilla/storage/Variant.h"
#include "nsBaseHashtable.h"
#include "nsCOMPtr.h"
#include "nsClassHashtable.h"
#include "nsContentUtils.h"
#include "nsDataHashtable.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsEscape.h"
#include "nsExceptionHandler.h"
#include "nsHashKeys.h"
#include "nsIAsyncInputStream.h"
#include "nsID.h"
#include "nsIDirectoryEnumerator.h"
#include "nsIEventTarget.h"
#include "nsIFile.h"
#include "nsIFileProtocolHandler.h"
#include "nsIFileStreams.h"
#include "nsIFileURL.h"
#include "nsIInputStream.h"
#include "nsIOutputStream.h"
#include "nsIProtocolHandler.h"
#include "nsIRunnable.h"
#include "nsISupports.h"
#include "nsISupportsPriority.h"
#include "nsISupportsUtils.h"
#include "nsIThread.h"
#include "nsIThreadInternal.h"
#include "nsITimer.h"
#include "nsIURIMutator.h"
#include "nsIVariant.h"
#include "nsInterfaceHashtable.h"
#include "nsLiteralString.h"
#include "nsNetCID.h"
#include "nsPrintfCString.h"
#include "nsProxyRelease.h"
#include "nsRefPtrHashtable.h"
#include "nsServiceManagerUtils.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsStringFlags.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsTHashtable.h"
#include "nsTLiteralString.h"
#include "nsTStringRepr.h"
#include "nsThreadPool.h"
#include "nsThreadUtils.h"
#include "nscore.h"
#include "prinrval.h"
#include "prio.h"
#include "prsystem.h"
#include "prthread.h"
#include "prtime.h"
#include "prtypes.h"
#include "snappy/snappy.h"
struct JSContext;
class JSObject;
template <class T>
class nsPtrHashKey;
#define DISABLE_ASSERTS_FOR_FUZZING 0
#if DISABLE_ASSERTS_FOR_FUZZING
# define ASSERT_UNLESS_FUZZING(...) \
do { \
} while (0)
#else
# define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
#endif
#define IDB_DEBUG_LOG(_args) \
MOZ_LOG(IndexedDatabaseManager::GetLoggingModule(), LogLevel::Debug, _args)
#if defined(MOZ_WIDGET_ANDROID)
# define IDB_MOBILE
#endif
namespace mozilla {
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc,
PR_Close);
namespace dom::indexedDB {
using namespace mozilla::dom::quota;
using namespace mozilla::ipc;
using mozilla::dom::quota::Client;
namespace {
class ConnectionPool;
class Database;
struct DatabaseActorInfo;
class DatabaseFile;
class DatabaseLoggingInfo;
class DatabaseMaintenance;
class Factory;
class Maintenance;
class MutableFile;
class OpenDatabaseOp;
class TransactionBase;
class TransactionDatabaseOperationBase;
class VersionChangeTransaction;
template <bool StatementHasIndexKeyBindings>
struct ValuePopulateResponseHelper;
/*******************************************************************************
* Constants
******************************************************************************/
const int32_t kStorageProgressGranularity = 1000;
// Changing the value here will override the page size of new databases only.
// A journal mode change and VACUUM are needed to change existing databases, so
// the best way to do that is to use the schema version upgrade mechanism.
const uint32_t kSQLitePageSizeOverride =
#ifdef IDB_MOBILE
2048;
#else
4096;
#endif
static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
(kSQLitePageSizeOverride % 2 == 0 &&
kSQLitePageSizeOverride >= 512 &&
kSQLitePageSizeOverride <= 65536),
"Must be 0 (disabled) or a power of 2 between 512 and 65536!");
// Set to -1 to use SQLite's default, 0 to disable, or some positive number to
// enforce a custom limit.
const int32_t kMaxWALPages = 5000; // 20MB on desktop, 10MB on mobile.
// Set to some multiple of the page size to grow the database in larger chunks.
const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;
static_assert(kSQLiteGrowthIncrement >= 0 &&
kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
"Must be 0 (disabled) or a positive multiple of the page size!");
// The maximum number of threads that can be used for database activity at a
// single time.
const uint32_t kMaxConnectionThreadCount = 20;
static_assert(kMaxConnectionThreadCount, "Must have at least one thread!");
// The maximum number of threads to keep when idle. Threads that become idle in
// excess of this number will be shut down immediately.
const uint32_t kMaxIdleConnectionThreadCount = 2;
static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount,
"Idle thread limit must be less than total thread limit!");
// The length of time that database connections will be held open after all
// transactions have completed before doing idle maintenance.
const uint32_t kConnectionIdleMaintenanceMS = 2 * 1000; // 2 seconds
// The length of time that database connections will be held open after all
// transactions and maintenance have completed.
const uint32_t kConnectionIdleCloseMS = 10 * 1000; // 10 seconds
// The length of time that idle threads will stay alive before being shut down.
const uint32_t kConnectionThreadIdleMS = 30 * 1000; // 30 seconds
#define SAVEPOINT_CLAUSE "SAVEPOINT sp;"_ns
// For efficiency reasons, kEncryptedStreamBlockSize must be a multiple of large
// 4k disk sectors.
static_assert(kEncryptedStreamBlockSize % 4096 == 0);
// Similarly, the file copy buffer size must be a multiple of the encrypted
// block size.
static_assert(kFileCopyBufferSize % kEncryptedStreamBlockSize == 0);
constexpr auto kJournalDirectoryName = u"journals"_ns;
constexpr auto kFileManagerDirectoryNameSuffix = u".files"_ns;
constexpr auto kSQLiteSuffix = u".sqlite"_ns;
constexpr auto kSQLiteJournalSuffix = u".sqlite-journal"_ns;
constexpr auto kSQLiteSHMSuffix = u".sqlite-shm"_ns;
constexpr auto kSQLiteWALSuffix = u".sqlite-wal"_ns;
const char kPrefFileHandleEnabled[] = "dom.fileHandle.enabled";
constexpr auto kPermissionStringBase = "indexedDB-chrome-"_ns;
constexpr auto kPermissionReadSuffix = "-read"_ns;
constexpr auto kPermissionWriteSuffix = "-write"_ns;
// The following constants define all names of binding parameters in statements,
// where they are bound by name. This should include all parameter names which
// are bound by name. Binding may be done by index when the statement definition
// and binding are done in the same local scope, and no other reasons prevent
// using the indexes (e.g. multiple statement variants with differing number or
// order of parameters). Neither the styles of specifying parameter names
// (literally vs. via these constants) nor the binding styles (by index vs. by
// name) should not be mixed for the same statement. The decision must be made
// for each statement based on the proximity of statement and binding calls.
constexpr auto kStmtParamNameCurrentKey = "current_key"_ns;
constexpr auto kStmtParamNameRangeBound = "range_bound"_ns;
constexpr auto kStmtParamNameObjectStorePosition = "object_store_position"_ns;
constexpr auto kStmtParamNameLowerKey = "lower_key"_ns;
constexpr auto kStmtParamNameUpperKey = "upper_key"_ns;
constexpr auto kStmtParamNameKey = "key"_ns;
constexpr auto kStmtParamNameObjectStoreId = "object_store_id"_ns;
constexpr auto kStmtParamNameIndexId = "index_id"_ns;
// TODO: Maybe the uses of kStmtParamNameId should be replaced by more
// specific constants such as kStmtParamNameObjectStoreId.
constexpr auto kStmtParamNameId = "id"_ns;
constexpr auto kStmtParamNameValue = "value"_ns;
constexpr auto kStmtParamNameObjectDataKey = "object_data_key"_ns;
constexpr auto kStmtParamNameIndexDataValues = "index_data_values"_ns;
constexpr auto kStmtParamNameData = "data"_ns;
constexpr auto kStmtParamNameFileIds = "file_ids"_ns;
constexpr auto kStmtParamNameValueLocale = "value_locale"_ns;
constexpr auto kStmtParamNameLimit = "limit"_ns;
// The following constants define some names of columns in tables, which are
// referred to in remote locations, e.g. in calls to
// GetBindingClauseForKeyRange.
constexpr auto kColumnNameKey = "key"_ns;
constexpr auto kColumnNameValue = "value"_ns;
constexpr auto kColumnNameAliasSortKey = "sort_column"_ns;
// SQL fragments used at multiple locations.
constexpr auto kOpenLimit = " LIMIT "_ns;
// The deletion marker file is created before RemoveDatabaseFilesAndDirectory
// begins deleting a database. It is removed as the last step of deletion. If a
// deletion marker file is found when initializing the origin, the deletion
// routine is run again to ensure that the database and all of its related files
// are removed. The primary goal of this mechanism is to avoid situations where
// a database has been partially deleted, leading to inconsistent state for the
// origin.
constexpr auto kIdbDeletionMarkerFilePrefix = u"idb-deleting-"_ns;
const uint32_t kDeleteTimeoutMs = 1000;
#ifdef DEBUG
const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
const uint32_t kDEBUGThreadSleepMS = 0;
const int32_t kDEBUGTransactionThreadPriority =
nsISupportsPriority::PRIORITY_NORMAL;
const uint32_t kDEBUGTransactionThreadSleepMS = 0;
#endif
/*******************************************************************************
* Metadata classes
******************************************************************************/
// Can be instantiated either on the QuotaManager IO thread or on a
// versionchange transaction thread. These threads can never race so this is
// totally safe.
struct FullIndexMetadata {
IndexMetadata mCommonMetadata = {0, nsString(), KeyPath(0), nsCString(),
false, false, false};
FlippedOnce<false> mDeleted;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullIndexMetadata)
private:
~FullIndexMetadata() = default;
};
typedef nsRefPtrHashtable<nsUint64HashKey, FullIndexMetadata> IndexTable;
// Can be instantiated either on the QuotaManager IO thread or on a
// versionchange transaction thread. These threads can never race so this is
// totally safe.
struct FullObjectStoreMetadata {
ObjectStoreMetadata mCommonMetadata = {0, nsString(), KeyPath(0), false};
IndexTable mIndexes;
// These two members are only ever touched on a transaction thread!
int64_t mNextAutoIncrementId = 0;
int64_t mCommittedAutoIncrementId = 0;
FlippedOnce<false> mDeleted;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);
bool HasLiveIndexes() const;
private:
~FullObjectStoreMetadata() = default;
};
typedef nsRefPtrHashtable<nsUint64HashKey, FullObjectStoreMetadata>
ObjectStoreTable;
static_assert(
std::is_same_v<
IndexOrObjectStoreId,
std::remove_cv_t<std::remove_reference_t<decltype(
std::declval<const ObjectStoreGetParams&>().objectStoreId())>>>);
static_assert(std::is_same_v<
IndexOrObjectStoreId,
std::remove_cv_t<std::remove_reference_t<decltype(
std::declval<const IndexGetParams&>().objectStoreId())>>>);
struct FullDatabaseMetadata final : AtomicSafeRefCounted<FullDatabaseMetadata> {
DatabaseMetadata mCommonMetadata;
nsCString mDatabaseId;
nsString mFilePath;
ObjectStoreTable mObjectStores;
IndexOrObjectStoreId mNextObjectStoreId = 0;
IndexOrObjectStoreId mNextIndexId = 0;
public:
explicit FullDatabaseMetadata(const DatabaseMetadata& aCommonMetadata)
: mCommonMetadata(aCommonMetadata) {
AssertIsOnBackgroundThread();
}
[[nodiscard]] SafeRefPtr<FullDatabaseMetadata> Duplicate() const;
MOZ_DECLARE_REFCOUNTED_TYPENAME(FullDatabaseMetadata)
};
template <class Enumerable>
auto MatchMetadataNameOrId(const Enumerable& aEnumerable,
IndexOrObjectStoreId aId,
Maybe<const nsAString&> aName = Nothing()) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aId);
const auto it = std::find_if(
aEnumerable.cbegin(), aEnumerable.cend(),
[aId, aName](const auto& entry) {
MOZ_ASSERT(entry.GetKey() != 0);
const auto& value = entry.GetData();
MOZ_ASSERT(value);
return !value->mDeleted &&
(aId == value->mCommonMetadata.id() ||
(aName && *aName == value->mCommonMetadata.name()));
});
return it != aEnumerable.cend() ? SomeRef(*it->GetData()) : Nothing();
}
/*******************************************************************************
* SQLite functions
******************************************************************************/
// WARNING: the hash function used for the database name must not change.
// That's why this function exists separately from mozilla::HashString(), even
// though it is (at the time of writing) equivalent. See bug 780408 and bug
// 940315 for details.
uint32_t HashName(const nsAString& aName) {
struct Helper {
static uint32_t RotateBitsLeft32(uint32_t aValue, uint8_t aBits) {
MOZ_ASSERT(aBits < 32);
return (aValue << aBits) | (aValue >> (32 - aBits));
}
};
static const uint32_t kGoldenRatioU32 = 0x9e3779b9u;
return std::accumulate(aName.BeginReading(), aName.EndReading(), uint32_t(0),
[](uint32_t hash, char16_t ch) {
return kGoldenRatioU32 *
(Helper::RotateBitsLeft32(hash, 5) ^ ch);
});
}
Result<Ok, nsresult> AssertExists(const nsCOMPtr<nsIFile>& aDirectory) {
#ifdef DEBUG
IDB_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(aDirectory, Exists));
MOZ_ASSERT(exists);
#endif
return Ok{};
};
nsresult ClampResultCode(nsresult aResultCode) {
if (NS_SUCCEEDED(aResultCode) ||
NS_ERROR_GET_MODULE(aResultCode) == NS_ERROR_MODULE_DOM_INDEXEDDB) {
return aResultCode;
}
switch (aResultCode) {
case NS_ERROR_FILE_NO_DEVICE_SPACE:
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
case NS_ERROR_STORAGE_CONSTRAINT:
return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
default:
#ifdef DEBUG
nsPrintfCString message("Converting non-IndexedDB error code (0x%" PRIX32
") to "
"NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR",
static_cast<uint32_t>(aResultCode));
NS_WARNING(message.get());
#else
;
#endif
}
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
nsAutoString GetDatabaseFilenameBase(const nsAString& aDatabaseName) {
nsAutoString databaseFilenameBase;
// WARNING: do not change this hash function. See the comment in HashName()
// for details.
databaseFilenameBase.AppendInt(HashName(aDatabaseName));
nsAutoCString escapedName;
if (!NS_Escape(NS_ConvertUTF16toUTF8(aDatabaseName), escapedName,
url_XPAlphas)) {
MOZ_CRASH("Can't escape database name!");
}
const char* forwardIter = escapedName.BeginReading();
const char* backwardIter = escapedName.EndReading() - 1;
nsAutoCString substring;
while (forwardIter <= backwardIter && substring.Length() < 21) {
if (substring.Length() % 2) {
substring.Append(*backwardIter--);
} else {
substring.Append(*forwardIter++);
}
}
databaseFilenameBase.AppendASCII(substring.get(), substring.Length());
return databaseFilenameBase;
}
Result<nsCOMPtr<nsIFileURL>, nsresult> GetDatabaseFileURL(
nsIFile& aDatabaseFile, const int64_t aDirectoryLockId,
const Maybe<CipherKey>& aMaybeKey) {
MOZ_ASSERT(aDirectoryLockId >= -1);
IDB_TRY_INSPECT(const auto& protocolHandler,
ToResultGet<nsCOMPtr<nsIProtocolHandler>>(
MOZ_SELECT_OVERLOAD(do_GetService),
NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file"));
IDB_TRY_INSPECT(const auto& fileHandler,
ToResultGet<nsCOMPtr<nsIFileProtocolHandler>>(
MOZ_SELECT_OVERLOAD(do_QueryInterface), protocolHandler));
IDB_TRY_INSPECT(const auto& mutator, MOZ_TO_RESULT_INVOKE_TYPED(
nsCOMPtr<nsIURIMutator>, fileHandler,
NewFileURIMutator, &aDatabaseFile));
// aDirectoryLockId should only be -1 when we are called
// - from FileManager::InitDirectory when the temporary storage hasn't been
// initialized yet. At that time, the in-memory objects (e.g. OriginInfo)
// are only being created so it doesn't make sense to tunnel quota
// information to TelemetryVFS to get corresponding QuotaObject instances
// for SQLite files.
// - from DeleteDatabaseOp::LoadPreviousVersion, since this might require
// temporarily exceeding the quota limit before the database can be deleted.
const auto directoryLockIdClause =
aDirectoryLockId >= 0
? "&directoryLockId="_ns + IntToCString(aDirectoryLockId)
: EmptyCString();
const auto keyClause = [&aMaybeKey] {
nsAutoCString keyClause;
if (aMaybeKey) {
keyClause.AssignLiteral("&key=");
for (uint8_t byte : IndexedDBCipherStrategy::SerializeKey(*aMaybeKey)) {
keyClause.AppendPrintf("%02x", byte);
}
}
return keyClause;
}();
IDB_TRY_UNWRAP(
auto result, ([&mutator, &directoryLockIdClause, &keyClause] {
nsCOMPtr<nsIFileURL> result;
nsresult rv = NS_MutateURI(mutator)
.SetQuery("cache=private"_ns + directoryLockIdClause +
keyClause)
.Finalize(result);
return NS_SUCCEEDED(rv) ? Result<nsCOMPtr<nsIFileURL>, nsresult>{result}
: Err(rv);
}()));
return result;
}
nsresult SetDefaultPragmas(mozIStorageConnection& aConnection) {
MOZ_ASSERT(!NS_IsMainThread());
static constexpr auto kBuiltInPragmas =
// We use foreign keys in DEBUG builds only because there is a performance
// cost to using them.
"PRAGMA foreign_keys = "
#ifdef DEBUG
"ON"
#else
"OFF"
#endif
";"
// The "INSERT OR REPLACE" statement doesn't fire the update trigger,
// instead it fires only the insert trigger. This confuses the update
// refcount function. This behavior changes with enabled recursive
// triggers, so the statement fires the delete trigger first and then the
// insert trigger.
"PRAGMA recursive_triggers = ON;"
// We aggressively truncate the database file when idle so don't bother
// overwriting the WAL with 0 during active periods.
"PRAGMA secure_delete = OFF;"_ns;
IDB_TRY(aConnection.ExecuteSimpleSQL(kBuiltInPragmas));
IDB_TRY(aConnection.ExecuteSimpleSQL(nsAutoCString{
"PRAGMA synchronous = "_ns +
(IndexedDatabaseManager::FullSynchronous() ? "FULL"_ns : "NORMAL"_ns) +
";"_ns}));
#ifndef IDB_MOBILE
if (kSQLiteGrowthIncrement) {
// This is just an optimization so ignore the failure if the disk is
// currently too full.
IDB_TRY(
ToResult(aConnection.SetGrowthIncrement(kSQLiteGrowthIncrement, ""_ns))
.mapErr([](const nsresult rv) {
return rv == NS_ERROR_FILE_TOO_BIG ? NS_OK : rv;
}));
}
#endif // IDB_MOBILE
return NS_OK;
}
Result<nsCOMPtr<mozIStorageStatement>, nsresult>
CreateAndExecuteSingleStepStatement(mozIStorageConnection& aConnection,
const nsACString& aStatementString) {
IDB_TRY_UNWRAP(auto stmt, MOZ_TO_RESULT_INVOKE_TYPED(
nsCOMPtr<mozIStorageStatement>, aConnection,
CreateStatement, aStatementString));
IDB_TRY_UNWRAP(const DebugOnly<bool> hasResult,
MOZ_TO_RESULT_INVOKE(stmt, ExecuteStep));
MOZ_ASSERT(hasResult);
return stmt;
}
template <typename StepFunc>
Result<Ok, nsresult> CollectWhileHasResult(mozIStorageStatement& aStmt,
StepFunc&& aStepFunc) {
return CollectWhile(
[&aStmt] { IDB_TRY_RETURN(MOZ_TO_RESULT_INVOKE(aStmt, ExecuteStep)); },
[&aStmt, &aStepFunc] { return aStepFunc(aStmt); });
}
nsresult SetJournalMode(mozIStorageConnection& aConnection) {
MOZ_ASSERT(!NS_IsMainThread());
// Try enabling WAL mode. This can fail in various circumstances so we have to
// check the results here.
constexpr auto journalModeQueryStart = "PRAGMA journal_mode = "_ns;
constexpr auto journalModeWAL = "wal"_ns;
IDB_TRY_INSPECT(const auto& stmt,
CreateAndExecuteSingleStepStatement(
aConnection, journalModeQueryStart + journalModeWAL));
IDB_TRY_INSPECT(
const auto& journalMode,
MOZ_TO_RESULT_INVOKE_TYPED(nsCString, stmt, GetUTF8String, 0));
if (journalMode.Equals(journalModeWAL)) {
// WAL mode successfully enabled. Maybe set limits on its size here.
if (kMaxWALPages >= 0) {
IDB_TRY(aConnection.ExecuteSimpleSQL("PRAGMA wal_autocheckpoint = "_ns +
IntToCString(kMaxWALPages)));
}
} else {
NS_WARNING("Failed to set WAL mode, falling back to normal journal mode.");
#ifdef IDB_MOBILE
IDB_TRY(
aConnection.ExecuteSimpleSQL(journalModeQueryStart + "truncate"_ns));
#endif
}
return NS_OK;
}
Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult> OpenDatabase(
mozIStorageService& aStorageService, nsIFileURL& aFileURL,
const uint32_t aTelemetryId = 0) {
const nsAutoCString telemetryFilename =
aTelemetryId ? "indexedDB-"_ns + IntToCString(aTelemetryId) +
NS_ConvertUTF16toUTF8(kSQLiteSuffix)
: nsAutoCString();
IDB_TRY_UNWRAP(auto connection,
MOZ_TO_RESULT_INVOKE_TYPED(
nsCOMPtr<mozIStorageConnection>, aStorageService,
OpenDatabaseWithFileURL, &aFileURL, telemetryFilename));
return WrapMovingNotNull(std::move(connection));
}
Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
OpenDatabaseAndHandleBusy(mozIStorageService& aStorageService,
nsIFileURL& aFileURL,
const uint32_t aTelemetryId = 0) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
using ConnectionType = Maybe<MovingNotNull<nsCOMPtr<mozIStorageConnection>>>;
IDB_TRY_UNWRAP(
auto connection,
OpenDatabase(aStorageService, aFileURL, aTelemetryId)
.map([](auto connection) -> ConnectionType {
return Some(std::move(connection));
})
.orElse(ErrToDefaultOkOrErr<NS_ERROR_STORAGE_BUSY, ConnectionType>));
if (connection.isNothing()) {
#ifdef DEBUG
{
nsCString path;
MOZ_ALWAYS_SUCCEEDS(aFileURL.GetFileName(path));
nsPrintfCString message(
"Received NS_ERROR_STORAGE_BUSY when attempting to open database "
"'%s', retrying for up to 10 seconds",
path.get());
NS_WARNING(message.get());
}
#endif
// Another thread must be checkpointing the WAL. Wait up to 10 seconds for
// that to complete.
const TimeStamp start = TimeStamp::NowLoRes();
do {
PR_Sleep(PR_MillisecondsToInterval(100));
IDB_TRY_UNWRAP(
connection,
OpenDatabase(aStorageService, aFileURL, aTelemetryId)
.map([](auto connection) -> ConnectionType {
return Some(std::move(connection));
})
.orElse([&start](
nsresult aValue) -> Result<ConnectionType, nsresult> {
if (aValue != NS_ERROR_STORAGE_BUSY ||
TimeStamp::NowLoRes() - start >
TimeDuration::FromSeconds(10)) {
return Err(aValue);
}
return ConnectionType();
}));
} while (connection.isNothing());
}
return connection.extract();
}
// Returns true if a given nsIFile exists and is a directory. Returns false if
// it doesn't exist. Returns an error if it exists, but is not a directory, or
// any other error occurs.
Result<bool, nsresult> ExistsAsDirectory(nsIFile& aDirectory) {
IDB_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(aDirectory, Exists));
if (exists) {
IDB_TRY_INSPECT(const bool& isDirectory,
MOZ_TO_RESULT_INVOKE(aDirectory, IsDirectory));
IDB_TRY(OkIf(isDirectory), Err(NS_ERROR_FAILURE));
}
return exists;
}
constexpr nsresult mapNoDeviceSpaceError(nsresult aRv) {
if (aRv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
// mozstorage translates SQLITE_FULL to
// NS_ERROR_FILE_NO_DEVICE_SPACE, which we know better as
// NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
}
return aRv;
}
Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
CreateStorageConnection(nsIFile& aDBFile, nsIFile& aFMDirectory,
const nsAString& aName, const nsACString& aOrigin,
const int64_t aDirectoryLockId,
const uint32_t aTelemetryId,
const Maybe<CipherKey>& aMaybeKey) {
AssertIsOnIOThread();
MOZ_ASSERT(aDirectoryLockId >= -1);
AUTO_PROFILER_LABEL("CreateStorageConnection", DOM);
IDB_TRY_INSPECT(const auto& dbFileUrl,
GetDatabaseFileURL(aDBFile, aDirectoryLockId, aMaybeKey));
IDB_TRY_INSPECT(
const auto& storageService,
ToResultGet<nsCOMPtr<mozIStorageService>>(
MOZ_SELECT_OVERLOAD(do_GetService), MOZ_STORAGE_SERVICE_CONTRACTID));
IDB_TRY_UNWRAP(
auto connection,
OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId)
.map([](auto connection) -> nsCOMPtr<mozIStorageConnection> {
return std::move(connection).unwrapBasePtr();
})
.orElse([&aName](nsresult aValue)
-> Result<nsCOMPtr<mozIStorageConnection>, nsresult> {
// If we're just opening the database during origin initialization,
// then we don't want to erase any files. The failure here will fail
// origin initialization too.
if (aValue != NS_ERROR_FILE_CORRUPTED || aName.IsVoid()) {
return Err(aValue);
}
return nsCOMPtr<mozIStorageConnection>();
}));
if (!connection) {
// XXX Shouldn't we also update quota usage?
// Nuke the database file.
IDB_TRY(aDBFile.Remove(false));
IDB_TRY_INSPECT(const bool& existsAsDirectory,
ExistsAsDirectory(aFMDirectory));
if (existsAsDirectory) {
IDB_TRY(aFMDirectory.Remove(true));
}
IDB_TRY_UNWRAP(connection, OpenDatabaseAndHandleBusy(
*storageService, *dbFileUrl, aTelemetryId));
}
IDB_TRY(SetDefaultPragmas(*connection));
IDB_TRY(connection->EnableModule("filesystem"_ns));
// Check to make sure that the database schema is correct.
IDB_TRY_INSPECT(const int32_t& schemaVersion,
MOZ_TO_RESULT_INVOKE(connection, GetSchemaVersion));
// Unknown schema will fail origin initialization too.
IDB_TRY(
OkIf(schemaVersion || !aName.IsVoid()),
Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) {
IDB_WARNING("Unable to open IndexedDB database, schema is not set!");
});
IDB_TRY(
OkIf(schemaVersion <= kSQLiteSchemaVersion),
Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) {
IDB_WARNING("Unable to open IndexedDB database, schema is too high!");
});
bool journalModeSet = false;
if (schemaVersion != kSQLiteSchemaVersion) {
const bool newDatabase = !schemaVersion;
if (newDatabase) {
// Set the page size first.
const auto sqlitePageSizeOverride =
aMaybeKey ? 8192 : kSQLitePageSizeOverride;
if (sqlitePageSizeOverride) {
IDB_TRY(connection->ExecuteSimpleSQL(nsPrintfCString(
"PRAGMA page_size = %" PRIu32 ";", sqlitePageSizeOverride)));
}
// We have to set the auto_vacuum mode before opening a transaction.
IDB_TRY((MOZ_TO_RESULT_INVOKE(
connection, ExecuteSimpleSQL,
#ifdef IDB_MOBILE
// Turn on full auto_vacuum mode to reclaim disk space on
// mobile devices (at the cost of some COMMIT speed).
"PRAGMA auto_vacuum = FULL;"_ns
#else
// Turn on incremental auto_vacuum mode on desktop builds.
"PRAGMA auto_vacuum = INCREMENTAL;"_ns
#endif
)
.mapErr(mapNoDeviceSpaceError)));
IDB_TRY(SetJournalMode(*connection));
journalModeSet = true;
} else {
#ifdef DEBUG
// Disable foreign key support while upgrading. This has to be done before
// starting a transaction.
MOZ_ALWAYS_SUCCEEDS(
connection->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns));
#endif
}
bool vacuumNeeded = false;
mozStorageTransaction transaction(
connection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
if (newDatabase) {
IDB_TRY(CreateTables(*connection));
#ifdef DEBUG
{
IDB_TRY_INSPECT(const int32_t& schemaVersion,
MOZ_TO_RESULT_INVOKE(connection, GetSchemaVersion),
QM_ASSERT_UNREACHABLE);
MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
}
#endif
// The parameter names are not used, parameters are bound by index only
// locally in the same function.
IDB_TRY_INSPECT(
const auto& stmt,
MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<mozIStorageStatement>, connection,
CreateStatement,
"INSERT INTO database (name, origin) "
"VALUES (:name, :origin)"_ns));
IDB_TRY(stmt->BindStringByIndex(0, aName));
IDB_TRY(stmt->BindUTF8StringByIndex(1, aOrigin));
IDB_TRY(stmt->Execute());
} else {
IDB_TRY_UNWRAP(vacuumNeeded,
MaybeUpgradeSchema(*connection, schemaVersion,
aFMDirectory, aOrigin));
}
IDB_TRY(MOZ_TO_RESULT_INVOKE(transaction, Commit)
.mapErr(mapNoDeviceSpaceError));
#ifdef DEBUG
if (!newDatabase) {
// Re-enable foreign key support after doing a foreign key check.
nsCOMPtr<mozIStorageStatement> checkStmt;
MOZ_ALWAYS_SUCCEEDS(connection->CreateStatement(
"PRAGMA foreign_key_check;"_ns, getter_AddRefs(checkStmt)));
bool hasResult;
MOZ_ALWAYS_SUCCEEDS(checkStmt->ExecuteStep(&hasResult));
MOZ_ASSERT(!hasResult, "Database has inconsisistent foreign keys!");
MOZ_ALWAYS_SUCCEEDS(
connection->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns));
}
#endif
if (kSQLitePageSizeOverride && !newDatabase) {
IDB_TRY_INSPECT(const auto& stmt,
CreateAndExecuteSingleStepStatement(
*connection, "PRAGMA page_size;"_ns));
IDB_TRY_INSPECT(const int32_t& pageSize,
MOZ_TO_RESULT_INVOKE(stmt, GetInt32, 0));
MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536);
if (kSQLitePageSizeOverride != uint32_t(pageSize)) {
// We must not be in WAL journal mode to change the page size.
IDB_TRY(
connection->ExecuteSimpleSQL("PRAGMA journal_mode = DELETE;"_ns));
IDB_TRY_INSPECT(const auto& stmt,
CreateAndExecuteSingleStepStatement(
*connection, "PRAGMA journal_mode;"_ns));
IDB_TRY_INSPECT(
const auto& journalMode,
MOZ_TO_RESULT_INVOKE_TYPED(nsCString, stmt, GetUTF8String, 0));
if (journalMode.EqualsLiteral("delete")) {
// Successfully set to rollback journal mode so changing the page size
// is possible with a VACUUM.
IDB_TRY(connection->ExecuteSimpleSQL(nsPrintfCString(
"PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride)));
// We will need to VACUUM in order to change the page size.
vacuumNeeded = true;
} else {
NS_WARNING(
"Failed to set journal_mode for database, unable to "
"change the page size!");
}
}
}
if (vacuumNeeded) {
IDB_TRY(connection->ExecuteSimpleSQL("VACUUM;"_ns));
}
if (newDatabase || vacuumNeeded) {
if (journalModeSet) {
// Make sure we checkpoint to get an accurate file size.
IDB_TRY(
connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(FULL);"_ns));
}
IDB_TRY_INSPECT(const int64_t& fileSize,
MOZ_TO_RESULT_INVOKE(aDBFile, GetFileSize));
MOZ_ASSERT(fileSize > 0);
PRTime vacuumTime = PR_Now();
MOZ_ASSERT(vacuumTime);
// The parameter names are not used, parameters are bound by index only
// locally in the same function.
IDB_TRY_INSPECT(
const auto& vacuumTimeStmt,
MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<mozIStorageStatement>, connection,
CreateStatement,
"UPDATE database "
"SET last_vacuum_time = :time"
", last_vacuum_size = :size;"_ns));
IDB_TRY(vacuumTimeStmt->BindInt64ByIndex(0, vacuumTime));
IDB_TRY(vacuumTimeStmt->BindInt64ByIndex(1, fileSize));
IDB_TRY(vacuumTimeStmt->Execute());
}
}
if (!journalModeSet) {
IDB_TRY(SetJournalMode(*connection));
}
return WrapMovingNotNullUnchecked(std::move(connection));
}
nsCOMPtr<nsIFile> GetFileForPath(const nsAString& aPath) {
MOZ_ASSERT(!aPath.IsEmpty());
IDB_TRY_RETURN(QM_NewLocalFile(aPath), nullptr);
}
Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
GetStorageConnection(nsIFile& aDatabaseFile, const int64_t aDirectoryLockId,
const uint32_t aTelemetryId,
const Maybe<CipherKey>& aMaybeKey) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(aDirectoryLockId >= 0);
AUTO_PROFILER_LABEL("GetStorageConnection", DOM);
IDB_TRY_INSPECT(const bool& exists,
MOZ_TO_RESULT_INVOKE(aDatabaseFile, Exists));
IDB_TRY(OkIf(exists), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
IDB_REPORT_INTERNAL_ERR_LAMBDA);
IDB_TRY_INSPECT(
const auto& dbFileUrl,
GetDatabaseFileURL(aDatabaseFile, aDirectoryLockId, aMaybeKey));
IDB_TRY_INSPECT(
const auto& storageService,
ToResultGet<nsCOMPtr<mozIStorageService>>(
MOZ_SELECT_OVERLOAD(do_GetService), MOZ_STORAGE_SERVICE_CONTRACTID));
IDB_TRY_UNWRAP(
nsCOMPtr<mozIStorageConnection> connection,
OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId));
IDB_TRY(SetDefaultPragmas(*connection));
IDB_TRY(SetJournalMode(*connection));
return WrapMovingNotNullUnchecked(std::move(connection));
}
Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
GetStorageConnection(const nsAString& aDatabaseFilePath,
const int64_t aDirectoryLockId,
const uint32_t aTelemetryId,
const Maybe<CipherKey>& aMaybeKey) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, kSQLiteSuffix));
MOZ_ASSERT(aDirectoryLockId >= 0);
nsCOMPtr<nsIFile> dbFile = GetFileForPath(aDatabaseFilePath);
IDB_TRY(OkIf(dbFile), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
IDB_REPORT_INTERNAL_ERR_LAMBDA);
return GetStorageConnection(*dbFile, aDirectoryLockId, aTelemetryId,
aMaybeKey);
}
/*******************************************************************************
* ConnectionPool declarations
******************************************************************************/
class DatabaseConnection final {
friend class ConnectionPool;
enum class CheckpointMode { Full, Restart, Truncate };
public:
class AutoSavepoint;
class CachedStatement;
class UpdateRefcountFunction;
class MOZ_STACK_CLASS BorrowedStatement : mozStorageStatementScoper {
public:
mozIStorageStatement& operator*() const;
MOZ_NONNULL_RETURN mozIStorageStatement* operator->() const
MOZ_NO_ADDREF_RELEASE_ON_RETURN;
BorrowedStatement(BorrowedStatement&& aOther) = default;
// No funny business allowed.
BorrowedStatement& operator=(BorrowedStatement&&) = delete;
BorrowedStatement(const BorrowedStatement&) = delete;
BorrowedStatement& operator=(const BorrowedStatement&) = delete;
private:
friend class CachedStatement;
#if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
BorrowedStatement(NotNull<mozIStorageStatement*> aStatement,
const nsACString& aQuery)
: mozStorageStatementScoper(aStatement),
mExtraInfo{ScopedLogExtraInfo::kTagQuery, aQuery} {}
ScopedLogExtraInfo mExtraInfo;
#else
MOZ_IMPLICIT BorrowedStatement(NotNull<mozIStorageStatement*> aStatement)
: mozStorageStatementScoper(aStatement) {}
#endif
};
private:
InitializedOnce<const NotNull<nsCOMPtr<mozIStorageConnection>>>
mStorageConnection;
InitializedOnce<const NotNull<SafeRefPtr<FileManager>>> mFileManager;
nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
mCachedStatements;
RefPtr<UpdateRefcountFunction> mUpdateRefcountFunction;
RefPtr<QuotaObject> mQuotaObject;
RefPtr<QuotaObject> mJournalQuotaObject;
bool mInReadTransaction;
bool mInWriteTransaction;
#ifdef DEBUG
uint32_t mDEBUGSavepointCount;
#endif
NS_DECL_OWNINGTHREAD
public:
void AssertIsOnConnectionThread() const {
NS_ASSERT_OWNINGTHREAD(DatabaseConnection);
}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DatabaseConnection)
bool HasStorageConnection() const {
return static_cast<bool>(mStorageConnection);
}
mozIStorageConnection& MutableStorageConnection() const {
AssertIsOnConnectionThread();
MOZ_ASSERT(mStorageConnection);
return **mStorageConnection;
}
UpdateRefcountFunction* GetUpdateRefcountFunction() const {
AssertIsOnConnectionThread();
return mUpdateRefcountFunction;
}
Result<CachedStatement, nsresult> GetCachedStatement(
const nsACString& aQuery);
Result<BorrowedStatement, nsresult> BorrowCachedStatement(
const nsACString& aQuery);
template <typename BindFunctor>
nsresult ExecuteCachedStatement(const nsACString& aQuery,
const BindFunctor& aBindFunctor);
nsresult ExecuteCachedStatement(const nsACString& aQuery);
nsresult BeginWriteTransaction();
nsresult CommitWriteTransaction();
void RollbackWriteTransaction();
void FinishWriteTransaction();
nsresult StartSavepoint();
nsresult ReleaseSavepoint();
nsresult RollbackSavepoint();
nsresult Checkpoint() {
AssertIsOnConnectionThread();
return CheckpointInternal(CheckpointMode::Full);
}
void DoIdleProcessing(bool aNeedsCheckpoint);
void Close();
nsresult DisableQuotaChecks();
void EnableQuotaChecks();
private:
DatabaseConnection(
MovingNotNull<nsCOMPtr<mozIStorageConnection>> aStorageConnection,
MovingNotNull<SafeRefPtr<FileManager>> aFileManager);
~DatabaseConnection();
nsresult Init();
nsresult CheckpointInternal(CheckpointMode aMode);
Result<uint32_t, nsresult> GetFreelistCount(
CachedStatement& aCachedStatement);
/**
* On success, returns whether some pages were freed.
*/
Result<bool, nsresult> ReclaimFreePagesWhileIdle(
CachedStatement& aFreelistStatement, CachedStatement& aRollbackStatement,
uint32_t aFreelistCount, bool aNeedsCheckpoint);
Result<int64_t, nsresult> GetFileSize(const nsAString& aPath);
};
class MOZ_STACK_CLASS DatabaseConnection::AutoSavepoint final {
DatabaseConnection* mConnection;
#ifdef DEBUG
const TransactionBase* mDEBUGTransaction;
#endif
public:
AutoSavepoint();
~AutoSavepoint();
nsresult Start(const TransactionBase& aTransaction);
nsresult Commit();
};
class DatabaseConnection::CachedStatement final {
friend class DatabaseConnection;
nsCOMPtr<mozIStorageStatement> mStatement;
#if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
nsCString mQuery;
#endif
#ifdef DEBUG
DatabaseConnection* mDEBUGConnection;
#endif
public:
#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
CachedStatement();
~CachedStatement();
#else
CachedStatement() = default;
#endif
void AssertIsOnConnectionThread() const {
#ifdef DEBUG
if (mDEBUGConnection) {
mDEBUGConnection->AssertIsOnConnectionThread();
}
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
#endif
}
explicit operator bool() const;
BorrowedStatement Borrow() const;
private:
// Only called by DatabaseConnection.
CachedStatement(DatabaseConnection* aConnection,
nsCOMPtr<mozIStorageStatement> aStatement,
const nsACString& aQuery);
public:
#if defined(NS_BUILD_REFCNT_LOGGING)
CachedStatement(CachedStatement&& aOther)
: mStatement(std::move(aOther.mStatement))
# if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
,
mQuery(std::move(aOther.mQuery))
# endif
# ifdef DEBUG
,
mDEBUGConnection(aOther.mDEBUGConnection)
# endif
{
MOZ_COUNT_CTOR(DatabaseConnection::CachedStatement);
}
#else
CachedStatement(CachedStatement&&) = default;
#endif
CachedStatement& operator=(CachedStatement&&) = default;
// No funny business allowed.
CachedStatement(const CachedStatement&) = delete;
CachedStatement& operator=(const CachedStatement&) = delete;
};
class DatabaseConnection::UpdateRefcountFunction final
: public mozIStorageFunction {
class FileInfoEntry;
enum class UpdateType { Increment, Decrement };
DatabaseConnection* const mConnection;
FileManager& mFileManager;
nsClassHashtable<nsUint64HashKey, FileInfoEntry> mFileInfoEntries;
nsDataHashtable<nsUint64HashKey, FileInfoEntry*> mSavepointEntriesIndex;
nsTArray<int64_t> mJournalsToCreateBeforeCommit;
nsTArray<int64_t> mJournalsToRemoveAfterCommit;
nsTArray<int64_t> mJournalsToRemoveAfterAbort;
bool mInSavepoint;
public:
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
UpdateRefcountFunction(DatabaseConnection* aConnection,
FileManager& aFileManager);
nsresult WillCommit();
void DidCommit();
void DidAbort();
void StartSavepoint();
void ReleaseSavepoint();
void RollbackSavepoint();
void Reset();
private:
~UpdateRefcountFunction() = default;
nsresult ProcessValue(mozIStorageValueArray* aValues, int32_t aIndex,
UpdateType aUpdateType);
nsresult CreateJournals();
nsresult RemoveJournals(const nsTArray<int64_t>& aJournals);
};
class DatabaseConnection::UpdateRefcountFunction::FileInfoEntry final {
SafeRefPtr<FileInfo> mFileInfo;
int32_t mDelta;
int32_t mSavepointDelta;
public:
explicit FileInfoEntry(SafeRefPtr<FileInfo> aFileInfo)
: mFileInfo(std::move(aFileInfo)), mDelta(0), mSavepointDelta(0) {
MOZ_COUNT_CTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
}
void IncDeltas(bool aUpdateSavepointDelta) {
++mDelta;
if (aUpdateSavepointDelta) {
++mSavepointDelta;
}
}
void DecDeltas(bool aUpdateSavepointDelta) {
--mDelta;
if (aUpdateSavepointDelta) {
--mSavepointDelta;
}
}
void DecBySavepointDelta() { mDelta -= mSavepointDelta; }
SafeRefPtr<FileInfo> ReleaseFileInfo() { return std::move(mFileInfo); }
void MaybeUpdateDBRefs() {
if (mDelta) {
mFileInfo->UpdateDBRefs(mDelta);
}
}
int32_t Delta() const { return mDelta; }
int32_t SavepointDelta() const { return mSavepointDelta; }
~FileInfoEntry() {
MOZ_COUNT_DTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
}
};
class ConnectionPool final {
public:
class FinishCallback;
private:
class ConnectionRunnable;
class CloseConnectionRunnable;
struct DatabaseInfo;
struct DatabasesCompleteCallback;
class FinishCallbackWrapper;
class IdleConnectionRunnable;
class ThreadRunnable;
class TransactionInfo;
struct TransactionInfoPair;
struct IdleResource {
TimeStamp mIdleTime;
IdleResource(const IdleResource& aOther) = delete;
IdleResource(IdleResource&& aOther) noexcept
: IdleResource(aOther.mIdleTime) {}
IdleResource& operator=(const IdleResource& aOther) = delete;
IdleResource& operator=(IdleResource&& aOther) = delete;
protected:
explicit IdleResource(const TimeStamp& aIdleTime);
~IdleResource();
};
struct IdleDatabaseInfo final : public IdleResource {
InitializedOnce<const NotNull<DatabaseInfo*>> mDatabaseInfo;
public:
explicit IdleDatabaseInfo(DatabaseInfo& aDatabaseInfo);
IdleDatabaseInfo(const IdleDatabaseInfo& aOther) = delete;
IdleDatabaseInfo(IdleDatabaseInfo&& aOther) noexcept
: IdleResource(std::move(aOther)),
mDatabaseInfo{std::move(aOther.mDatabaseInfo)} {
MOZ_ASSERT(mDatabaseInfo);
MOZ_COUNT_CTOR(ConnectionPool::IdleDatabaseInfo);
}
IdleDatabaseInfo& operator=(const IdleDatabaseInfo& aOther) = delete;
IdleDatabaseInfo& operator=(IdleDatabaseInfo&& aOther) = delete;
~IdleDatabaseInfo();
bool operator==(const IdleDatabaseInfo& aOther) const {
return *mDatabaseInfo == *aOther.mDatabaseInfo;
}
bool operator==(const DatabaseInfo* aDatabaseInfo) const {
return *mDatabaseInfo == aDatabaseInfo;
}
bool operator<(const IdleDatabaseInfo& aOther) const {
return mIdleTime < aOther.mIdleTime;
}
};
class ThreadInfo {
public:
ThreadInfo();
ThreadInfo(nsCOMPtr<nsIThread> aThread, RefPtr<ThreadRunnable> aRunnable)
: mThread{std::move(aThread)}, mRunnable{std::move(aRunnable)} {
AssertIsOnBackgroundThread();
AssertValid();
MOZ_COUNT_CTOR(ConnectionPool::ThreadInfo);
}
ThreadInfo(const ThreadInfo& aOther) = delete;
ThreadInfo& operator=(const ThreadInfo& aOther) = delete;
ThreadInfo(ThreadInfo&& aOther) noexcept;
ThreadInfo& operator=(ThreadInfo&& aOther) = default;
bool IsValid() const {
const bool res = mThread;
if (res) {
AssertValid();
} else {
AssertEmpty();
}
return res;
}
void AssertValid() const {
MOZ_ASSERT(mThread);
MOZ_ASSERT(mRunnable);
}
void AssertEmpty() const {
MOZ_ASSERT(!mThread);
MOZ_ASSERT(!mRunnable);
}
nsIThread& ThreadRef() {
AssertValid();
return *mThread;
}
std::tuple<nsCOMPtr<nsIThread>, RefPtr<ThreadRunnable>> Forget() {
AssertValid();
return {std::move(mThread), std::move(mRunnable)};
}
~ThreadInfo();
bool operator==(const ThreadInfo& aOther) const {
return mThread == aOther.mThread && mRunnable == aOther.mRunnable;
}
private:
nsCOMPtr<nsIThread> mThread;
RefPtr<ThreadRunnable> mRunnable;
};
struct IdleThreadInfo final : public IdleResource {
ThreadInfo mThreadInfo;
explicit IdleThreadInfo(ThreadInfo aThreadInfo);
IdleThreadInfo(const IdleThreadInfo& aOther) = delete;
IdleThreadInfo(IdleThreadInfo&& aOther) noexcept
: IdleResource(std::move(aOther)),
mThreadInfo(std::move(aOther.mThreadInfo)) {
AssertIsOnBackgroundThread();
mThreadInfo.AssertValid();
MOZ_COUNT_CTOR(ConnectionPool::IdleThreadInfo);
}
IdleThreadInfo& operator=(const IdleThreadInfo& aOther) = delete;
IdleThreadInfo& operator=(IdleThreadInfo&& aOther) = delete;
~IdleThreadInfo();
bool operator==(const IdleThreadInfo& aOther) const {
return mThreadInfo == aOther.mThreadInfo;
}
bool operator<(const IdleThreadInfo& aOther) const {
return mIdleTime < aOther.mIdleTime;
}
};
// This mutex guards mDatabases, see below.
Mutex mDatabasesMutex;
nsTArray<IdleThreadInfo> mIdleThreads;
nsTArray<IdleDatabaseInfo> mIdleDatabases;
nsTArray<NotNull<DatabaseInfo*>> mDatabasesPerformingIdleMaintenance;
nsCOMPtr<nsITimer> mIdleTimer;
TimeStamp mTargetIdleTime;
// Only modifed on the owning thread, but read on multiple threads. Therefore
// all modifications and all reads off the owning thread must be protected by
// mDatabasesMutex.
nsClassHashtable<nsCStringHashKey, DatabaseInfo> mDatabases;
nsClassHashtable<nsUint64HashKey, TransactionInfo> mTransactions;
nsTArray<NotNull<TransactionInfo*>> mQueuedTransactions;
nsTArray<UniquePtr<DatabasesCompleteCallback>> mCompleteCallbacks;
uint64_t mNextTransactionId;
uint32_t mTotalThreadCount;
FlippedOnce<false> mShutdownRequested;
FlippedOnce<false> mShutdownComplete;
public:
ConnectionPool();
void AssertIsOnOwningThread() const {
NS_ASSERT_OWNINGTHREAD(ConnectionPool);
}
Result<RefPtr<DatabaseConnection>, nsresult> GetOrCreateConnection(
const Database& aDatabase);
uint64_t Start(const nsID& aBackgroundChildLoggingId,
const nsACString& aDatabaseId, int64_t aLoggingSerialNumber,
const nsTArray<nsString>& aObjectStoreNames,
bool aIsWriteTransaction,
TransactionDatabaseOperationBase* aTransactionOp);
void Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable);
void Finish(uint64_t aTransactionId, FinishCallback* aCallback);
void CloseDatabaseWhenIdle(const nsACString& aDatabaseId) {
Unused << CloseDatabaseWhenIdleInternal(aDatabaseId);
}
void WaitForDatabasesToComplete(nsTArray<nsCString>&& aDatabaseIds,
nsIRunnable* aCallback);
void Shutdown();
NS_INLINE_DECL_REFCOUNTING(ConnectionPool)
private:
~ConnectionPool();
static void IdleTimerCallback(nsITimer* aTimer, void* aClosure);