diff options
Diffstat (limited to 'dom/storage/StorageDBThread.h')
-rw-r--r-- | dom/storage/StorageDBThread.h | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/dom/storage/StorageDBThread.h b/dom/storage/StorageDBThread.h new file mode 100644 index 0000000000..7708de5a97 --- /dev/null +++ b/dom/storage/StorageDBThread.h @@ -0,0 +1,493 @@ +/* -*- 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 |