/* -*- 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_DataStorage_h #define mozilla_DataStorage_h #include "mozilla/Atomics.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Monitor.h" #include "mozilla/Mutex.h" #include "mozilla/StaticPtr.h" #include "nsCOMPtr.h" #include "nsTHashMap.h" #include "nsIObserver.h" #include "nsITimer.h" #include "nsRefPtrHashtable.h" #include "nsString.h" class psm_DataStorageTest; namespace mozilla { class DataStorageMemoryReporter; class TaskQueue; /** * DataStorage is a threadsafe, generic, narrow string-based hash map that * persists data on disk and additionally handles temporary and private data. * However, if used in a context where there is no profile directory, data * will not be persisted. * * Its lifecycle is as follows: * - Allocate with a filename (this is or will eventually be a file in the * profile directory, if the profile exists). * - Call Init() from the main thread. This spins off an asynchronous read * of the backing file. * - Eventually observers of the topic "data-storage-ready" will be notified * with the backing filename as the data in the notification when this * has completed. * - Should the profile directory not be available, (e.g. in xpcshell), * DataStorage will not initially read any persistent data. The * "data-storage-ready" event will still be emitted. This follows semantics * similar to the permission manager and allows tests that test unrelated * components to proceed without a profile. * - A timer periodically fires on a background thread that checks if any * persistent data has changed, and if so writes all persistent data to the * backing file. When this happens, observers will be notified with the * topic "data-storage-written" and the backing filename as the data. * It is possible to receive a "data-storage-written" event while there exist * pending persistent data changes. However, those changes will eventually be * written when the timer fires again, and eventually another * "data-storage-written" event will be sent. * - When a DataStorage instance observes the topic "profile-before-change" in * anticipation of shutdown, all persistent data for that DataStorage is * written to the backing file (this blocks the main thread). In the process * of doing this, the background serial event target responsible for these * writes is then shut down to prevent further writes to that file (the * background timer is also cancelled when this happens). * If "profile-before-change" is not observed, this happens upon observing * "xpcom-shutdown-threads". * - For testing purposes, the preference "test.datastorage.write_timer_ms" can * be set to cause the asynchronous writing of data to happen more quickly. * - To prevent unbounded memory and disk use, the number of entries in each * table is limited to 1024. Evictions are handled in by a modified LRU scheme * (see implementation comments). * - NB: Instances of DataStorage have long lifetimes because they are strong * observers of events and won't go away until the observer service does. * * For each key/value: * - The key must be a non-empty string containing no instances of '\t' or '\n' * (this is a limitation of how the data is stored and will be addressed in * the future). * - The key must have a length no more than 256. * - The value must not contain '\n' and must have a length no more than 1024. * (the length limits are to prevent unbounded disk and memory usage) */ /** * Data that is DataStorage_Persistent is saved on disk. DataStorage_Temporary * and DataStorage_Private are not saved. DataStorage_Private is meant to * only be set and accessed from private contexts. It will be cleared upon * observing the event "last-pb-context-exited". */ enum DataStorageType { DataStorage_Persistent, DataStorage_Temporary, DataStorage_Private }; struct DataStorageItem final { nsCString key; nsCString value; DataStorageType type; }; enum class DataStorageClass { #define DATA_STORAGE(_) _, #include "mozilla/DataStorageList.h" #undef DATA_STORAGE }; class DataStorage : public nsIObserver { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIOBSERVER // If there is a profile directory, there is or will eventually be a file // by the name specified by aFilename there. static already_AddRefed Get(DataStorageClass aFilename); // Initializes the DataStorage. Must be called before using. nsresult Init(); // Given a key and a type of data, returns a value. Returns an empty string if // the key is not present for that type of data. If Get is called before the // "data-storage-ready" event is observed, it will block. NB: It is not // currently possible to differentiate between missing data and data that is // the empty string. nsCString Get(const nsCString& aKey, DataStorageType aType); // Give a key, value, and type of data, adds an entry as appropriate. // Updates existing entries. nsresult Put(const nsCString& aKey, const nsCString& aValue, DataStorageType aType); // Given a key and type of data, removes an entry if present. void Remove(const nsCString& aKey, DataStorageType aType); // Removes all entries of all types of data. nsresult Clear(); // Read all of the data items. void GetAll(nsTArray* aItems); size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); // Return true if this data storage is ready to be used. bool IsReady(); void ArmTimer(const MutexAutoLock& aProofOfLock); void ShutdownTimer(); private: explicit DataStorage(const nsString& aFilename); virtual ~DataStorage() = default; static already_AddRefed GetFromRawFileName( const nsString& aFilename); friend class ::psm_DataStorageTest; friend class mozilla::DataStorageMemoryReporter; class Writer; class Reader; class Entry { public: Entry(); bool UpdateScore(); uint32_t mScore; int32_t mLastAccessed; // the last accessed time in days since the epoch nsCString mValue; }; // Utility class for scanning tables for an entry to evict. class KeyAndEntry { public: nsCString mKey; Entry mEntry; }; typedef nsTHashMap DataStorageTable; typedef nsRefPtrHashtable DataStorages; void WaitForReady(); nsresult AsyncWriteData(const MutexAutoLock& aProofOfLock); nsresult AsyncReadData(const MutexAutoLock& aProofOfLock); static nsresult ValidateKeyAndValue(const nsCString& aKey, const nsCString& aValue); static void TimerCallback(nsITimer* aTimer, void* aClosure); void NotifyObservers(const char* aTopic); bool GetInternal(const nsCString& aKey, Entry* aEntry, DataStorageType aType, const MutexAutoLock& aProofOfLock); nsresult PutInternal(const nsCString& aKey, Entry& aEntry, DataStorageType aType, const MutexAutoLock& aProofOfLock); void MaybeEvictOneEntry(DataStorageType aType, const MutexAutoLock& aProofOfLock); DataStorageTable& GetTableForType(DataStorageType aType, const MutexAutoLock& aProofOfLock); void ReadAllFromTable(DataStorageType aType, nsTArray* aItems, const MutexAutoLock& aProofOfLock); Mutex mMutex; // This mutex protects access to the following members: DataStorageTable mPersistentDataTable MOZ_GUARDED_BY(mMutex); DataStorageTable mTemporaryDataTable MOZ_GUARDED_BY(mMutex); DataStorageTable mPrivateDataTable MOZ_GUARDED_BY(mMutex); nsCOMPtr mBackingFile MOZ_GUARDED_BY(mMutex); bool mPendingWrite MOZ_GUARDED_BY( mMutex); // true if a write is needed but hasn't been dispatched bool mTimerArmed MOZ_GUARDED_BY(mMutex); bool mShuttingDown MOZ_GUARDED_BY(mMutex); RefPtr mBackgroundTaskQueue MOZ_GUARDED_BY(mMutex); // (End list of members protected by mMutex) nsCOMPtr mTimer; mozilla::Atomic mInitCalled; // Indicates that Init() has been called. uint32_t mTimerDelayMS; Monitor mReadyMonitor; // Do not acquire this at the same time as mMutex. bool mReady MOZ_GUARDED_BY(mReadyMonitor); // Indicates that saved data has // been read and Get can proceed. const nsString mFilename; static StaticAutoPtr sDataStorages; }; } // namespace mozilla #endif // mozilla_DataStorage_h