gecko-dev/dom/storage/StorageDBThread.h

494 lines
17 KiB
C++

/* -*- 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/. */
#ifndef mozilla_dom_StorageDBThread_h
#define mozilla_dom_StorageDBThread_h
#include "prthread.h"
#include "prinrval.h"
#include "nsTArray.h"
#include "mozilla/Atomics.h"
#include "mozilla/Monitor.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/OriginAttributes.h"
#include "mozilla/storage/StatementCache.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "nsString.h"
#include "nsCOMPtr.h"
#include "nsClassHashtable.h"
#include "nsIFile.h"
#include "nsIThreadInternal.h"
#include "nsThreadUtils.h"
#include "nsTHashSet.h"
class mozIStorageConnection;
namespace mozilla::dom {
class LocalStorageCacheBridge;
class StorageUsageBridge;
class StorageUsage;
using StatementCache = mozilla::storage::StatementCache<mozIStorageStatement>;
// XXX Fix me!
// 1. Move comments to StorageDBThread/StorageDBChild.
// 2. Devirtualize relevant methods in StorageDBThread/StorageDBChild.
// 3. Remove relevant methods in StorageDBThread/StorageDBChild that are
// unused.
// 4. Remove this class completely.
//
// See bug 1387636 for more details.
#if 0
// Interface used by the cache to post operations to the asynchronous
// database thread or process.
class StorageDBBridge
{
public:
StorageDBBridge();
virtual ~StorageDBBridge() {}
// Ensures the database engine is started
virtual nsresult Init() = 0;
// Releases the database and disallows its usage
virtual nsresult Shutdown() = 0;
// Asynchronously fills the cache with data from the database for first use.
// When |aPriority| is true, the preload operation is scheduled as the first
// one. This method is responsible to keep hard reference to the cache for
// the time of the preload or, when preload cannot be performed, call
// LoadDone() immediately.
virtual void AsyncPreload(LocalStorageCacheBridge* aCache,
bool aPriority = false) = 0;
// Asynchronously fill the |usage| object with actual usage of data by its
// scope. The scope is eTLD+1 tops, never deeper subdomains.
virtual void AsyncGetUsage(StorageUsageBridge* aUsage) = 0;
// Synchronously fills the cache, when |aForceSync| is false and cache already
// got some data before, the method waits for the running preload to finish
virtual void SyncPreload(LocalStorageCacheBridge* aCache,
bool aForceSync = false) = 0;
// Called when an existing key is modified in the storage, schedules update to
// the database
virtual nsresult AsyncAddItem(LocalStorageCacheBridge* aCache,
const nsAString& aKey,
const nsAString& aValue) = 0;
// Called when an existing key is modified in the storage, schedules update to
// the database
virtual nsresult AsyncUpdateItem(LocalStorageCacheBridge* aCache,
const nsAString& aKey,
const nsAString& aValue) = 0;
// Called when an item is removed from the storage, schedules delete of the
// key
virtual nsresult AsyncRemoveItem(LocalStorageCacheBridge* aCache,
const nsAString& aKey) = 0;
// Called when the whole storage is cleared by the DOM API, schedules delete
// of the scope
virtual nsresult AsyncClear(LocalStorageCacheBridge* aCache) = 0;
// Called when chrome deletes e.g. cookies, schedules delete of the whole
// database
virtual void AsyncClearAll() = 0;
// Called when only a domain and its subdomains is about to clear
virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix) = 0;
// Called when data matching an origin pattern have to be cleared
virtual void AsyncClearMatchingOriginAttributes(const OriginAttributesPattern& aPattern) = 0;
// Forces scheduled DB operations to be early flushed to the disk
virtual void AsyncFlush() = 0;
// Check whether the scope has any data stored on disk and is thus allowed to
// preload
virtual bool ShouldPreloadOrigin(const nsACString& aOriginNoSuffix) = 0;
};
#endif
// The implementation of the the database engine, this directly works
// with the sqlite or any other db API we are based on
// This class is resposible for collecting and processing asynchronous
// DB operations over caches (LocalStorageCache) communicating though
// LocalStorageCacheBridge interface class
class StorageDBThread final {
public:
class PendingOperations;
// Representation of a singe database task, like adding and removing keys,
// (pre)loading the whole origin data, cleaning.
class DBOperation {
public:
enum OperationType {
// Only operation that reads data from the database
opPreload,
// The same as opPreload, just executed with highest priority
opPreloadUrgent,
// Load usage of a scope
opGetUsage,
// Operations invoked by the DOM content API
opAddItem,
opUpdateItem,
opRemoveItem,
// Clears a specific single origin data
opClear,
// Operations invoked by chrome
// Clear all the data stored in the database, for all scopes, no
// exceptions
opClearAll,
// Clear data under a domain and all its subdomains regardless
// OriginAttributes value
opClearMatchingOrigin,
// Clear all data matching an OriginAttributesPattern regardless a domain
opClearMatchingOriginAttributes,
};
explicit DBOperation(const OperationType aType,
LocalStorageCacheBridge* aCache = nullptr,
const nsAString& aKey = u""_ns,
const nsAString& aValue = u""_ns);
DBOperation(const OperationType aType, StorageUsageBridge* aUsage);
DBOperation(const OperationType aType, const nsACString& aOriginNoSuffix);
DBOperation(const OperationType aType,
const OriginAttributesPattern& aOriginNoSuffix);
~DBOperation();
// Executes the operation, doesn't necessarity have to be called on the I/O
// thread
void PerformAndFinalize(StorageDBThread* aThread);
// Finalize the operation, i.e. do any internal cleanup and finish calls
void Finalize(nsresult aRv);
// The operation type
OperationType Type() const { return mType; }
// The origin in the database usage format (reversed)
const nsCString OriginNoSuffix() const;
// The origin attributes suffix
const nsCString OriginSuffix() const;
// |origin suffix + origin key| the operation is working with or a scope
// pattern to delete with simple SQL's "LIKE %" from the database.
const nsCString Origin() const;
// |origin suffix + origin key + key| the operation is working with
const nsCString Target() const;
// Pattern to delete matching data with this op
const OriginAttributesPattern& OriginPattern() const {
return mOriginPattern;
}
private:
// The operation implementation body
nsresult Perform(StorageDBThread* aThread);
friend class PendingOperations;
OperationType mType;
RefPtr<LocalStorageCacheBridge> mCache;
RefPtr<StorageUsageBridge> mUsage;
nsString const mKey;
nsString const mValue;
nsCString const mOrigin;
OriginAttributesPattern const mOriginPattern;
};
// Encapsulation of collective and coalescing logic for all pending operations
// except preloads that are handled separately as priority operations
class PendingOperations {
public:
PendingOperations();
// Method responsible for coalescing redundant update operations with the
// same |Target()| or clear operations with the same or matching |Origin()|
void Add(UniquePtr<DBOperation> aOperation);
// True when there are some scheduled operations to flush on disk
bool HasTasks() const;
// Moves collected operations to a local flat list to allow execution of the
// operation list out of the thread lock
bool Prepare();
// Executes the previously |Prepared()'ed| list of operations, returns
// result, but doesn't handle it in any way in case of a failure
nsresult Execute(StorageDBThread* aThread);
// Finalizes the pending operation list, returns false when too many
// operations failed to flush what indicates a long standing issue with the
// database access.
bool Finalize(nsresult aRv);
// true when a clear that deletes the given origin attr pattern and/or
// origin key is among the pending operations; when a preload for that scope
// is being scheduled, it must be finished right away
bool IsOriginClearPending(const nsACString& aOriginSuffix,
const nsACString& aOriginNoSuffix) const;
// Checks whether there is a pending update operation for this scope.
bool IsOriginUpdatePending(const nsACString& aOriginSuffix,
const nsACString& aOriginNoSuffix) const;
private:
// Returns true iff new operation is of type newType and there is a pending
// operation of type pendingType for the same key (target).
bool CheckForCoalesceOpportunity(DBOperation* aNewOp,
DBOperation::OperationType aPendingType,
DBOperation::OperationType aNewType);
// List of all clearing operations, executed first
nsClassHashtable<nsCStringHashKey, DBOperation> mClears;
// List of all update/insert operations, executed as second
nsClassHashtable<nsCStringHashKey, DBOperation> mUpdates;
// Collection of all tasks, valid only between Prepare() and Execute()
nsTArray<UniquePtr<DBOperation> > mExecList;
// Number of failing flush attempts
uint32_t mFlushFailureCount;
};
class ThreadObserver final : public nsIThreadObserver {
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSITHREADOBSERVER
ThreadObserver()
: mHasPendingEvents(false), mMonitor("StorageThreadMonitor") {}
bool HasPendingEvents() {
mMonitor.AssertCurrentThreadOwns();
return mHasPendingEvents;
}
void ClearPendingEvents() {
mMonitor.AssertCurrentThreadOwns();
mHasPendingEvents = false;
}
Monitor& GetMonitor() { return mMonitor; }
private:
virtual ~ThreadObserver() = default;
bool mHasPendingEvents;
// The monitor we drive the thread with
Monitor mMonitor MOZ_UNANNOTATED;
};
class InitHelper;
class NoteBackgroundThreadRunnable;
class ShutdownRunnable : public Runnable {
// Expected to be only 0 or 1.
const uint32_t mPrivateBrowsingId;
// Only touched on the main thread.
bool& mDone;
public:
explicit ShutdownRunnable(const uint32_t aPrivateBrowsingId, bool& aDone)
: Runnable("dom::StorageDBThread::ShutdownRunnable"),
mPrivateBrowsingId(aPrivateBrowsingId),
mDone(aDone) {
MOZ_ASSERT(NS_IsMainThread());
}
private:
~ShutdownRunnable() = default;
NS_DECL_NSIRUNNABLE
};
public:
explicit StorageDBThread(uint32_t aPrivateBrowsingId);
virtual ~StorageDBThread() = default;
static StorageDBThread* Get(uint32_t aPrivateBrowsingId);
static StorageDBThread* GetOrCreate(const nsString& aProfilePath,
uint32_t aPrivateBrowsingId);
static nsresult GetProfilePath(nsString& aProfilePath);
virtual nsresult Init(const nsString& aProfilePath);
// Flushes all uncommited data and stops the I/O thread.
virtual nsresult Shutdown();
virtual void AsyncPreload(LocalStorageCacheBridge* aCache,
bool aPriority = false) {
InsertDBOp(MakeUnique<DBOperation>(
aPriority ? DBOperation::opPreloadUrgent : DBOperation::opPreload,
aCache));
}
virtual void SyncPreload(LocalStorageCacheBridge* aCache,
bool aForce = false);
virtual void AsyncGetUsage(StorageUsageBridge* aUsage) {
InsertDBOp(MakeUnique<DBOperation>(DBOperation::opGetUsage, aUsage));
}
virtual nsresult AsyncAddItem(LocalStorageCacheBridge* aCache,
const nsAString& aKey,
const nsAString& aValue) {
return InsertDBOp(
MakeUnique<DBOperation>(DBOperation::opAddItem, aCache, aKey, aValue));
}
virtual nsresult AsyncUpdateItem(LocalStorageCacheBridge* aCache,
const nsAString& aKey,
const nsAString& aValue) {
return InsertDBOp(MakeUnique<DBOperation>(DBOperation::opUpdateItem, aCache,
aKey, aValue));
}
virtual nsresult AsyncRemoveItem(LocalStorageCacheBridge* aCache,
const nsAString& aKey) {
return InsertDBOp(
MakeUnique<DBOperation>(DBOperation::opRemoveItem, aCache, aKey));
}
virtual nsresult AsyncClear(LocalStorageCacheBridge* aCache) {
return InsertDBOp(MakeUnique<DBOperation>(DBOperation::opClear, aCache));
}
virtual void AsyncClearAll() {
InsertDBOp(MakeUnique<DBOperation>(DBOperation::opClearAll));
}
virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix) {
InsertDBOp(MakeUnique<DBOperation>(DBOperation::opClearMatchingOrigin,
aOriginNoSuffix));
}
virtual void AsyncClearMatchingOriginAttributes(
const OriginAttributesPattern& aPattern) {
InsertDBOp(MakeUnique<DBOperation>(
DBOperation::opClearMatchingOriginAttributes, aPattern));
}
virtual void AsyncFlush();
virtual bool ShouldPreloadOrigin(const nsACString& aOrigin);
// Get the complete list of scopes having data.
void GetOriginsHavingData(nsTArray<nsCString>* aOrigins);
private:
nsCOMPtr<nsIFile> mDatabaseFile;
PRThread* mThread;
// Used to observe runnables dispatched to our thread and to monitor it.
RefPtr<ThreadObserver> mThreadObserver;
// Flag to stop, protected by the monitor returned by
// mThreadObserver->GetMonitor().
bool mStopIOThread;
// Whether WAL is enabled
bool mWALModeEnabled;
// Whether DB has already been open, avoid races between main thread reads
// and pending DB init in the background I/O thread
Atomic<bool, ReleaseAcquire> mDBReady;
// State of the database initiation
nsresult mStatus;
// List of origins (including origin attributes suffix) having data, for
// optimization purposes only
nsTHashSet<nsCString> mOriginsHavingData;
// Connection used by the worker thread for all read and write ops
nsCOMPtr<mozIStorageConnection> mWorkerConnection;
// Connection used only on the main thread for sync read operations
nsCOMPtr<mozIStorageConnection> mReaderConnection;
StatementCache mWorkerStatements;
StatementCache mReaderStatements;
// Time the first pending operation has been added to the pending operations
// list
TimeStamp mDirtyEpoch;
// Flag to force immediate flush of all pending operations
bool mFlushImmediately;
// List of preloading operations, in chronological or priority order.
// Executed prioritly over pending update operations.
nsTArray<DBOperation*> mPreloads;
// Collector of pending update operations
PendingOperations mPendingTasks;
// Expected to be only 0 or 1.
const uint32_t mPrivateBrowsingId;
// Counter of calls for thread priority rising.
int32_t mPriorityCounter;
// Helper to direct an operation to one of the arrays above;
// also checks IsOriginClearPending for preloads
nsresult InsertDBOp(UniquePtr<DBOperation> aOperation);
// Opens the database, first thing we do after start of the thread.
nsresult OpenDatabaseConnection();
nsresult OpenAndUpdateDatabase();
nsresult InitDatabase();
nsresult ShutdownDatabase();
// Tries to establish WAL mode
nsresult SetJournalMode(bool aIsWal);
nsresult TryJournalMode();
// Sets the threshold for auto-checkpointing the WAL.
nsresult ConfigureWALBehavior();
void SetHigherPriority();
void SetDefaultPriority();
// Ensures we flush pending tasks in some reasonble time
void ScheduleFlush();
// Called when flush of pending tasks is being executed
void UnscheduleFlush();
// This method is used for two purposes:
// 1. as a value passed to monitor.Wait() method
// 2. as in indicator that flush has to be performed
//
// Return:
// - TimeDuration::Forever() when no pending tasks are scheduled
// - Non-zero TimeDuration when tasks have been scheduled, but it
// is still not time to perform the flush ; it is actual time to
// wait until the flush has to happen.
// - 0 TimeDuration when it is time to do the flush
TimeDuration TimeUntilFlush();
// Notifies to the main thread that flush has completed
void NotifyFlushCompletion();
// Thread loop
static void ThreadFunc(void* aArg);
void ThreadFunc();
};
} // namespace mozilla::dom
#endif // mozilla_dom_StorageDBThread_h