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 StartupCache_h_ michael@0: #define StartupCache_h_ michael@0: michael@0: #include "nsClassHashtable.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsZipArchive.h" michael@0: #include "nsIStartupCache.h" michael@0: #include "nsITimer.h" michael@0: #include "nsIMemoryReporter.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsIOutputStream.h" michael@0: #include "nsIFile.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: michael@0: /** michael@0: * The StartupCache is a persistent cache of simple key-value pairs, michael@0: * where the keys are null-terminated c-strings and the values are michael@0: * arbitrary data, passed as a (char*, size) tuple. michael@0: * michael@0: * Clients should use the GetSingleton() static method to access the cache. It michael@0: * will be available from the end of XPCOM init (NS_InitXPCOM3 in nsXPComInit.cpp), michael@0: * until XPCOM shutdown begins. The GetSingleton() method will return null if the cache michael@0: * is unavailable. The cache is only provided for libxul builds -- michael@0: * it will fail to link in non-libxul builds. The XPCOM interface is provided michael@0: * only to allow compiled-code tests; clients should avoid using it. michael@0: * michael@0: * The API provided is very simple: GetBuffer() returns a buffer that was previously michael@0: * stored in the cache (if any), and PutBuffer() inserts a buffer into the cache. michael@0: * GetBuffer returns a new buffer, and the caller must take ownership of it. michael@0: * PutBuffer will assert if the client attempts to insert a buffer with the same name as michael@0: * an existing entry. The cache makes a copy of the passed-in buffer, so client michael@0: * retains ownership. michael@0: * michael@0: * InvalidateCache() may be called if a client suspects data corruption michael@0: * or wishes to invalidate for any other reason. This will remove all existing cache data. michael@0: * Additionally, the static method IgnoreDiskCache() can be called if it is michael@0: * believed that the on-disk cache file is itself corrupt. This call implicitly michael@0: * calls InvalidateCache (if the singleton has been initialized) to ensure any michael@0: * data already read from disk is discarded. The cache will not load data from michael@0: * the disk file until a successful write occurs. michael@0: * michael@0: * Finally, getDebugObjectOutputStream() allows debug code to wrap an objectstream michael@0: * with a debug objectstream, to check for multiply-referenced objects. These will michael@0: * generally fail to deserialize correctly, unless they are stateless singletons or the michael@0: * client maintains their own object data map for deserialization. michael@0: * michael@0: * Writes before the final-ui-startup notification are placed in an intermediate michael@0: * cache in memory, then written out to disk at a later time, to get writes off the michael@0: * startup path. In any case, clients should not rely on being able to GetBuffer() michael@0: * data that is written to the cache, since it may not have been written to disk or michael@0: * another client may have invalidated the cache. In other words, it should be used as michael@0: * a cache only, and not a reliable persistent store. michael@0: * michael@0: * Some utility functions are provided in StartupCacheUtils. These functions wrap the michael@0: * buffers into object streams, which may be useful for serializing objects. Note michael@0: * the above caution about multiply-referenced objects, though -- the streams are just michael@0: * as 'dumb' as the underlying buffers about multiply-referenced objects. They just michael@0: * provide some convenience in writing out data. michael@0: */ michael@0: michael@0: namespace mozilla { michael@0: michael@0: namespace scache { michael@0: michael@0: struct CacheEntry michael@0: { michael@0: nsAutoArrayPtr data; michael@0: uint32_t size; michael@0: michael@0: CacheEntry() : data(nullptr), size(0) { } michael@0: michael@0: // Takes possession of buf michael@0: CacheEntry(char* buf, uint32_t len) : data(buf), size(len) { } michael@0: michael@0: ~CacheEntry() michael@0: { michael@0: } michael@0: michael@0: size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { michael@0: return mallocSizeOf(data); michael@0: } michael@0: }; michael@0: michael@0: // We don't want to refcount StartupCache, and ObserverService wants to michael@0: // refcount its listeners, so we'll let it refcount this instead. michael@0: class StartupCacheListener MOZ_FINAL : public nsIObserver michael@0: { michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: }; michael@0: michael@0: class StartupCache : public nsIMemoryReporter michael@0: { michael@0: michael@0: friend class StartupCacheListener; michael@0: friend class StartupCacheWrapper; michael@0: michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIMEMORYREPORTER michael@0: michael@0: // StartupCache methods. See above comments for a more detailed description. michael@0: michael@0: // Returns a buffer that was previously stored, caller takes ownership. michael@0: nsresult GetBuffer(const char* id, char** outbuf, uint32_t* length); michael@0: michael@0: // Stores a buffer. Caller keeps ownership, we make a copy. michael@0: nsresult PutBuffer(const char* id, const char* inbuf, uint32_t length); michael@0: michael@0: // Removes the cache file. michael@0: void InvalidateCache(); michael@0: michael@0: // Signal that data should not be loaded from the cache file michael@0: static void IgnoreDiskCache(); michael@0: michael@0: // In DEBUG builds, returns a stream that will attempt to check for michael@0: // and disallow multiple writes of the same object. michael@0: nsresult GetDebugObjectOutputStream(nsIObjectOutputStream* aStream, michael@0: nsIObjectOutputStream** outStream); michael@0: michael@0: nsresult RecordAgesAlways(); michael@0: michael@0: static StartupCache* GetSingleton(); michael@0: static void DeleteSingleton(); michael@0: michael@0: // This measures all the heap memory used by the StartupCache, i.e. it michael@0: // excludes the mapping. michael@0: size_t HeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); michael@0: michael@0: size_t SizeOfMapping(); michael@0: michael@0: private: michael@0: StartupCache(); michael@0: virtual ~StartupCache(); michael@0: michael@0: enum TelemetrifyAge { michael@0: IGNORE_AGE = 0, michael@0: RECORD_AGE = 1 michael@0: }; michael@0: static enum TelemetrifyAge gPostFlushAgeAction; michael@0: michael@0: nsresult LoadArchive(enum TelemetrifyAge flag); michael@0: nsresult Init(); michael@0: void WriteToDisk(); michael@0: nsresult ResetStartupWriteTimer(); michael@0: void WaitOnWriteThread(); michael@0: michael@0: static nsresult InitSingleton(); michael@0: static void WriteTimeout(nsITimer *aTimer, void *aClosure); michael@0: static void ThreadedWrite(void *aClosure); michael@0: michael@0: static size_t SizeOfEntryExcludingThis(const nsACString& key, michael@0: const nsAutoPtr& data, michael@0: mozilla::MallocSizeOf mallocSizeOf, michael@0: void *); michael@0: michael@0: nsClassHashtable mTable; michael@0: nsRefPtr mArchive; michael@0: nsCOMPtr mFile; michael@0: michael@0: nsCOMPtr mObserverService; michael@0: nsRefPtr mListener; michael@0: nsCOMPtr mTimer; michael@0: michael@0: bool mStartupWriteInitiated; michael@0: michael@0: static StaticRefPtr gStartupCache; michael@0: static bool gShutdownInitiated; michael@0: static bool gIgnoreDiskCache; michael@0: PRThread *mWriteThread; michael@0: #ifdef DEBUG michael@0: nsTHashtable mWriteObjectMap; michael@0: #endif michael@0: }; michael@0: michael@0: // This debug outputstream attempts to detect if clients are writing multiple michael@0: // references to the same object. We only support that if that object michael@0: // is a singleton. michael@0: #ifdef DEBUG michael@0: class StartupCacheDebugOutputStream MOZ_FINAL michael@0: : public nsIObjectOutputStream michael@0: { michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBJECTOUTPUTSTREAM michael@0: michael@0: StartupCacheDebugOutputStream (nsIObjectOutputStream* binaryStream, michael@0: nsTHashtable* objectMap) michael@0: : mBinaryStream(binaryStream), mObjectMap(objectMap) { } michael@0: michael@0: NS_FORWARD_SAFE_NSIBINARYOUTPUTSTREAM(mBinaryStream) michael@0: NS_FORWARD_SAFE_NSIOUTPUTSTREAM(mBinaryStream) michael@0: michael@0: bool CheckReferences(nsISupports* aObject); michael@0: michael@0: nsCOMPtr mBinaryStream; michael@0: nsTHashtable *mObjectMap; michael@0: }; michael@0: #endif // DEBUG michael@0: michael@0: // XPCOM wrapper interface provided for tests only. michael@0: #define NS_STARTUPCACHE_CID \ michael@0: {0xae4505a9, 0x87ab, 0x477c, \ michael@0: {0xb5, 0x77, 0xf9, 0x23, 0x57, 0xed, 0xa8, 0x84}} michael@0: // contract id: "@mozilla.org/startupcache/cache;1" michael@0: michael@0: class StartupCacheWrapper MOZ_FINAL michael@0: : public nsIStartupCache michael@0: { michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSISTARTUPCACHE michael@0: michael@0: static StartupCacheWrapper* GetSingleton(); michael@0: static StartupCacheWrapper *gStartupCacheWrapper; michael@0: }; michael@0: michael@0: } // namespace scache michael@0: } // namespace mozilla michael@0: #endif //StartupCache_h_