/* -*- 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; // 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 mCache; RefPtr 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 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 mClears; // List of all update/insert operations, executed as second nsClassHashtable mUpdates; // Collection of all tasks, valid only between Prepare() and Execute() nsTArray > 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( aPriority ? DBOperation::opPreloadUrgent : DBOperation::opPreload, aCache)); } virtual void SyncPreload(LocalStorageCacheBridge* aCache, bool aForce = false); virtual void AsyncGetUsage(StorageUsageBridge* aUsage) { InsertDBOp(MakeUnique(DBOperation::opGetUsage, aUsage)); } virtual nsresult AsyncAddItem(LocalStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue) { return InsertDBOp( MakeUnique(DBOperation::opAddItem, aCache, aKey, aValue)); } virtual nsresult AsyncUpdateItem(LocalStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue) { return InsertDBOp(MakeUnique(DBOperation::opUpdateItem, aCache, aKey, aValue)); } virtual nsresult AsyncRemoveItem(LocalStorageCacheBridge* aCache, const nsAString& aKey) { return InsertDBOp( MakeUnique(DBOperation::opRemoveItem, aCache, aKey)); } virtual nsresult AsyncClear(LocalStorageCacheBridge* aCache) { return InsertDBOp(MakeUnique(DBOperation::opClear, aCache)); } virtual void AsyncClearAll() { InsertDBOp(MakeUnique(DBOperation::opClearAll)); } virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix) { InsertDBOp(MakeUnique(DBOperation::opClearMatchingOrigin, aOriginNoSuffix)); } virtual void AsyncClearMatchingOriginAttributes( const OriginAttributesPattern& aPattern) { InsertDBOp(MakeUnique( DBOperation::opClearMatchingOriginAttributes, aPattern)); } virtual void AsyncFlush(); virtual bool ShouldPreloadOrigin(const nsACString& aOrigin); // Get the complete list of scopes having data. void GetOriginsHavingData(nsTArray* aOrigins); private: nsCOMPtr mDatabaseFile; PRThread* mThread; // Used to observe runnables dispatched to our thread and to monitor it. RefPtr 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 mDBReady; // State of the database initiation nsresult mStatus; // List of origins (including origin attributes suffix) having data, for // optimization purposes only nsTHashSet mOriginsHavingData; // Connection used by the worker thread for all read and write ops nsCOMPtr mWorkerConnection; // Connection used only on the main thread for sync read operations nsCOMPtr 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 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 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