michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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: #include "prio.h" michael@0: #include "pldhash.h" michael@0: #include "nsXPCOMStrings.h" michael@0: #include "mozilla/IOInterposer.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/scache/StartupCache.h" michael@0: michael@0: #include "nsAutoPtr.h" michael@0: #include "nsClassHashtable.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsIClassInfo.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIOutputStream.h" michael@0: #include "nsIStartupCache.h" michael@0: #include "nsIStorageStream.h" michael@0: #include "nsIStreamBufferAccess.h" michael@0: #include "nsIStringStream.h" michael@0: #include "nsISupports.h" michael@0: #include "nsITimer.h" michael@0: #include "nsIZipWriter.h" michael@0: #include "nsIZipReader.h" michael@0: #include "nsWeakReference.h" michael@0: #include "nsZipArchive.h" michael@0: #include "mozilla/Omnijar.h" michael@0: #include "prenv.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "nsIProtocolHandler.h" michael@0: michael@0: #ifdef IS_BIG_ENDIAN michael@0: #define SC_ENDIAN "big" michael@0: #else michael@0: #define SC_ENDIAN "little" michael@0: #endif michael@0: michael@0: #if PR_BYTES_PER_WORD == 4 michael@0: #define SC_WORDSIZE "4" michael@0: #else michael@0: #define SC_WORDSIZE "8" michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: namespace scache { michael@0: michael@0: MOZ_DEFINE_MALLOC_SIZE_OF(StartupCacheMallocSizeOf) michael@0: michael@0: NS_IMETHODIMP michael@0: StartupCache::CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: #define REPORT(_path, _kind, _amount, _desc) \ michael@0: do { \ michael@0: nsresult rv = \ michael@0: aHandleReport->Callback(EmptyCString(), \ michael@0: NS_LITERAL_CSTRING(_path), \ michael@0: _kind, UNITS_BYTES, _amount, \ michael@0: NS_LITERAL_CSTRING(_desc), aData); \ michael@0: NS_ENSURE_SUCCESS(rv, rv); \ michael@0: } while (0) michael@0: michael@0: REPORT("explicit/startup-cache/mapping", KIND_NONHEAP, michael@0: SizeOfMapping(), michael@0: "Memory used to hold the mapping of the startup cache from file. " michael@0: "This memory is likely to be swapped out shortly after start-up."); michael@0: michael@0: REPORT("explicit/startup-cache/data", KIND_HEAP, michael@0: HeapSizeOfIncludingThis(StartupCacheMallocSizeOf), michael@0: "Memory used by the startup cache for things other than the file " michael@0: "mapping."); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static const char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN; michael@0: #if defined(XP_WIN) && defined(MOZ_METRO) michael@0: static const char sMetroStartupCacheName[] = "metroStartupCache." SC_WORDSIZE "." SC_ENDIAN; michael@0: #endif michael@0: michael@0: StartupCache* michael@0: StartupCache::GetSingleton() michael@0: { michael@0: if (!gStartupCache) { michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: return nullptr; michael@0: } michael@0: #ifdef MOZ_B2G michael@0: return nullptr; michael@0: #endif michael@0: michael@0: StartupCache::InitSingleton(); michael@0: } michael@0: michael@0: return StartupCache::gStartupCache; michael@0: } michael@0: michael@0: void michael@0: StartupCache::DeleteSingleton() michael@0: { michael@0: StartupCache::gStartupCache = nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: StartupCache::InitSingleton() michael@0: { michael@0: nsresult rv; michael@0: StartupCache::gStartupCache = new StartupCache(); michael@0: michael@0: rv = StartupCache::gStartupCache->Init(); michael@0: if (NS_FAILED(rv)) { michael@0: StartupCache::gStartupCache = nullptr; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: StaticRefPtr StartupCache::gStartupCache; michael@0: bool StartupCache::gShutdownInitiated; michael@0: bool StartupCache::gIgnoreDiskCache; michael@0: enum StartupCache::TelemetrifyAge StartupCache::gPostFlushAgeAction = StartupCache::IGNORE_AGE; michael@0: michael@0: NS_IMPL_ISUPPORTS(StartupCache, nsIMemoryReporter) michael@0: michael@0: StartupCache::StartupCache() michael@0: : mArchive(nullptr), mStartupWriteInitiated(false), mWriteThread(nullptr) michael@0: { } michael@0: michael@0: StartupCache::~StartupCache() michael@0: { michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: } michael@0: michael@0: // Generally, the in-memory table should be empty here, michael@0: // but an early shutdown means either mTimer didn't run michael@0: // or the write thread is still running. michael@0: WaitOnWriteThread(); michael@0: michael@0: // If we shutdown quickly timer wont have fired. Instead of writing michael@0: // it on the main thread and block the shutdown we simply wont update michael@0: // the startup cache. Always do this if the file doesn't exist since michael@0: // we use it part of the package step. michael@0: if (!mArchive) { michael@0: WriteToDisk(); michael@0: } michael@0: michael@0: UnregisterWeakMemoryReporter(this); michael@0: } michael@0: michael@0: nsresult michael@0: StartupCache::Init() michael@0: { michael@0: // workaround for bug 653936 michael@0: nsCOMPtr jarInitializer(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "jar")); michael@0: michael@0: nsresult rv; michael@0: michael@0: // This allows to override the startup cache filename michael@0: // which is useful from xpcshell, when there is no ProfLDS directory to keep cache in. michael@0: char *env = PR_GetEnv("MOZ_STARTUP_CACHE"); michael@0: if (env) { michael@0: rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, getter_AddRefs(mFile)); michael@0: } else { michael@0: nsCOMPtr file; michael@0: rv = NS_GetSpecialDirectory("ProfLDS", michael@0: getter_AddRefs(file)); michael@0: if (NS_FAILED(rv)) { michael@0: // return silently, this will fail in mochitests's xpcshell process. michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr profDir; michael@0: NS_GetSpecialDirectory("ProfDS", getter_AddRefs(profDir)); michael@0: if (profDir) { michael@0: bool same; michael@0: if (NS_SUCCEEDED(profDir->Equals(file, &same)) && !same) { michael@0: // We no longer store the startup cache in the main profile michael@0: // directory, so we should cleanup the old one. michael@0: if (NS_SUCCEEDED( michael@0: profDir->AppendNative(NS_LITERAL_CSTRING("startupCache")))) { michael@0: profDir->Remove(true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: rv = file->AppendNative(NS_LITERAL_CSTRING("startupCache")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Try to create the directory if it's not there yet michael@0: rv = file->Create(nsIFile::DIRECTORY_TYPE, 0777); michael@0: if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) michael@0: return rv; michael@0: michael@0: #if defined(XP_WIN) && defined(MOZ_METRO) michael@0: if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Metro) { michael@0: rv = file->AppendNative(NS_LITERAL_CSTRING(sMetroStartupCacheName)); michael@0: } else michael@0: #endif michael@0: { michael@0: rv = file->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName)); michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mFile = do_QueryInterface(file); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(mFile, NS_ERROR_UNEXPECTED); michael@0: michael@0: mObserverService = do_GetService("@mozilla.org/observer-service;1"); michael@0: michael@0: if (!mObserverService) { michael@0: NS_WARNING("Could not get observerService."); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: mListener = new StartupCacheListener(); michael@0: rv = mObserverService->AddObserver(mListener, NS_XPCOM_SHUTDOWN_OBSERVER_ID, michael@0: false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mObserverService->AddObserver(mListener, "startupcache-invalidate", michael@0: false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = LoadArchive(RECORD_AGE); michael@0: michael@0: // Sometimes we don't have a cache yet, that's ok. michael@0: // If it's corrupted, just remove it and start over. michael@0: if (gIgnoreDiskCache || (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)) { michael@0: NS_WARNING("Failed to load startupcache file correctly, removing!"); michael@0: InvalidateCache(); michael@0: } michael@0: michael@0: RegisterWeakMemoryReporter(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * LoadArchive can be called from the main thread or while reloading cache on write thread. michael@0: */ michael@0: nsresult michael@0: StartupCache::LoadArchive(enum TelemetrifyAge flag) michael@0: { michael@0: if (gIgnoreDiskCache) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: bool exists; michael@0: mArchive = nullptr; michael@0: nsresult rv = mFile->Exists(&exists); michael@0: if (NS_FAILED(rv) || !exists) michael@0: return NS_ERROR_FILE_NOT_FOUND; michael@0: michael@0: mArchive = new nsZipArchive(); michael@0: rv = mArchive->OpenArchive(mFile); michael@0: if (NS_FAILED(rv) || flag == IGNORE_AGE) michael@0: return rv; michael@0: michael@0: nsCString comment; michael@0: if (!mArchive->GetComment(comment)) { michael@0: return rv; michael@0: } michael@0: michael@0: const char *data; michael@0: size_t len = NS_CStringGetData(comment, &data); michael@0: PRTime creationStamp; michael@0: // We might not have a comment if the startup cache file was created michael@0: // before we started recording creation times in the comment. michael@0: if (len == sizeof(creationStamp)) { michael@0: memcpy(&creationStamp, data, len); michael@0: PRTime current = PR_Now(); michael@0: int64_t diff = current - creationStamp; michael@0: michael@0: // We can't use AccumulateTimeDelta here because we have no way of michael@0: // reifying a TimeStamp from creationStamp. michael@0: int64_t usec_per_hour = PR_USEC_PER_SEC * int64_t(3600); michael@0: int64_t hour_diff = (diff + usec_per_hour - 1) / usec_per_hour; michael@0: mozilla::Telemetry::Accumulate(Telemetry::STARTUP_CACHE_AGE_HOURS, michael@0: hour_diff); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: nsresult michael@0: GetBufferFromZipArchive(nsZipArchive *zip, bool doCRC, const char* id, michael@0: char** outbuf, uint32_t* length) michael@0: { michael@0: if (!zip) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsZipItemPtr zipItem(zip, id, doCRC); michael@0: if (!zipItem) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *outbuf = zipItem.Forget(); michael@0: *length = zipItem.Length(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: // NOTE: this will not find a new entry until it has been written to disk! michael@0: // Consumer should take ownership of the resulting buffer. michael@0: nsresult michael@0: StartupCache::GetBuffer(const char* id, char** outbuf, uint32_t* length) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread"); michael@0: WaitOnWriteThread(); michael@0: if (!mStartupWriteInitiated) { michael@0: CacheEntry* entry; michael@0: nsDependentCString idStr(id); michael@0: mTable.Get(idStr, &entry); michael@0: if (entry) { michael@0: *outbuf = new char[entry->size]; michael@0: memcpy(*outbuf, entry->data, entry->size); michael@0: *length = entry->size; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: nsresult rv = GetBufferFromZipArchive(mArchive, true, id, outbuf, length); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return rv; michael@0: michael@0: nsRefPtr omnijar = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); michael@0: // no need to checksum omnijarred entries michael@0: rv = GetBufferFromZipArchive(omnijar, false, id, outbuf, length); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return rv; michael@0: michael@0: omnijar = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); michael@0: // no need to checksum omnijarred entries michael@0: return GetBufferFromZipArchive(omnijar, false, id, outbuf, length); michael@0: } michael@0: michael@0: // Makes a copy of the buffer, client retains ownership of inbuf. michael@0: nsresult michael@0: StartupCache::PutBuffer(const char* id, const char* inbuf, uint32_t len) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread"); michael@0: WaitOnWriteThread(); michael@0: if (StartupCache::gShutdownInitiated) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsAutoArrayPtr data(new char[len]); michael@0: memcpy(data, inbuf, len); michael@0: michael@0: nsDependentCString idStr(id); michael@0: // Cache it for now, we'll write all together later. michael@0: CacheEntry* entry; michael@0: michael@0: #ifdef DEBUG michael@0: mTable.Get(idStr, &entry); michael@0: NS_ASSERTION(entry == nullptr, "Existing entry in StartupCache."); michael@0: michael@0: if (mArchive) { michael@0: nsZipItem* zipItem = mArchive->GetItem(id); michael@0: NS_ASSERTION(zipItem == nullptr, "Existing entry in disk StartupCache."); michael@0: } michael@0: #endif michael@0: michael@0: entry = new CacheEntry(data.forget(), len); michael@0: mTable.Put(idStr, entry); michael@0: return ResetStartupWriteTimer(); michael@0: } michael@0: michael@0: size_t michael@0: StartupCache::SizeOfMapping() michael@0: { michael@0: return mArchive ? mArchive->SizeOfMapping() : 0; michael@0: } michael@0: michael@0: size_t michael@0: StartupCache::HeapSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) michael@0: { michael@0: // This function could measure more members, but they haven't been found by michael@0: // DMD to be significant. They can be added later if necessary. michael@0: return aMallocSizeOf(this) + michael@0: mTable.SizeOfExcludingThis(SizeOfEntryExcludingThis, aMallocSizeOf); michael@0: } michael@0: michael@0: /* static */ size_t michael@0: StartupCache::SizeOfEntryExcludingThis(const nsACString& key, const nsAutoPtr& data, michael@0: mozilla::MallocSizeOf mallocSizeOf, void *) michael@0: { michael@0: return data->SizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: struct CacheWriteHolder michael@0: { michael@0: nsCOMPtr writer; michael@0: nsCOMPtr stream; michael@0: PRTime time; michael@0: }; michael@0: michael@0: PLDHashOperator michael@0: CacheCloseHelper(const nsACString& key, nsAutoPtr& data, michael@0: void* closure) michael@0: { michael@0: nsresult rv; michael@0: michael@0: CacheWriteHolder* holder = (CacheWriteHolder*) closure; michael@0: nsIStringInputStream* stream = holder->stream; michael@0: nsIZipWriter* writer = holder->writer; michael@0: michael@0: stream->ShareData(data->data, data->size); michael@0: michael@0: #ifdef DEBUG michael@0: bool hasEntry; michael@0: rv = writer->HasEntry(key, &hasEntry); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv) && hasEntry == false, michael@0: "Existing entry in disk StartupCache."); michael@0: #endif michael@0: rv = writer->AddEntryStream(key, holder->time, true, stream, false); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("cache entry deleted but not written to disk."); michael@0: } michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * WriteToDisk writes the cache out to disk. Callers of WriteToDisk need to call WaitOnWriteThread michael@0: * to make sure there isn't a write happening on another thread michael@0: */ michael@0: void michael@0: StartupCache::WriteToDisk() michael@0: { michael@0: nsresult rv; michael@0: mStartupWriteInitiated = true; michael@0: michael@0: if (mTable.Count() == 0) michael@0: return; michael@0: michael@0: nsCOMPtr zipW = do_CreateInstance("@mozilla.org/zipwriter;1"); michael@0: if (!zipW) michael@0: return; michael@0: michael@0: rv = zipW->Open(mFile, PR_RDWR | PR_CREATE_FILE); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("could not open zipfile for write"); michael@0: return; michael@0: } michael@0: michael@0: // If we didn't have an mArchive member, that means that we failed to michael@0: // open the startup cache for reading. Therefore, we need to record michael@0: // the time of creation in a zipfile comment; this will be useful for michael@0: // Telemetry statistics. michael@0: PRTime now = PR_Now(); michael@0: if (!mArchive) { michael@0: nsCString comment; michael@0: comment.Assign((char *)&now, sizeof(now)); michael@0: zipW->SetComment(comment); michael@0: } michael@0: michael@0: nsCOMPtr stream michael@0: = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Couldn't create string input stream."); michael@0: return; michael@0: } michael@0: michael@0: CacheWriteHolder holder; michael@0: holder.stream = stream; michael@0: holder.writer = zipW; michael@0: holder.time = now; michael@0: michael@0: mTable.Enumerate(CacheCloseHelper, &holder); michael@0: michael@0: // Close the archive so Windows doesn't choke. michael@0: mArchive = nullptr; michael@0: zipW->Close(); michael@0: michael@0: // We succesfully wrote the archive to disk; mark the disk file as trusted michael@0: gIgnoreDiskCache = false; michael@0: michael@0: // Our reader's view of the archive is outdated now, reload it. michael@0: LoadArchive(gPostFlushAgeAction); michael@0: michael@0: return; michael@0: } michael@0: michael@0: void michael@0: StartupCache::InvalidateCache() michael@0: { michael@0: WaitOnWriteThread(); michael@0: mTable.Clear(); michael@0: mArchive = nullptr; michael@0: nsresult rv = mFile->Remove(false); michael@0: if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && michael@0: rv != NS_ERROR_FILE_NOT_FOUND) { michael@0: gIgnoreDiskCache = true; michael@0: mozilla::Telemetry::Accumulate(Telemetry::STARTUP_CACHE_INVALID, true); michael@0: return; michael@0: } michael@0: gIgnoreDiskCache = false; michael@0: LoadArchive(gPostFlushAgeAction); michael@0: } michael@0: michael@0: void michael@0: StartupCache::IgnoreDiskCache() michael@0: { michael@0: gIgnoreDiskCache = true; michael@0: if (gStartupCache) michael@0: gStartupCache->InvalidateCache(); michael@0: } michael@0: michael@0: /* michael@0: * WaitOnWriteThread() is called from a main thread to wait for the worker michael@0: * thread to finish. However since the same code is used in the worker thread and michael@0: * main thread, the worker thread can also call WaitOnWriteThread() which is a no-op. michael@0: */ michael@0: void michael@0: StartupCache::WaitOnWriteThread() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Startup cache should only wait for io thread on main thread"); michael@0: if (!mWriteThread || mWriteThread == PR_GetCurrentThread()) michael@0: return; michael@0: michael@0: PR_JoinThread(mWriteThread); michael@0: mWriteThread = nullptr; michael@0: } michael@0: michael@0: void michael@0: StartupCache::ThreadedWrite(void *aClosure) michael@0: { michael@0: PR_SetCurrentThreadName("StartupCache"); michael@0: mozilla::IOInterposer::RegisterCurrentThread(); michael@0: /* michael@0: * It is safe to use the pointer passed in aClosure to reference the michael@0: * StartupCache object because the thread's lifetime is tightly coupled to michael@0: * the lifetime of the StartupCache object; this thread is joined in the michael@0: * StartupCache destructor, guaranteeing that this function runs if and only michael@0: * if the StartupCache object is valid. michael@0: */ michael@0: StartupCache* startupCacheObj = static_cast(aClosure); michael@0: startupCacheObj->WriteToDisk(); michael@0: mozilla::IOInterposer::UnregisterCurrentThread(); michael@0: } michael@0: michael@0: /* michael@0: * The write-thread is spawned on a timeout(which is reset with every write). This michael@0: * can avoid a slow shutdown. After writing out the cache, the zipreader is michael@0: * reloaded on the worker thread. michael@0: */ michael@0: void michael@0: StartupCache::WriteTimeout(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: /* michael@0: * It is safe to use the pointer passed in aClosure to reference the michael@0: * StartupCache object because the timer's lifetime is tightly coupled to michael@0: * the lifetime of the StartupCache object; this timer is canceled in the michael@0: * StartupCache destructor, guaranteeing that this function runs if and only michael@0: * if the StartupCache object is valid. michael@0: */ michael@0: StartupCache* startupCacheObj = static_cast(aClosure); michael@0: startupCacheObj->mWriteThread = PR_CreateThread(PR_USER_THREAD, michael@0: StartupCache::ThreadedWrite, michael@0: startupCacheObj, michael@0: PR_PRIORITY_NORMAL, michael@0: PR_GLOBAL_THREAD, michael@0: PR_JOINABLE_THREAD, michael@0: 0); michael@0: } michael@0: michael@0: // We don't want to refcount StartupCache, so we'll just michael@0: // hold a ref to this and pass it to observerService instead. michael@0: NS_IMPL_ISUPPORTS(StartupCacheListener, nsIObserver) michael@0: michael@0: nsresult michael@0: StartupCacheListener::Observe(nsISupports *subject, const char* topic, const char16_t* data) michael@0: { michael@0: StartupCache* sc = StartupCache::GetSingleton(); michael@0: if (!sc) michael@0: return NS_OK; michael@0: michael@0: if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { michael@0: // Do not leave the thread running past xpcom shutdown michael@0: sc->WaitOnWriteThread(); michael@0: StartupCache::gShutdownInitiated = true; michael@0: } else if (strcmp(topic, "startupcache-invalidate") == 0) { michael@0: sc->InvalidateCache(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: StartupCache::GetDebugObjectOutputStream(nsIObjectOutputStream* aStream, michael@0: nsIObjectOutputStream** aOutStream) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aStream); michael@0: #ifdef DEBUG michael@0: StartupCacheDebugOutputStream* stream michael@0: = new StartupCacheDebugOutputStream(aStream, &mWriteObjectMap); michael@0: NS_ADDREF(*aOutStream = stream); michael@0: #else michael@0: NS_ADDREF(*aOutStream = aStream); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: StartupCache::ResetStartupWriteTimer() michael@0: { michael@0: mStartupWriteInitiated = false; michael@0: nsresult rv; michael@0: if (!mTimer) michael@0: mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); michael@0: else michael@0: rv = mTimer->Cancel(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // Wait for 10 seconds, then write out the cache. michael@0: mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, this, 60000, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: StartupCache::RecordAgesAlways() michael@0: { michael@0: gPostFlushAgeAction = RECORD_AGE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // StartupCacheDebugOutputStream implementation michael@0: #ifdef DEBUG michael@0: NS_IMPL_ISUPPORTS(StartupCacheDebugOutputStream, nsIObjectOutputStream, michael@0: nsIBinaryOutputStream, nsIOutputStream) michael@0: michael@0: bool michael@0: StartupCacheDebugOutputStream::CheckReferences(nsISupports* aObject) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr classInfo = do_QueryInterface(aObject); michael@0: if (!classInfo) { michael@0: NS_ERROR("aObject must implement nsIClassInfo"); michael@0: return false; michael@0: } michael@0: michael@0: uint32_t flags; michael@0: rv = classInfo->GetFlags(&flags); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: if (flags & nsIClassInfo::SINGLETON) michael@0: return true; michael@0: michael@0: nsISupportsHashKey* key = mObjectMap->GetEntry(aObject); michael@0: if (key) { michael@0: NS_ERROR("non-singleton aObject is referenced multiple times in this" michael@0: "serialization, we don't support that."); michael@0: return false; michael@0: } michael@0: michael@0: mObjectMap->PutEntry(aObject); michael@0: return true; michael@0: } michael@0: michael@0: // nsIObjectOutputStream implementation michael@0: nsresult michael@0: StartupCacheDebugOutputStream::WriteObject(nsISupports* aObject, bool aIsStrongRef) michael@0: { michael@0: nsCOMPtr rootObject(do_QueryInterface(aObject)); michael@0: michael@0: NS_ASSERTION(rootObject.get() == aObject, michael@0: "bad call to WriteObject -- call WriteCompoundObject!"); michael@0: bool check = CheckReferences(aObject); michael@0: NS_ENSURE_TRUE(check, NS_ERROR_FAILURE); michael@0: return mBinaryStream->WriteObject(aObject, aIsStrongRef); michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheDebugOutputStream::WriteSingleRefObject(nsISupports* aObject) michael@0: { michael@0: nsCOMPtr rootObject(do_QueryInterface(aObject)); michael@0: michael@0: NS_ASSERTION(rootObject.get() == aObject, michael@0: "bad call to WriteSingleRefObject -- call WriteCompoundObject!"); michael@0: bool check = CheckReferences(aObject); michael@0: NS_ENSURE_TRUE(check, NS_ERROR_FAILURE); michael@0: return mBinaryStream->WriteSingleRefObject(aObject); michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheDebugOutputStream::WriteCompoundObject(nsISupports* aObject, michael@0: const nsIID& aIID, michael@0: bool aIsStrongRef) michael@0: { michael@0: nsCOMPtr rootObject(do_QueryInterface(aObject)); michael@0: michael@0: nsCOMPtr roundtrip; michael@0: rootObject->QueryInterface(aIID, getter_AddRefs(roundtrip)); michael@0: NS_ASSERTION(roundtrip.get() == aObject, michael@0: "bad aggregation or multiple inheritance detected by call to " michael@0: "WriteCompoundObject!"); michael@0: michael@0: bool check = CheckReferences(aObject); michael@0: NS_ENSURE_TRUE(check, NS_ERROR_FAILURE); michael@0: return mBinaryStream->WriteCompoundObject(aObject, aIID, aIsStrongRef); michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheDebugOutputStream::WriteID(nsID const& aID) michael@0: { michael@0: return mBinaryStream->WriteID(aID); michael@0: } michael@0: michael@0: char* michael@0: StartupCacheDebugOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) michael@0: { michael@0: return mBinaryStream->GetBuffer(aLength, aAlignMask); michael@0: } michael@0: michael@0: void michael@0: StartupCacheDebugOutputStream::PutBuffer(char* aBuffer, uint32_t aLength) michael@0: { michael@0: mBinaryStream->PutBuffer(aBuffer, aLength); michael@0: } michael@0: #endif //DEBUG michael@0: michael@0: StartupCacheWrapper* StartupCacheWrapper::gStartupCacheWrapper = nullptr; michael@0: michael@0: NS_IMPL_ISUPPORTS(StartupCacheWrapper, nsIStartupCache) michael@0: michael@0: StartupCacheWrapper* StartupCacheWrapper::GetSingleton() michael@0: { michael@0: if (!gStartupCacheWrapper) michael@0: gStartupCacheWrapper = new StartupCacheWrapper(); michael@0: michael@0: NS_ADDREF(gStartupCacheWrapper); michael@0: return gStartupCacheWrapper; michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheWrapper::GetBuffer(const char* id, char** outbuf, uint32_t* length) michael@0: { michael@0: StartupCache* sc = StartupCache::GetSingleton(); michael@0: if (!sc) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: return sc->GetBuffer(id, outbuf, length); michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheWrapper::PutBuffer(const char* id, const char* inbuf, uint32_t length) michael@0: { michael@0: StartupCache* sc = StartupCache::GetSingleton(); michael@0: if (!sc) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: return sc->PutBuffer(id, inbuf, length); michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheWrapper::InvalidateCache() michael@0: { michael@0: StartupCache* sc = StartupCache::GetSingleton(); michael@0: if (!sc) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: sc->InvalidateCache(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheWrapper::IgnoreDiskCache() michael@0: { michael@0: StartupCache::IgnoreDiskCache(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheWrapper::GetDebugObjectOutputStream(nsIObjectOutputStream* stream, michael@0: nsIObjectOutputStream** outStream) michael@0: { michael@0: StartupCache* sc = StartupCache::GetSingleton(); michael@0: if (!sc) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: return sc->GetDebugObjectOutputStream(stream, outStream); michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheWrapper::StartupWriteComplete(bool *complete) michael@0: { michael@0: StartupCache* sc = StartupCache::GetSingleton(); michael@0: if (!sc) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: sc->WaitOnWriteThread(); michael@0: *complete = sc->mStartupWriteInitiated && sc->mTable.Count() == 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheWrapper::ResetStartupWriteTimer() michael@0: { michael@0: StartupCache* sc = StartupCache::GetSingleton(); michael@0: return sc ? sc->ResetStartupWriteTimer() : NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheWrapper::GetObserver(nsIObserver** obv) { michael@0: StartupCache* sc = StartupCache::GetSingleton(); michael@0: if (!sc) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: NS_ADDREF(*obv = sc->mListener); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: StartupCacheWrapper::RecordAgesAlways() { michael@0: StartupCache *sc = StartupCache::GetSingleton(); michael@0: return sc ? sc->RecordAgesAlways() : NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: } // namespace scache michael@0: } // namespace mozilla