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