StartupCache.h (11258B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #ifndef StartupCache_h_ 7 #define StartupCache_h_ 8 9 #include <utility> 10 11 #include "nsClassHashtable.h" 12 #include "nsComponentManagerUtils.h" 13 #include "nsTArray.h" 14 #include "nsTHashSet.h" 15 #include "nsTStringHasher.h" // mozilla::DefaultHasher<nsCString> 16 #include "nsZipArchive.h" 17 #include "nsITimer.h" 18 #include "nsIMemoryReporter.h" 19 #include "nsIObserverService.h" 20 #include "nsIObserver.h" 21 #include "nsIObjectOutputStream.h" 22 #include "nsIFile.h" 23 #include "mozilla/Attributes.h" 24 #include "mozilla/AutoMemMap.h" 25 #include "mozilla/Compression.h" 26 #include "mozilla/MemoryReporting.h" 27 #include "mozilla/Monitor.h" 28 #include "mozilla/Mutex.h" 29 #include "mozilla/Result.h" 30 #include "mozilla/UniquePtr.h" 31 #include "mozilla/UniquePtrExtensions.h" 32 33 /** 34 * The StartupCache is a persistent cache of simple key-value pairs, 35 * where the keys are null-terminated c-strings and the values are 36 * arbitrary data, passed as a (char*, size) tuple. 37 * 38 * Clients should use the GetSingleton() static method to access the cache. It 39 * will be available from the end of XPCOM init (NS_InitXPCOM3 in 40 * XPCOMInit.cpp), until XPCOM shutdown begins. The GetSingleton() method will 41 * return null if the cache is unavailable. The cache is only provided for 42 * libxul builds -- it will fail to link in non-libxul builds. The XPCOM 43 * interface is provided only to allow compiled-code tests; clients should avoid 44 * using it. 45 * 46 * The API provided is very simple: GetBuffer() returns a buffer that was 47 * previously stored in the cache (if any), and PutBuffer() inserts a buffer 48 * into the cache. GetBuffer returns a new buffer, and the caller must take 49 * ownership of it. PutBuffer will assert if the client attempts to insert a 50 * buffer with the same name as an existing entry. The cache makes a copy of the 51 * passed-in buffer, so client retains ownership. 52 * 53 * InvalidateCache() may be called if a client suspects data corruption 54 * or wishes to invalidate for any other reason. This will remove all existing 55 * cache data. Additionally, the static method IgnoreDiskCache() can be called 56 * if it is believed that the on-disk cache file is itself corrupt. This call 57 * implicitly calls InvalidateCache (if the singleton has been initialized) to 58 * ensure any data already read from disk is discarded. The cache will not load 59 * data from the disk file until a successful write occurs. 60 * 61 * Finally, getDebugObjectOutputStream() allows debug code to wrap an 62 * objectstream with a debug objectstream, to check for multiply-referenced 63 * objects. These will generally fail to deserialize correctly, unless they are 64 * stateless singletons or the client maintains their own object data map for 65 * deserialization. 66 * 67 * Writes before the final-ui-startup notification are placed in an intermediate 68 * cache in memory, then written out to disk at a later time, to get writes off 69 * the startup path. In any case, clients should not rely on being able to 70 * GetBuffer() data that is written to the cache, since it may not have been 71 * written to disk or another client may have invalidated the cache. In other 72 * words, it should be used as a cache only, and not a reliable persistent 73 * store. 74 * 75 * Some utility functions are provided in StartupCacheUtils. These functions 76 * wrap the buffers into object streams, which may be useful for serializing 77 * objects. Note the above caution about multiply-referenced objects, though -- 78 * the streams are just as 'dumb' as the underlying buffers about 79 * multiply-referenced objects. They just provide some convenience in writing 80 * out data. 81 */ 82 83 namespace mozilla { 84 85 namespace scache { 86 87 struct StartupCacheEntry { 88 UniqueFreePtr<char[]> mData; 89 uint32_t mOffset; 90 uint32_t mCompressedSize; 91 uint32_t mUncompressedSize; 92 int32_t mHeaderOffsetInFile; 93 int32_t mRequestedOrder; 94 bool mRequested; 95 96 MOZ_IMPLICIT StartupCacheEntry(uint32_t aOffset, uint32_t aCompressedSize, 97 uint32_t aUncompressedSize) 98 : mData(nullptr), 99 mOffset(aOffset), 100 mCompressedSize(aCompressedSize), 101 mUncompressedSize(aUncompressedSize), 102 mHeaderOffsetInFile(0), 103 mRequestedOrder(0), 104 mRequested(false) {} 105 106 StartupCacheEntry(UniqueFreePtr<char[]> aData, size_t aLength, 107 int32_t aRequestedOrder) 108 : mData(std::move(aData)), 109 mOffset(0), 110 mCompressedSize(0), 111 mUncompressedSize(aLength), 112 mHeaderOffsetInFile(0), 113 mRequestedOrder(0), 114 mRequested(true) {} 115 116 // std::pair is not trivially move assignable/constructible, so make our own. 117 struct KeyValuePair { 118 const nsCString* first; 119 StartupCacheEntry* second; 120 KeyValuePair(const nsCString* aKeyPtr, StartupCacheEntry* aValuePtr) 121 : first(aKeyPtr), second(aValuePtr) {} 122 }; 123 static_assert(std::is_trivially_move_assignable<KeyValuePair>::value); 124 static_assert(std::is_trivially_move_constructible<KeyValuePair>::value); 125 126 struct Comparator { 127 using Value = KeyValuePair; 128 129 bool Equals(const Value& a, const Value& b) const { 130 return a.second->mRequestedOrder == b.second->mRequestedOrder; 131 } 132 133 bool LessThan(const Value& a, const Value& b) const { 134 return a.second->mRequestedOrder < b.second->mRequestedOrder; 135 } 136 }; 137 }; 138 139 // We don't want to refcount StartupCache, and ObserverService wants to 140 // refcount its listeners, so we'll let it refcount this instead. 141 class StartupCacheListener final : public nsIObserver { 142 ~StartupCacheListener() = default; 143 NS_DECL_THREADSAFE_ISUPPORTS 144 NS_DECL_NSIOBSERVER 145 }; 146 147 class StartupCache : public nsIMemoryReporter { 148 friend class StartupCacheListener; 149 150 public: 151 NS_DECL_THREADSAFE_ISUPPORTS 152 NS_DECL_NSIMEMORYREPORTER 153 154 // StartupCache methods. See above comments for a more detailed description. 155 156 // true if the archive has an entry for the buffer or not. 157 bool HasEntry(const char* id); 158 159 // Returns a buffer that was previously stored, caller does not take ownership 160 nsresult GetBuffer(const char* id, const char** outbuf, uint32_t* length); 161 162 // Stores a buffer. Caller yields ownership. 163 nsresult PutBuffer(const char* id, UniqueFreePtr<char[]>&& inbuf, 164 uint32_t length); 165 166 // Removes the cache file. 167 void InvalidateCache(bool memoryOnly = false); 168 169 // If some event knowingly re-generates the startup cache (like live language 170 // switching) count these events in order to allow them. 171 void CountAllowedInvalidation(); 172 173 // For use during shutdown - this will write the startupcache's data 174 // to disk if the timer hasn't already gone off. 175 void MaybeInitShutdownWrite(); 176 177 // For use during shutdown - ensure we complete the shutdown write 178 // before shutdown, even in the FastShutdown case. 179 void EnsureShutdownWriteComplete(); 180 181 // Signal that data should not be loaded from the cache file 182 static void IgnoreDiskCache(); 183 184 static bool GetIgnoreDiskCache(); 185 186 // In DEBUG builds, returns a stream that will attempt to check for 187 // and disallow multiple writes of the same object. 188 nsresult GetDebugObjectOutputStream(nsIObjectOutputStream* aStream, 189 nsIObjectOutputStream** outStream); 190 191 static StartupCache* GetSingletonNoInit(); 192 static StartupCache* GetSingleton(); 193 static void DeleteSingleton(); 194 195 // This measures all the heap memory used by the StartupCache, i.e. it 196 // excludes the mapping. 197 size_t HeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const 198 MOZ_REQUIRES(mTableLock); 199 200 bool ShouldCompactCache() MOZ_REQUIRES(mTableLock); 201 nsresult ResetStartupWriteTimerCheckingReadCount(); 202 nsresult ResetStartupWriteTimerAndLock(); 203 nsresult ResetStartupWriteTimer() MOZ_REQUIRES(mTableLock); 204 bool StartupWriteComplete(); 205 206 private: 207 StartupCache(); 208 virtual ~StartupCache(); 209 210 friend class StartupCacheInfo; 211 212 Result<Ok, nsresult> LoadArchive() MOZ_REQUIRES(mTableLock); 213 nsresult Init(); 214 215 // Returns a file pointer for the cache file with the given name in the 216 // current profile. 217 Result<nsCOMPtr<nsIFile>, nsresult> GetCacheFile(const nsAString& suffix); 218 219 // Opens the cache file for reading. 220 Result<Ok, nsresult> OpenCache(); 221 222 // Writes the cache to disk 223 Result<Ok, nsresult> WriteToDisk() MOZ_REQUIRES(mTableLock); 224 225 void WaitOnPrefetch(); 226 void StartPrefetchMemory() MOZ_REQUIRES(mTableLock); 227 228 static nsresult InitSingleton(); 229 static void WriteTimeout(nsITimer* aTimer, void* aClosure); 230 void MaybeWriteOffMainThread(); 231 void ThreadedPrefetch(uint8_t* aStart, size_t aSize); 232 233 Monitor mPrefetchComplete{"StartupCachePrefetch"}; 234 bool mPrefetchInProgress MOZ_GUARDED_BY(mPrefetchComplete){false}; 235 236 // This is normally accessed on MainThread, but WriteToDisk() can 237 // access it on other threads 238 HashMap<nsCString, StartupCacheEntry> mTable MOZ_GUARDED_BY(mTableLock); 239 // This owns references to the contents of tables which have been invalidated. 240 // In theory it grows forever if the cache is continually filled and then 241 // invalidated, but this should not happen in practice. Deleting old tables 242 // could create dangling pointers. RefPtrs could be introduced, but it would 243 // be a large amount of error-prone work to change. 244 nsTArray<decltype(mTable)> mOldTables MOZ_GUARDED_BY(mTableLock); 245 size_t mAllowedInvalidationsCount; 246 nsCOMPtr<nsIFile> mFile; 247 mozilla::loader::AutoMemMap mCacheData MOZ_GUARDED_BY(mTableLock); 248 Mutex mTableLock; 249 250 nsCOMPtr<nsIObserverService> mObserverService; 251 RefPtr<StartupCacheListener> mListener; 252 nsCOMPtr<nsITimer> mTimer; 253 254 bool mDirty MOZ_GUARDED_BY(mTableLock); 255 bool mWrittenOnce MOZ_GUARDED_BY(mTableLock); 256 bool mCurTableReferenced MOZ_GUARDED_BY(mTableLock); 257 258 uint32_t mRequestedCount; 259 size_t mCacheEntriesBaseOffset; 260 261 static StaticRefPtr<StartupCache> gStartupCache; 262 static bool gShutdownInitiated; 263 static bool gIgnoreDiskCache; 264 static bool gFoundDiskCacheOnInit; 265 266 UniquePtr<Compression::LZ4FrameDecompressionContext> mDecompressionContext; 267 #ifdef DEBUG 268 nsTHashSet<nsCOMPtr<nsISupports>> mWriteObjectMap; 269 #endif 270 }; 271 272 // This debug outputstream attempts to detect if clients are writing multiple 273 // references to the same object. We only support that if that object 274 // is a singleton. 275 #ifdef DEBUG 276 class StartupCacheDebugOutputStream final : public nsIObjectOutputStream { 277 ~StartupCacheDebugOutputStream() = default; 278 279 NS_DECL_ISUPPORTS 280 NS_DECL_NSIOBJECTOUTPUTSTREAM 281 282 StartupCacheDebugOutputStream(nsIObjectOutputStream* binaryStream, 283 nsTHashSet<nsCOMPtr<nsISupports>>* objectMap) 284 : mBinaryStream(binaryStream), mObjectMap(objectMap) {} 285 286 NS_FORWARD_SAFE_NSIBINARYOUTPUTSTREAM(mBinaryStream) 287 NS_FORWARD_SAFE_NSIOUTPUTSTREAM(mBinaryStream) 288 289 bool CheckReferences(nsISupports* aObject); 290 291 nsCOMPtr<nsIObjectOutputStream> mBinaryStream; 292 nsTHashSet<nsCOMPtr<nsISupports>>* mObjectMap; 293 }; 294 #endif // DEBUG 295 296 } // namespace scache 297 } // namespace mozilla 298 299 #endif // StartupCache_h_