michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef DOMStorageDBThread_h___ michael@0: #define DOMStorageDBThread_h___ michael@0: michael@0: #include "prthread.h" michael@0: #include "prinrval.h" michael@0: #include "nsTArray.h" michael@0: #include "mozilla/Monitor.h" michael@0: #include "mozilla/storage/StatementCache.h" michael@0: #include "nsString.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsClassHashtable.h" michael@0: #include "nsIFile.h" michael@0: michael@0: class mozIStorageConnection; michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: class DOMStorageCacheBridge; michael@0: class DOMStorageUsageBridge; michael@0: class DOMStorageUsage; michael@0: michael@0: typedef mozilla::storage::StatementCache StatementCache; michael@0: michael@0: // Interface used by the cache to post operations to the asynchronous michael@0: // database thread or process. michael@0: class DOMStorageDBBridge michael@0: { michael@0: public: michael@0: DOMStorageDBBridge(); michael@0: virtual ~DOMStorageDBBridge() {} michael@0: michael@0: // Ensures the database engine is started michael@0: virtual nsresult Init() = 0; michael@0: michael@0: // Releases the database and disallows its usage michael@0: virtual nsresult Shutdown() = 0; michael@0: michael@0: // Asynchronously fills the cache with data from the database for first use. michael@0: // When |aPriority| is true, the preload operation is scheduled as the first one. michael@0: // This method is responsible to keep hard reference to the cache for the time of michael@0: // the preload or, when preload cannot be performed, call LoadDone() immediately. michael@0: virtual void AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority = false) = 0; michael@0: michael@0: // Asynchronously fill the |usage| object with actual usage of data by its scope. michael@0: // The scope is eTLD+1 tops, never deeper subdomains. michael@0: virtual void AsyncGetUsage(DOMStorageUsageBridge* aUsage) = 0; michael@0: michael@0: // Synchronously fills the cache, when |aForceSync| is false and cache already got some michael@0: // data before, the method waits for the running preload to finish michael@0: virtual void SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync = false) = 0; michael@0: michael@0: // Called when an existing key is modified in the storage, schedules update to the database michael@0: virtual nsresult AsyncAddItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue) = 0; michael@0: michael@0: // Called when an existing key is modified in the storage, schedules update to the database michael@0: virtual nsresult AsyncUpdateItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue) = 0; michael@0: michael@0: // Called when an item is removed from the storage, schedules delete of the key michael@0: virtual nsresult AsyncRemoveItem(DOMStorageCacheBridge* aCache, const nsAString& aKey) = 0; michael@0: michael@0: // Called when the whole storage is cleared by the DOM API, schedules delete of the scope michael@0: virtual nsresult AsyncClear(DOMStorageCacheBridge* aCache) = 0; michael@0: michael@0: // Called when chrome deletes e.g. cookies, schedules delete of the whole database michael@0: virtual void AsyncClearAll() = 0; michael@0: michael@0: // Called when only a domain and its subdomains or an app data is about to clear michael@0: virtual void AsyncClearMatchingScope(const nsACString& aScope) = 0; michael@0: michael@0: // Forces scheduled DB operations to be early flushed to the disk michael@0: virtual void AsyncFlush() = 0; michael@0: michael@0: // Check whether the scope has any data stored on disk and is thus allowed to preload michael@0: virtual bool ShouldPreloadScope(const nsACString& aScope) = 0; michael@0: michael@0: // Get the complete list of scopes having data michael@0: virtual void GetScopesHavingData(InfallibleTArray* aScopes) = 0; michael@0: }; michael@0: michael@0: // The implementation of the the database engine, this directly works michael@0: // with the sqlite or any other db API we are based on michael@0: // This class is resposible for collecting and processing asynchronous michael@0: // DB operations over caches (DOMStorageCache) communicating though michael@0: // DOMStorageCacheBridge interface class michael@0: class DOMStorageDBThread MOZ_FINAL : public DOMStorageDBBridge michael@0: { michael@0: public: michael@0: class PendingOperations; michael@0: michael@0: // Representation of a singe database task, like adding and removing keys, michael@0: // (pre)loading the whole origin data, cleaning. michael@0: class DBOperation michael@0: { michael@0: public: michael@0: typedef enum { michael@0: // Only operation that reads data from the database michael@0: opPreload, michael@0: // The same as opPreload, just executed with highest priority michael@0: opPreloadUrgent, michael@0: michael@0: // Load usage of a scope michael@0: opGetUsage, michael@0: michael@0: // Operations invoked by the DOM content API michael@0: opAddItem, michael@0: opUpdateItem, michael@0: opRemoveItem, michael@0: opClear, michael@0: michael@0: // Operations invoked by chrome michael@0: opClearAll, michael@0: opClearMatchingScope, michael@0: } OperationType; michael@0: michael@0: DBOperation(const OperationType aType, michael@0: DOMStorageCacheBridge* aCache = nullptr, michael@0: const nsAString& aKey = EmptyString(), michael@0: const nsAString& aValue = EmptyString()); michael@0: DBOperation(const OperationType aType, michael@0: DOMStorageUsageBridge* aUsage); michael@0: DBOperation(const OperationType aType, michael@0: const nsACString& aScope); michael@0: ~DBOperation(); michael@0: michael@0: // Executes the operation, doesn't necessarity have to be called on the I/O thread michael@0: void PerformAndFinalize(DOMStorageDBThread* aThread); michael@0: michael@0: // Finalize the operation, i.e. do any internal cleanup and finish calls michael@0: void Finalize(nsresult aRv); michael@0: michael@0: // The operation type michael@0: OperationType Type() { return mType; } michael@0: michael@0: // The operation scope (=origin) michael@0: const nsCString Scope(); michael@0: michael@0: // |Scope + key| the operation is working with michael@0: const nsCString Target(); michael@0: michael@0: private: michael@0: // The operation implementation body michael@0: nsresult Perform(DOMStorageDBThread* aThread); michael@0: michael@0: friend class PendingOperations; michael@0: OperationType mType; michael@0: nsRefPtr mCache; michael@0: nsRefPtr mUsage; michael@0: nsString mKey; michael@0: nsString mValue; michael@0: nsCString mScope; michael@0: }; michael@0: michael@0: // Encapsulation of collective and coalescing logic for all pending operations michael@0: // except preloads that are handled separately as priority operations michael@0: class PendingOperations { michael@0: public: michael@0: PendingOperations(); michael@0: michael@0: // Method responsible for coalescing redundant update operations with the same michael@0: // |Target()| or clear operations with the same or matching |Scope()| michael@0: void Add(DBOperation* aOperation); michael@0: michael@0: // True when there are some scheduled operations to flush on disk michael@0: bool HasTasks(); michael@0: michael@0: // Moves collected operations to a local flat list to allow execution of the operation michael@0: // list out of the thread lock michael@0: bool Prepare(); michael@0: michael@0: // Executes the previously |Prepared()'ed| list of operations, retuns result, but doesn't michael@0: // handle it in any way in case of a failure michael@0: nsresult Execute(DOMStorageDBThread* aThread); michael@0: michael@0: // Finalizes the pending operation list, returns false when too many operations failed michael@0: // to flush what indicates a long standing issue with the database access. michael@0: bool Finalize(nsresult aRv); michael@0: michael@0: // true when a clear that deletes the given |scope| is among the pending operations; michael@0: // when a preload for that scope is being scheduled, it must be finished right away michael@0: bool IsScopeClearPending(const nsACString& aScope); michael@0: michael@0: // Checks whether there is a pending update (or clear, actually) operation for this scope. michael@0: bool IsScopeUpdatePending(const nsACString& aScope); michael@0: michael@0: private: michael@0: // Returns true iff new operation is of type newType and there is a pending michael@0: // operation of type pendingType for the same key (target). michael@0: bool CheckForCoalesceOpportunity(DBOperation* aNewOp, michael@0: DBOperation::OperationType aPendingType, michael@0: DBOperation::OperationType aNewType); michael@0: michael@0: // List of all clearing operations, executed first michael@0: nsClassHashtable mClears; michael@0: michael@0: // List of all update/insert operations, executed as second michael@0: nsClassHashtable mUpdates; michael@0: michael@0: // Collection of all tasks, valid only between Prepare() and Execute() michael@0: nsTArray > mExecList; michael@0: michael@0: // Number of failing flush attempts michael@0: uint32_t mFlushFailureCount; michael@0: }; michael@0: michael@0: public: michael@0: DOMStorageDBThread(); michael@0: virtual ~DOMStorageDBThread() {} michael@0: michael@0: virtual nsresult Init(); michael@0: virtual nsresult Shutdown(); michael@0: michael@0: virtual void AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority = false) michael@0: { InsertDBOp(new DBOperation(aPriority ? DBOperation::opPreloadUrgent : DBOperation::opPreload, aCache)); } michael@0: michael@0: virtual void SyncPreload(DOMStorageCacheBridge* aCache, bool aForce = false); michael@0: michael@0: virtual void AsyncGetUsage(DOMStorageUsageBridge * aUsage) michael@0: { InsertDBOp(new DBOperation(DBOperation::opGetUsage, aUsage)); } michael@0: michael@0: virtual nsresult AsyncAddItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue) michael@0: { return InsertDBOp(new DBOperation(DBOperation::opAddItem, aCache, aKey, aValue)); } michael@0: michael@0: virtual nsresult AsyncUpdateItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue) michael@0: { return InsertDBOp(new DBOperation(DBOperation::opUpdateItem, aCache, aKey, aValue)); } michael@0: michael@0: virtual nsresult AsyncRemoveItem(DOMStorageCacheBridge* aCache, const nsAString& aKey) michael@0: { return InsertDBOp(new DBOperation(DBOperation::opRemoveItem, aCache, aKey)); } michael@0: michael@0: virtual nsresult AsyncClear(DOMStorageCacheBridge* aCache) michael@0: { return InsertDBOp(new DBOperation(DBOperation::opClear, aCache)); } michael@0: michael@0: virtual void AsyncClearAll() michael@0: { InsertDBOp(new DBOperation(DBOperation::opClearAll)); } michael@0: michael@0: virtual void AsyncClearMatchingScope(const nsACString& aScope) michael@0: { InsertDBOp(new DBOperation(DBOperation::opClearMatchingScope, aScope)); } michael@0: michael@0: virtual void AsyncFlush(); michael@0: michael@0: virtual bool ShouldPreloadScope(const nsACString& aScope); michael@0: virtual void GetScopesHavingData(InfallibleTArray* aScopes); michael@0: michael@0: private: michael@0: nsCOMPtr mDatabaseFile; michael@0: PRThread* mThread; michael@0: michael@0: // The monitor we drive the thread with michael@0: Monitor mMonitor; michael@0: michael@0: // Flag to stop, protected by the monitor michael@0: bool mStopIOThread; michael@0: michael@0: // Whether WAL is enabled michael@0: bool mWALModeEnabled; michael@0: michael@0: // Whether DB has already been open, avoid races between main thread reads michael@0: // and pending DB init in the background I/O thread michael@0: bool mDBReady; michael@0: michael@0: // State of the database initiation michael@0: nsresult mStatus; michael@0: michael@0: // List of scopes having data, for optimization purposes only michael@0: nsTHashtable mScopesHavingData; michael@0: michael@0: StatementCache mWorkerStatements; michael@0: StatementCache mReaderStatements; michael@0: michael@0: // Connection used by the worker thread for all read and write ops michael@0: nsCOMPtr mWorkerConnection; michael@0: michael@0: // Connection used only on the main thread for sync read operations michael@0: nsCOMPtr mReaderConnection; michael@0: michael@0: // Time the first pending operation has been added to the pending operations michael@0: // list michael@0: PRIntervalTime mDirtyEpoch; michael@0: michael@0: // Flag to force immediate flush of all pending operations michael@0: bool mFlushImmediately; michael@0: michael@0: // List of preloading operations, in chronological or priority order. michael@0: // Executed prioritly over pending update operations. michael@0: nsTArray mPreloads; michael@0: michael@0: // Collector of pending update operations michael@0: PendingOperations mPendingTasks; michael@0: michael@0: // Counter of calls for thread priority rising. michael@0: int32_t mPriorityCounter; michael@0: michael@0: // Helper to direct an operation to one of the arrays above; michael@0: // also checks IsScopeClearPending for preloads michael@0: nsresult InsertDBOp(DBOperation* aOperation); michael@0: michael@0: // Opens the database, first thing we do after start of the thread. michael@0: nsresult OpenDatabaseConnection(); michael@0: nsresult InitDatabase(); michael@0: nsresult ShutdownDatabase(); michael@0: michael@0: // Tries to establish WAL mode michael@0: nsresult SetJournalMode(bool aIsWal); michael@0: nsresult TryJournalMode(); michael@0: michael@0: // Sets the threshold for auto-checkpointing the WAL. michael@0: nsresult ConfigureWALBehavior(); michael@0: michael@0: void SetHigherPriority(); michael@0: void SetDefaultPriority(); michael@0: michael@0: // Ensures we flush pending tasks in some reasonble time michael@0: void ScheduleFlush(); michael@0: michael@0: // Called when flush of pending tasks is being executed michael@0: void UnscheduleFlush(); michael@0: michael@0: // This method is used for two purposes: michael@0: // 1. as a value passed to monitor.Wait() method michael@0: // 2. as in indicator that flush has to be performed michael@0: // michael@0: // Return: michael@0: // - PR_INTERVAL_NO_TIMEOUT when no pending tasks are scheduled michael@0: // - larger then zero when tasks have been scheduled, but it is michael@0: // still not time to perform the flush ; it is actual interval michael@0: // time to wait until the flush has to happen michael@0: // - 0 when it is time to do the flush michael@0: PRIntervalTime TimeUntilFlush(); michael@0: michael@0: // Notifies to the main thread that flush has completed michael@0: void NotifyFlushCompletion(); michael@0: michael@0: // Thread loop michael@0: static void ThreadFunc(void* aArg); michael@0: void ThreadFunc(); michael@0: }; michael@0: michael@0: } // ::dom michael@0: } // ::mozilla michael@0: michael@0: #endif /* DOMStorageDBThread_h___ */