DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Implementation

Mercurial (409f3966645a)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
/* -*- 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 "nsDataHashtable.h"
#include "nsIObserver.h"
#include "nsITimer.h"
#include "nsRefPtrHashtable.h"
#include "nsString.h"

class psm_DataStorageTest;

namespace mozilla {
class DataStorageMemoryReporter;

namespace dom {
class ContentChild;
class DataStorageEntry;
class DataStorageItem;
}

/**
 * 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.
 * - When any persistent data changes, a timer is initialized that will
 *   eventually asynchronously write 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 cause the
 *   timer to be reinitialized and another "data-storage-written" event will
 *   be sent.
 * - When DataStorage observes the topic "profile-before-change" in
 *   anticipation of shutdown, all persistent data is synchronously written to
 *   the backing file. The worker thread responsible for these writes is then
 *   disabled to prevent further writes to that file (the delayed-write timer
 *   is cancelled when this happens). Note that the "worker thread" is actually
 *   a single thread shared between all DataStorage instances.
 * - 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
};

enum class DataStorageClass {
#define DATA_STORAGE(_) _,
#include "mozilla/DataStorageList.h"
#undef DATA_STORAGE
};

class DataStorage : public nsIObserver
{
  typedef dom::DataStorageItem DataStorageItem;

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<DataStorage> Get(DataStorageClass aFilename);
  static already_AddRefed<DataStorage> GetIfExists(DataStorageClass aFilename);

  // Initializes the DataStorage. Must be called before using.
  // aDataWillPersist returns whether or not data can be persistently saved.
  // aItems is used in the content process to initialize a cache of the items
  // received from the parent process over IPC. nullptr must be passed for the
  // parent process.
  nsresult Init(/*out*/bool& aDataWillPersist,
                const InfallibleTArray<mozilla::dom::DataStorageItem>*
                  aItems = nullptr);
  // 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 file names that we know about.
  static void GetAllFileNames(nsTArray<nsString>& aItems);

  // Read all child process data that we know about.
  static void GetAllChildProcessData(nsTArray<mozilla::dom::DataStorageEntry>& aEntries);

  // Read all of the data items.
  void GetAll(InfallibleTArray<DataStorageItem>* aItems);

  // Set the cached copy of our DataStorage entries in the content process.
  static void SetCachedStorageEntries(const InfallibleTArray<mozilla::dom::DataStorageEntry>& aEntries);

  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;

private:
  explicit DataStorage(const nsString& aFilename);
  virtual ~DataStorage();

  static already_AddRefed<DataStorage> GetFromRawFileName(const nsString& aFilename);

  friend class ::psm_DataStorageTest;
  friend class mozilla::dom::ContentChild;
  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 nsDataHashtable<nsCStringHashKey, Entry> DataStorageTable;
  typedef nsRefPtrHashtable<nsStringHashKey, DataStorage> DataStorages;

  void WaitForReady();
  nsresult AsyncWriteData(const MutexAutoLock& aProofOfLock);
  nsresult AsyncReadData(bool& aHaveProfileDir,
                         const MutexAutoLock& aProofOfLock);
  nsresult AsyncSetTimer(const MutexAutoLock& aProofOfLock);
  nsresult DispatchShutdownTimer(const MutexAutoLock& aProofOfLock);

  static nsresult ValidateKeyAndValue(const nsCString& aKey,
                                      const nsCString& aValue);
  static void TimerCallback(nsITimer* aTimer, void* aClosure);
  void SetTimer();
  void ShutdownTimer();
  void NotifyObservers(const char* aTopic);

  void PrefChanged(const char* aPref);

  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,
                        InfallibleTArray<DataStorageItem>* aItems,
                        const MutexAutoLock& aProofOfLock);

  Mutex mMutex; // This mutex protects access to the following members:
  DataStorageTable  mPersistentDataTable;
  DataStorageTable  mTemporaryDataTable;
  DataStorageTable  mPrivateDataTable;
  nsCOMPtr<nsIFile> mBackingFile;
  nsCOMPtr<nsITimer> mTimer; // All uses after init must be on the worker thread
  uint32_t mTimerDelay; // in milliseconds
  bool mPendingWrite; // true if a write is needed but hasn't been dispatched
  bool mShuttingDown;
  // (End list of members protected by mMutex)

  mozilla::Atomic<bool> mInitCalled; // Indicates that Init() has been called.

  Monitor mReadyMonitor; // Do not acquire this at the same time as mMutex.
  bool mReady; // Indicates that saved data has been read and Get can proceed.

  const nsString mFilename;

  static StaticAutoPtr<DataStorages> sDataStorages;
};

} // namespace mozilla

#endif // mozilla_DataStorage_h