author | bhsieh@mozilla.com |
Thu, 12 Aug 2010 12:37:44 -0700 | |
changeset 51307 | b8e409a955c1105ae26ea1c402c0116b4708b585 |
parent 51306 | 4747f09a62e399f5ac0c2bcb16736a047c03b996 |
child 51308 | 55dbe9d5910640b9b12da95f3053d1d374724fe7 |
push id | 15277 |
push user | dwitte@mozilla.com |
push date | Tue, 24 Aug 2010 04:18:33 +0000 |
treeherder | mozilla-central@49dc8d6901a7 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | dwitte, bsmedberg, bsmedberg |
bugs | 520309 |
milestone | 2.0b5pre |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/modules/libjar/nsZipArchive.cpp +++ b/modules/libjar/nsZipArchive.cpp @@ -731,16 +731,22 @@ PRUint8* nsZipArchive::GetData(nsZipItem // -- check if there is enough source data in the file if (offset + aItem->Size() > len) return nsnull; return data + offset; } +PRBool +nsZipArchive::CheckCRC(nsZipItem* aItem, PRUint8* aItemData) { + PRUint32 crc = crc32(0, (const unsigned char*)aItemData, aItem->Size()); + return crc == aItem->CRC32(); +} + //------------------------------------------ // nsZipArchive constructor and destructor //------------------------------------------ nsZipArchive::nsZipArchive() : mBuiltSynthetics(false) { MOZ_COUNT_CTOR(nsZipArchive);
--- a/modules/libjar/nsZipArchive.h +++ b/modules/libjar/nsZipArchive.h @@ -191,16 +191,18 @@ public: /** * Get pointer to the data of the item. * @param aItem Pointer to nsZipItem * reutrns null when zip file is corrupt. */ PRUint8* GetData(nsZipItem* aItem); + PRBool CheckCRC(nsZipItem* aItem, PRUint8* aData); + private: //--- private members --- nsZipItem* mFiles[ZIP_TABSIZE]; PLArenaPool mArena; // Whether we synthesized the directory entries bool mBuiltSynthetics;
new file mode 100644 --- /dev/null +++ b/startupcache/Makefile.in @@ -0,0 +1,82 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is Startup Cache. +# +# The Initial Developer of the Original Code is +# Mozilla Corporation. +# Portions created by the Initial Developer are Copyright (C) 2010 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Benedict Hsieh <bhsieh@mozilla.com> +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + + +DEPTH = .. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +DIRS = $(NULL) + +ifdef ENABLE_TESTS +TOOL_DIRS += test +endif + +MODULE = startupcache +MODULE_NAME = StartupCacheModule +LIBRARY_NAME = startupcache +SHORT_LIBNAME = scache +EXPORT_LIBRARY = 1 +LIBXUL_LIBRARY = 1 +IS_COMPONENT = 1 +GRE_MODULE = 1 + +CPPSRCS = StartupCache.cpp \ + StartupCacheUtils.cpp \ + StartupCacheModule.cpp \ + $(NULL) + +EXPORTS_NAMESPACES = mozilla/scache +EXPORTS_mozilla/scache = StartupCache.h \ + StartupCacheUtils.h \ + $(NULL) + +XPIDLSRCS = nsIStartupCache.idl \ + $(NULL) + +EXTRA_DSO_LDOPTS += \ + $(LIBS_DIR) \ + $(ZLIB_LIBS) \ + $(MOZ_COMPONENT_LIBS) \ + $(NULL) + + + +include $(topsrcdir)/config/rules.mk
new file mode 100644 --- /dev/null +++ b/startupcache/StartupCache.cpp @@ -0,0 +1,633 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Startup Cache. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation <http://www.mozilla.org/>. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Benedict Hsieh <bhsieh@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "prio.h" +#include "prtypes.h" +#include "pldhash.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 "nsILocalFile.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" + +#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 { + +static const char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN; +static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); + +StartupCache* +StartupCache::GetSingleton() +{ + if (!gStartupCache) + StartupCache::InitSingleton(); + + return StartupCache::gStartupCache; +} + +void +StartupCache::DeleteSingleton() +{ + delete StartupCache::gStartupCache; +} + +nsresult +StartupCache::InitSingleton() +{ + nsresult rv; + StartupCache::gStartupCache = new StartupCache(); + + rv = StartupCache::gStartupCache->Init(); + if (NS_FAILED(rv)) { + delete StartupCache::gStartupCache; + } + return rv; +} + +StartupCache* StartupCache::gStartupCache; +PRBool StartupCache::gShutdownInitiated; + +StartupCache::StartupCache() + : mArchive(NULL), mStartupWriteInitiated(PR_FALSE) { } + +StartupCache::~StartupCache() +{ + if (mTable.Count() > 0) { + NS_WARNING("Shutting down with entries in startupcache, these will be lost"); + } + gStartupCache = nsnull; +} + +nsresult +StartupCache::Init() +{ + nsresult rv; + mTable.Init(); +#ifdef DEBUG + mWriteObjectMap.Init(); +#endif + + mZipW = do_CreateInstance("@mozilla.org/zipwriter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIFile> file; + rv = NS_GetSpecialDirectory("ProfLDS", + getter_AddRefs(file)); + if (NS_FAILED(rv)) { + // return silently, this will fail in mochitests's xpcshell process. + return rv; + } + + 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; + + 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, + PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + rv = LoadArchive(); + + // Sometimes we don't have a cache yet, that's ok. + // If it's corrupted, just remove it and start over. + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { + NS_WARNING("Failed to load startupcache file correctly, removing!"); + InvalidateCache(); + } + + mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + // Wait for 10 seconds, then write out the cache. + rv = mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, this, 10000, + nsITimer::TYPE_ONE_SHOT); + + return rv; +} + +nsresult +StartupCache::LoadArchive() +{ + PRBool exists; + mArchive = NULL; + nsresult rv = mFile->Exists(&exists); + if (NS_FAILED(rv) || !exists) + return NS_ERROR_FILE_NOT_FOUND; + + mArchive = new nsZipArchive(); + return mArchive->OpenArchive(mFile); +} + +// 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, PRUint32* length) +{ + PRBool exists; + char* data = NULL; + PRUint32 len; + + if (!mStartupWriteInitiated) { + CacheEntry* entry; + nsDependentCString idStr(id); + mTable.Get(idStr, &entry); + if (entry) { + data = entry->data; + len = entry->size; + } + } + + if (!data && mArchive) { + nsZipItem* zipItem = mArchive->GetItem(id); + if (zipItem) { + PRUint8* itemData = mArchive->GetData(zipItem); + if (!itemData || !mArchive->CheckCRC(zipItem, itemData)) { + NS_WARNING("StartupCache file corrupted!"); + InvalidateCache(); + return NS_ERROR_FILE_CORRUPTED; + } + + len = zipItem->Size(); + data = (char*) itemData; + } + } + + if (data) { + *outbuf = new char[len]; + memcpy(*outbuf, data, len); + *length = len; + return NS_OK; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +// Makes a copy of the buffer, client retains ownership of inbuf. +nsresult +StartupCache::PutBuffer(const char* id, const char* inbuf, PRUint32 len) +{ + nsresult rv; + + if (StartupCache::gShutdownInitiated) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsAutoArrayPtr<char> data(new char[len]); + memcpy(data, inbuf, len); + + nsDependentCString idStr(id); + if (!mStartupWriteInitiated) { + // Cache it for now, we'll write all together later. + CacheEntry* entry; + +#ifdef DEBUG + mTable.Get(idStr, &entry); + NS_ASSERTION(entry == nsnull, "Existing entry in StartupCache."); + + if (mArchive) { + nsZipItem* zipItem = mArchive->GetItem(id); + NS_ASSERTION(zipItem == nsnull, "Existing entry in disk StartupCache."); + } +#endif + + entry = new CacheEntry(data.forget(), len); + mTable.Put(idStr, entry); + return NS_OK; + } + + rv = mZipW->Open(mFile, PR_RDWR | PR_CREATE_FILE); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX We need to think about whether to write this out every time, + // or somehow detect a good time to write. We need to finish writing + // before shutdown though, and writing also requires a reload of the + // reader's archive, which probably can't handle having the underlying + // file change underneath it. Potentially could reload on the next + // read request, if this is a problem. See Bug 586859. +#ifdef DEBUG + PRBool hasEntry; + rv = mZipW->HasEntry(idStr, &hasEntry); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(hasEntry == PR_FALSE, "Existing entry in disk StartupCache."); +#endif + + nsCOMPtr<nsIStringInputStream> stream + = do_CreateInstance("@mozilla.org/io/string-input-stream;1", + &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = stream->AdoptData(data, len); + NS_ENSURE_SUCCESS(rv, rv); + data.forget(); + + rv = mZipW->AddEntryStream(idStr, 0, 0, stream, false); + NS_ENSURE_SUCCESS(rv, rv); + + // Close the archive so Windows doesn't choke. + mArchive = NULL; + rv = mZipW->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + // our reader's view of the archive is outdated now, reload it. + return LoadArchive(); +} + +struct CacheWriteHolder +{ + nsCOMPtr<nsIZipWriter> writer; + nsCOMPtr<nsIStringInputStream> stream; +}; + +PLDHashOperator +CacheCloseHelper(const nsACString& key, nsAutoPtr<CacheEntry>& 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 + PRBool hasEntry; + rv = writer->HasEntry(key, &hasEntry); + NS_ASSERTION(NS_SUCCEEDED(rv) && hasEntry == PR_FALSE, + "Existing entry in disk StartupCache."); +#endif + rv = writer->AddEntryStream(key, 0, 0, stream, false); + + if (NS_FAILED(rv)) { + NS_WARNING("cache entry deleted but not written to disk."); + } + return PL_DHASH_REMOVE; +} + +void +StartupCache::WriteToDisk() +{ + nsresult rv; + mStartupWriteInitiated = PR_TRUE; + + if (gShutdownInitiated) { + NS_WARNING("xpcom-shutdown recieved before initial write, will not write"); + // The dtor would clear for us, but we clear here so that StartupWriteComplete() + // will return true instead of waiting for the table to finish clearing. + // This mechanism will change when IO is moved off-thread (bug 586859). + mTable.Clear(); + return; + } + + if (mTable.Count() == 0) + return; + + rv = mZipW->Open(mFile, PR_RDWR | PR_CREATE_FILE); + if (NS_FAILED(rv)) { + NS_WARNING("could not open zipfile for write"); + return; + } + + nsCOMPtr<nsIStringInputStream> 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 = mZipW; + + mTable.Enumerate(CacheCloseHelper, &holder); + + // Close the archive so Windows doesn't choke. + mArchive = NULL; + mZipW->Close(); + + // our reader's view of the archive is outdated now, reload it. + LoadArchive(); + + return; +} + +void +StartupCache::InvalidateCache() +{ + mTable.Clear(); + mArchive = NULL; + + // This is usually closed, but it's possible to get into + // an inconsistent state. + mZipW->Close(); + mFile->Remove(false); + LoadArchive(); +} + +void +StartupCache::WriteTimeout(nsITimer *aTimer, void *aClosure) +{ + StartupCache* sc = (StartupCache*) aClosure; + sc->WriteToDisk(); +} + +// We don't want to refcount StartupCache, so we'll just +// hold a ref to this and pass it to observerService instead. +NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheListener, nsIObserver) + +nsresult +StartupCacheListener::Observe(nsISupports *subject, const char* topic, const PRUnichar* data) +{ + nsresult rv = NS_OK; + if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + StartupCache::gShutdownInitiated = PR_TRUE; + } + return rv; +} + +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; +} + +// StartupCacheDebugOutputStream implementation +#ifdef DEBUG +NS_IMPL_ISUPPORTS3(StartupCacheDebugOutputStream, nsIObjectOutputStream, + nsIBinaryOutputStream, nsIOutputStream) + +PRBool +StartupCacheDebugOutputStream::CheckReferences(nsISupports* aObject) +{ + nsresult rv; + + nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject); + if (!classInfo) { + NS_ERROR("aObject must implement nsIClassInfo"); + return PR_FALSE; + } + + PRUint32 flags; + rv = classInfo->GetFlags(&flags); + NS_ENSURE_SUCCESS(rv, rv); + if (flags & nsIClassInfo::SINGLETON) + return PR_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 PR_FALSE; + } + + mObjectMap->PutEntry(aObject); + return PR_TRUE; +} + +// nsIObjectOutputStream implementation +nsresult +StartupCacheDebugOutputStream::WriteObject(nsISupports* aObject, PRBool aIsStrongRef) +{ + nsresult rv; + + nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject)); + + NS_ASSERTION(rootObject.get() == aObject, + "bad call to WriteObject -- call WriteCompoundObject!"); + PRBool check = CheckReferences(aObject); + NS_ENSURE_TRUE(check, NS_ERROR_FAILURE); + return mBinaryStream->WriteObject(aObject, aIsStrongRef); +} + +nsresult +StartupCacheDebugOutputStream::WriteSingleRefObject(nsISupports* aObject) +{ + nsresult rv; + nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject)); + + NS_ASSERTION(rootObject.get() == aObject, + "bad call to WriteSingleRefObject -- call WriteCompoundObject!"); + PRBool check = CheckReferences(aObject); + NS_ENSURE_TRUE(check, NS_ERROR_FAILURE); + return mBinaryStream->WriteSingleRefObject(aObject); +} + +nsresult +StartupCacheDebugOutputStream::WriteCompoundObject(nsISupports* aObject, + const nsIID& aIID, + PRBool aIsStrongRef) +{ + nsresult rv; + nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject)); + + nsCOMPtr<nsISupports> roundtrip; + rootObject->QueryInterface(aIID, getter_AddRefs(roundtrip)); + NS_ASSERTION(roundtrip.get() == aObject, + "bad aggregation or multiple inheritance detected by call to " + "WriteCompoundObject!"); + + PRBool 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(PRUint32 aLength, PRUint32 aAlignMask) +{ + return mBinaryStream->GetBuffer(aLength, aAlignMask); +} + +void +StartupCacheDebugOutputStream::PutBuffer(char* aBuffer, PRUint32 aLength) +{ + mBinaryStream->PutBuffer(aBuffer, aLength); +} +#endif //DEBUG + +StartupCacheWrapper* StartupCacheWrapper::gStartupCacheWrapper = nsnull; + +NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheWrapper, nsIStartupCache) + +StartupCacheWrapper* StartupCacheWrapper::GetSingleton() +{ + if (!gStartupCacheWrapper) + gStartupCacheWrapper = new StartupCacheWrapper(); + + NS_ADDREF(gStartupCacheWrapper); + return gStartupCacheWrapper; +} + +nsresult +StartupCacheWrapper::GetBuffer(const char* id, char** outbuf, PRUint32* length) +{ + StartupCache* sc = StartupCache::GetSingleton(); + if (!sc) { + return NS_ERROR_NOT_INITIALIZED; + } + return sc->GetBuffer(id, outbuf, length); +} + +nsresult +StartupCacheWrapper::PutBuffer(const char* id, char* inbuf, PRUint32 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::GetDebugObjectOutputStream(nsIObjectOutputStream* stream, + nsIObjectOutputStream** outStream) +{ + StartupCache* sc = StartupCache::GetSingleton(); + if (!sc) { + return NS_ERROR_NOT_INITIALIZED; + } + return sc->GetDebugObjectOutputStream(stream, outStream); +} + +nsresult +StartupCacheWrapper::StartupWriteComplete(PRBool *complete) +{ + StartupCache* sc = StartupCache::GetSingleton(); + if (!sc) { + return NS_ERROR_NOT_INITIALIZED; + } + *complete = sc->mStartupWriteInitiated && sc->mTable.Count() == 0; + return NS_OK; +} + +nsresult +StartupCacheWrapper::ResetStartupWriteTimer() +{ + StartupCache* sc = StartupCache::GetSingleton(); + if (!sc) { + return NS_ERROR_NOT_INITIALIZED; + } + sc->mStartupWriteInitiated = PR_FALSE; + sc->mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, sc, 10000, + nsITimer::TYPE_ONE_SHOT); + return NS_OK; +} + +nsresult +StartupCacheWrapper::GetObserver(nsIObserver** obv) { + StartupCache* sc = StartupCache::GetSingleton(); + if (!sc) { + return NS_ERROR_NOT_INITIALIZED; + } + NS_ADDREF(*obv = sc->mListener); + return NS_OK; +} + +} // namespace scache +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/startupcache/StartupCache.h @@ -0,0 +1,223 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Startup Cache. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation <http://www.mozilla.org/>. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Benedict Hsieh <bhsieh@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef StartupCache_h_ +#define StartupCache_h_ + +#include "prio.h" +#include "prtypes.h" + +#include "nsClassHashtable.h" +#include "nsIZipWriter.h" +#include "nsIZipReader.h" +#include "nsComponentManagerUtils.h" +#include "nsZipArchive.h" +#include "nsIStartupCache.h" +#include "nsIStorageStream.h" +#include "nsITimer.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsIOutputStream.h" +#include "nsIFile.h" + +/** + * The StartupCache is a persistent cache of simple key-value pairs, + * where the keys are null-terminated c-strings and the values are + * arbitrary data, passed as a (char*, size) tuple. + * + * Clients should use the GetSingleton() static method to access the cache. It + * will be available from the end of XPCOM init (NS_InitXPCOM3 in nsXPComInit.cpp), + * until XPCOM shutdown begins. The GetSingleton() method will return null if the cache + * is unavailable. The cache is only provided for libxul builds -- + * it will fail to link in non-libxul builds. The XPCOM interface is provided + * only to allow compiled-code tests; clients should avoid using it. + * + * The API provided is very simple: GetBuffer() returns a buffer that was previously + * stored in the cache (if any), and PutBuffer() inserts a buffer into the cache. + * GetBuffer returns a new buffer, and the caller must take ownership of it. + * PutBuffer will assert if the client attempts to insert a buffer with the same name as + * an existing entry. The cache makes a copy of the passed-in buffer, so client + * retains ownership. + * + * InvalidateCache() may be called if a client suspects data corruption + * or wishes to invalidate for any other reason. This will remove all existing cache data. + * Finally, getDebugObjectOutputStream() allows debug code to wrap an objectstream + * with a debug objectstream, to check for multiply-referenced objects. These will + * generally fail to deserialize correctly, unless they are stateless singletons or the + * client maintains their own object data map for deserialization. + * + * Writes before the final-ui-startup notification are placed in an intermediate + * cache in memory, then written out to disk at a later time, to get writes off the + * startup path. In any case, clients should not rely on being able to GetBuffer() + * data that is written to the cache, since it may not have been written to disk or + * another client may have invalidated the cache. In other words, it should be used as + * a cache only, and not a reliable persistent store. + * + * Some utility functions are provided in StartupCacheUtils. These functions wrap the + * buffers into object streams, which may be useful for serializing objects. Note + * the above caution about multiply-referenced objects, though -- the streams are just + * as 'dumb' as the underlying buffers about multiply-referenced objects. They just + * provide some convenience in writing out data. + */ + +namespace mozilla { +namespace scache { + +struct CacheEntry +{ + nsAutoArrayPtr<char> data; + PRUint32 size; + + CacheEntry() : data(nsnull), size(0) { } + + // Takes possession of buf + CacheEntry(char* buf, PRUint32 len) : data(buf), size(len) { } + + ~CacheEntry() + { + } +}; + +// We don't want to refcount StartupCache, and ObserverService wants to +// refcount its listeners, so we'll let it refcount this instead. +class StartupCacheListener : public nsIObserver +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +}; + +class StartupCache +{ + +friend class StartupCacheListener; +friend class StartupCacheWrapper; + +public: + + // StartupCache methods. See above comments for a more detailed description. + + // Returns a buffer that was previously stored, caller takes ownership. + nsresult GetBuffer(const char* id, char** outbuf, PRUint32* length); + + // Stores a buffer. Caller keeps ownership, we make a copy. + nsresult PutBuffer(const char* id, const char* inbuf, PRUint32 length); + + // Removes the cache file. + void InvalidateCache(); + + // In DEBUG builds, returns a stream that will attempt to check for + // and disallow multiple writes of the same object. + nsresult GetDebugObjectOutputStream(nsIObjectOutputStream* aStream, + nsIObjectOutputStream** outStream); + + static StartupCache* GetSingleton(); + static void DeleteSingleton(); + +private: + StartupCache(); + ~StartupCache(); + + nsresult LoadArchive(); + nsresult Init(); + void WriteToDisk(); + + static nsresult InitSingleton(); + static void WriteTimeout(nsITimer *aTimer, void *aClosure); + + nsClassHashtable<nsCStringHashKey, CacheEntry> mTable; + nsCOMPtr<nsIZipWriter> mZipW; + nsAutoPtr<nsZipArchive> mArchive; + nsCOMPtr<nsILocalFile> mFile; + + nsCOMPtr<nsIObserverService> mObserverService; + nsRefPtr<StartupCacheListener> mListener; + nsCOMPtr<nsITimer> mTimer; + + PRBool mStartupWriteInitiated; + + static StartupCache *gStartupCache; + static PRBool gShutdownInitiated; + +#ifdef DEBUG + nsTHashtable<nsISupportsHashKey> mWriteObjectMap; +#endif +}; + +// This debug outputstream attempts to detect if clients are writing multiple +// references to the same object. We only support that if that object +// is a singleton. +#ifdef DEBUG +class StartupCacheDebugOutputStream + : public nsIObjectOutputStream +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIOBJECTOUTPUTSTREAM + + StartupCacheDebugOutputStream (nsIObjectOutputStream* binaryStream, + nsTHashtable<nsISupportsHashKey>* objectMap) + : mBinaryStream(binaryStream), mObjectMap(objectMap) { } + + NS_FORWARD_SAFE_NSIBINARYOUTPUTSTREAM(mBinaryStream) + NS_FORWARD_SAFE_NSIOUTPUTSTREAM(mBinaryStream) + + PRBool CheckReferences(nsISupports* aObject); + + nsCOMPtr<nsIObjectOutputStream> mBinaryStream; + nsTHashtable<nsISupportsHashKey> *mObjectMap; +}; +#endif // DEBUG + +// XPCOM wrapper interface provided for tests only. +#define NS_STARTUPCACHE_CID \ + {0xae4505a9, 0x87ab, 0x477c, \ + {0xb5, 0x77, 0xf9, 0x23, 0x57, 0xed, 0xa8, 0x84}} +// contract id: "@mozilla.org/startupcache/cache;1" + +class StartupCacheWrapper + : public nsIStartupCache +{ + NS_DECL_ISUPPORTS + NS_DECL_NSISTARTUPCACHE + + static StartupCacheWrapper* GetSingleton(); + static StartupCacheWrapper *gStartupCacheWrapper; +}; + +} // namespace scache +} // namespace mozilla +#endif //StartupCache_h_
new file mode 100644 --- /dev/null +++ b/startupcache/StartupCacheModule.cpp @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Startup Cache. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation <http://www.mozilla.org/>. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Benedict Hsieh <bhsieh@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <string.h> + +#include "nscore.h" +#include "pratom.h" +#include "prmem.h" +#include "prio.h" +#include "plstr.h" +#include "prlog.h" + +#include "nsID.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsCOMPtr.h" +#include "nsIModule.h" +#include "mozilla/ModuleUtils.h" +#include "mozilla/scache/StartupCache.h" + +using namespace mozilla::scache; + +// XXX Need help with guard for ENABLE_TEST +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(StartupCacheWrapper, + StartupCacheWrapper::GetSingleton) +NS_DEFINE_NAMED_CID(NS_STARTUPCACHE_CID); + +static const mozilla::Module::CIDEntry kStartupCacheCIDs[] = { + { &kNS_STARTUPCACHE_CID, false, NULL, StartupCacheWrapperConstructor }, + { NULL } +}; + +static const mozilla::Module::ContractIDEntry kStartupCacheContracts[] = { + { "@mozilla.org/startupcache/cache;1", &kNS_STARTUPCACHE_CID }, + { NULL } +}; + +static const mozilla::Module kStartupCacheModule = { + mozilla::Module::kVersion, + kStartupCacheCIDs, + kStartupCacheContracts, + NULL, + NULL, + NULL, + NULL +}; + +NSMODULE_DEFN(StartupCacheModule) = &kStartupCacheModule;
new file mode 100644 --- /dev/null +++ b/startupcache/StartupCacheUtils.cpp @@ -0,0 +1,90 @@ + +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsIStringStream.h" +#include "nsAutoPtr.h" +#include "StartupCacheUtils.h" +#include "mozilla/scache/StartupCache.h" + +namespace mozilla { +namespace scache { + +NS_EXPORT nsresult +NS_NewObjectInputStreamFromBuffer(char* buffer, PRUint32 len, + nsIObjectInputStream** stream) +{ + nsCOMPtr<nsIStringInputStream> stringStream + = do_CreateInstance("@mozilla.org/io/string-input-stream;1"); + nsCOMPtr<nsIObjectInputStream> objectInput + = do_CreateInstance("@mozilla.org/binaryinputstream;1"); + + stringStream->AdoptData(buffer, len); + objectInput->SetInputStream(stringStream); + + objectInput.forget(stream); + return NS_OK; +} + +NS_EXPORT nsresult +NS_NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream, + nsIStorageStream** stream) +{ + nsresult rv; + nsCOMPtr<nsIStorageStream> storageStream + = do_CreateInstance("@mozilla.org/storagestream;1"); + + rv = storageStream->Init(256, PR_UINT32_MAX, nsnull); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIObjectOutputStream> objectOutput + = do_CreateInstance("@mozilla.org/binaryoutputstream;1"); + nsCOMPtr<nsIOutputStream> outputStream + = do_QueryInterface(storageStream); + + objectOutput->SetOutputStream(outputStream); + +#ifdef DEBUG + // Wrap in debug stream to detect unsupported writes of + // multiply-referenced non-singleton objects + StartupCache* sc = StartupCache::GetSingleton(); + NS_ENSURE_TRUE(sc, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIObjectOutputStream> debugStream; + sc->GetDebugObjectOutputStream(objectOutput, getter_AddRefs(debugStream)); + debugStream.forget(wrapperStream); +#else + objectOutput.forget(wrapperStream); +#endif + + storageStream.forget(stream); + return NS_OK; +} + +NS_EXPORT nsresult +NS_NewBufferFromStorageStream(nsIStorageStream *storageStream, + char** buffer, PRUint32* len) +{ + nsresult rv; + nsCOMPtr<nsIInputStream> inputStream; + rv = storageStream->NewInputStream(0, getter_AddRefs(inputStream)); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 avail, read; + rv = inputStream->Available(&avail); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoArrayPtr<char> temp (new char[avail]); + rv = inputStream->Read(temp, avail, &read); + if (NS_SUCCEEDED(rv) && avail != read) + rv = NS_ERROR_UNEXPECTED; + + if (NS_FAILED(rv)) { + return rv; + } + + *len = avail; + *buffer = temp.forget(); + return NS_OK; +} + +} +}
new file mode 100644 --- /dev/null +++ b/startupcache/StartupCacheUtils.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Startup Cache. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation <http://www.mozilla.org/>. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Benedict Hsieh <bhsieh@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#ifndef nsStartupCacheUtils_h_ +#define nsStartupCacheUtils_h_ + +#include "nsIStorageStream.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" + +namespace mozilla { +namespace scache { + +NS_EXPORT nsresult +NS_NewObjectInputStreamFromBuffer(char* buffer, PRUint32 len, + nsIObjectInputStream** stream); + +// We can't retrieve the wrapped stream from the objectOutputStream later, +// so we return it here. +NS_EXPORT nsresult +NS_NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream, + nsIStorageStream** stream); + +NS_EXPORT nsresult +NS_NewBufferFromStorageStream(nsIStorageStream *storageStream, + char** buffer, PRUint32* len); +} +} +#endif //nsStartupCacheUtils_h_
new file mode 100644 --- /dev/null +++ b/startupcache/nsIStartupCache.idl @@ -0,0 +1,76 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Corporation startup cache code. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Benedict Hsieh <bhsieh@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsIInputStream.idl" +#include "nsISupports.idl" +#include "nsIObserver.idl" +#include "nsIObjectOutputStream.idl" + +[uuid(de798fab-af49-4a61-8144-81550986e1da)] +interface nsIStartupCache : nsISupports +{ + + /** This interface is provided for testing purposes only, basically + * just to solve link vagaries. See docs in StartupCache.h + * GetBuffer, PutBuffer, and InvalidateCache act as described + * in that file. */ + + PRUint32 getBuffer(in string aID, out charPtr aBuffer); + void putBuffer(in string aID, in charPtr aBuffer, + in PRUint32 aLength); + + void invalidateCache(); + + /** In debug builds, wraps this object output stream with a stream that will + * detect and prevent the write of a multiply-referenced non-singleton object + * during serialization. In non-debug, returns an add-ref'd pointer to + * original stream, unwrapped. */ + nsIObjectOutputStream getDebugObjectOutputStream(in nsIObjectOutputStream aStream); + + /* Allows clients to check whether the one-time writeout after startup + * has finished yet, and also to set this variable as needed (so test + * code can fire mulitple startup writes if needed). + */ + boolean startupWriteComplete(); + void resetStartupWriteTimer(); + + /* Allows clients to simulate the behavior of ObserverService. */ + readonly attribute nsIObserver observer; +}; +
new file mode 100644 --- /dev/null +++ b/startupcache/nsStartupCacheUtils.cpp @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Startup Cache. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation <http://www.mozilla.org/>. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Benedict Hsieh <bhsieh@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsStartupCacheUtils.h" + +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIInputStream.h" +#include "nsIStorageStream.h" +#include "nsIStringStream.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" + +nsresult +NS_NewObjectInputStreamFromBuffer(char* buffer, int len, + nsIObjectInputStream** stream) +{ + nsCOMPtr<nsIStringInputStream> stringStream + = do_CreateInstance("@mozilla.org/io/string-input-stream;1"); + if (!stringStream) + return NS_ERROR_OUT_OF_MEMORY; + nsCOMPtr<nsIObjectInputStream> objectInput + = do_CreateInstance("@mozilla.org/binaryinputstream;1"); + if (!objectInput) + return NS_ERROR_OUT_OF_MEMORY; + + stringStream->AdoptData(buffer, len); + objectInput->SetInputStream(stringStream); + + NS_ADDREF(*stream = objectInput); + return NS_OK; +} + +// This is questionable API name and design, but we can't +// retrieve the wrapped stream from the objectOutputStream later... +nsresult +NS_NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream, + nsIStorageStream** stream) +{ + nsCOMPtr<nsIStorageStream> storageStream; + nsresult rv = NS_NewStorageStream(256, (PRUint32)-1, + getter_AddRefs(storageStream)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIObjectOutputStream> objectOutput + = do_CreateInstance("@mozilla.org/binaryoutputstream;1"); + if (!objectOutput) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr<nsIOutputStream> outputStream + = do_QueryInterface(storageStream); + + objectOutput->SetOutputStream(outputStream); + NS_ADDREF(*wrapperStream = objectOutput); + NS_ADDREF(*stream = storageStream); + return NS_OK; +} + +nsresult +NS_NewBufferFromStorageStream(nsIStorageStream *storageStream, + char** buffer, int* len) +{ + nsresult rv; + nsCOMPtr<nsIInputStream> inputStream; + rv = storageStream->NewInputStream(0, getter_AddRefs(inputStream)); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 avail, read; + rv = inputStream->Available(&avail); + NS_ENSURE_SUCCESS(rv, rv); + + char* temp = new char[avail]; + if (!temp) + return NS_ERROR_OUT_OF_MEMORY; + + rv = inputStream->Read(temp, avail, &read); + if (NS_SUCCEEDED(rv) && avail != read) + rv = NS_ERROR_UNEXPECTED; + + if (NS_FAILED(rv)) { + delete temp; + return rv; + } + + *len = avail; + *buffer = temp; + return NS_OK; +} +
new file mode 100644 --- /dev/null +++ b/startupcache/test/Makefile.in @@ -0,0 +1,53 @@ +# +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Mozilla.org. +# Portions created by the Initial Developer are Copyright (C) 2010 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Benedict Hsieh <bhsieh@mozilla.com> +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = test_startupcache + +CPP_UNIT_TESTS = TestStartupCache.cpp + +EXTRA_DSO_LIBS += xul +LIBS += $(MOZ_COMPONENT_LIBS) + +include $(topsrcdir)/config/rules.mk
new file mode 100644 --- /dev/null +++ b/startupcache/test/TestStartupCache.cpp @@ -0,0 +1,332 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Startup Cache. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation <http://www.mozilla.org/>. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Benedict Hsieh <bhsieh@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "TestHarness.h" + +#include "nsThreadUtils.h" +#include "nsIClassInfo.h" +#include "nsIOutputStream.h" +#include "nsIObserver.h" +#include "nsISerializable.h" +#include "nsISupports.h" +#include "nsIStartupCache.h" +#include "nsIStringStream.h" +#include "nsIStorageStream.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIURI.h" +#include "nsStringAPI.h" + +namespace mozilla { +namespace scache { + +NS_IMPORT nsresult +NS_NewObjectInputStreamFromBuffer(char* buffer, PRUint32 len, + nsIObjectInputStream** stream); + +// We can't retrieve the wrapped stream from the objectOutputStream later, +// so we return it here. +NS_IMPORT nsresult +NS_NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream, + nsIStorageStream** stream); + +NS_IMPORT nsresult +NS_NewBufferFromStorageStream(nsIStorageStream *storageStream, + char** buffer, PRUint32* len); +} +} + +using namespace mozilla::scache; + +#define NS_ENSURE_STR_MATCH(str1, str2, testname) \ +PR_BEGIN_MACRO \ +if (0 != strcmp(str1, str2)) { \ + fail("failed " testname); \ + return NS_ERROR_FAILURE; \ +} \ +passed("passed " testname); \ +PR_END_MACRO + +nsresult +WaitForStartupTimer() { + nsresult rv; + nsCOMPtr<nsIStartupCache> sc + = do_GetService("@mozilla.org/startupcache/cache;1"); + PR_Sleep(10 * PR_TicksPerSecond()); + + PRBool complete; + while (true) { + NS_ProcessPendingEvents(nsnull); + rv = sc->StartupWriteComplete(&complete); + if (NS_FAILED(rv) || complete) + break; + PR_Sleep(1 * PR_TicksPerSecond()); + } + return rv; +} + +nsresult +TestStartupWriteRead() { + nsresult rv; + nsCOMPtr<nsIStartupCache> sc + = do_GetService("@mozilla.org/startupcache/cache;1", &rv); + if (!sc) { + fail("didn't get a pointer..."); + return NS_ERROR_FAILURE; + } else { + passed("got a pointer?"); + } + sc->InvalidateCache(); + + char* buf = "Market opportunities for BeardBook"; + char* id = "id"; + char* outbufPtr = NULL; + nsAutoArrayPtr<char> outbuf; + PRUint32 len; + + rv = sc->PutBuffer(id, buf, strlen(buf) + 1); + NS_ENSURE_SUCCESS(rv, rv); + + rv = sc->GetBuffer(id, &outbufPtr, &len); + NS_ENSURE_SUCCESS(rv, rv); + outbuf = outbufPtr; + NS_ENSURE_STR_MATCH(buf, outbuf, "pre-write read"); + + rv = WaitForStartupTimer(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = sc->GetBuffer(id, &outbufPtr, &len); + NS_ENSURE_SUCCESS(rv, rv); + outbuf = outbufPtr; + NS_ENSURE_STR_MATCH(buf, outbuf, "simple write/read"); + + return NS_OK; +} + +nsresult +TestWriteInvalidateRead() { + nsresult rv; + char* buf = "BeardBook competitive analysis"; + char* id = "id"; + char* outbuf = NULL; + PRUint32 len; + nsCOMPtr<nsIStartupCache> sc + = do_GetService("@mozilla.org/startupcache/cache;1", &rv); + sc->InvalidateCache(); + + rv = sc->PutBuffer(id, buf, strlen(buf) + 1); + NS_ENSURE_SUCCESS(rv, rv); + + sc->InvalidateCache(); + + rv = sc->GetBuffer(id, &outbuf, &len); + delete[] outbuf; + if (rv == NS_ERROR_NOT_AVAILABLE) { + passed("buffer not available after invalidate"); + } else if (NS_SUCCEEDED(rv)) { + fail("GetBuffer succeeded unexpectedly after invalidate"); + return NS_ERROR_UNEXPECTED; + } else { + fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE"); + return rv; + } + + sc->InvalidateCache(); + return NS_OK; +} + +nsresult +TestWriteObject() { + nsresult rv; + + nsCOMPtr<nsIURI> obj + = do_CreateInstance("@mozilla.org/network/simple-uri;1"); + if (!obj) { + fail("did not create object in test write object"); + return NS_ERROR_UNEXPECTED; + } + NS_NAMED_LITERAL_CSTRING(spec, "http://www.mozilla.org"); + obj->SetSpec(spec); + nsCOMPtr<nsIStartupCache> sc = do_GetService("@mozilla.org/startupcache/cache;1", &rv); + + sc->InvalidateCache(); + + // Create an object stream. Usually this is done with + // NS_NewObjectOutputWrappedStorageStream, but that uses + // StartupCache::GetSingleton in debug builds, and we + // don't have access to that here. Obviously. + char* id = "id"; + nsCOMPtr<nsIStorageStream> storageStream + = do_CreateInstance("@mozilla.org/storagestream;1"); + NS_ENSURE_ARG_POINTER(storageStream); + + rv = storageStream->Init(256, (PRUint32) -1, nsnull); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIObjectOutputStream> objectOutput + = do_CreateInstance("@mozilla.org/binaryoutputstream;1"); + if (!objectOutput) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr<nsIOutputStream> outputStream + = do_QueryInterface(storageStream); + + rv = objectOutput->SetOutputStream(outputStream); + + if (NS_FAILED(rv)) { + fail("failed to create output stream"); + return rv; + } + nsCOMPtr<nsISupports> objQI(do_QueryInterface(obj)); + rv = objectOutput->WriteObject(objQI, PR_TRUE); + if (NS_FAILED(rv)) { + fail("failed to write object"); + return rv; + } + + char* bufPtr = NULL; + nsAutoArrayPtr<char> buf; + PRUint32 len; + NS_NewBufferFromStorageStream(storageStream, &bufPtr, &len); + buf = bufPtr; + + // Since this is a post-startup write, it should be written and + // available. + rv = sc->PutBuffer(id, buf, len); + if (NS_FAILED(rv)) { + fail("failed to insert input stream"); + return rv; + } + + char* buf2Ptr = NULL; + nsAutoArrayPtr<char> buf2; + PRUint32 len2; + nsCOMPtr<nsIObjectInputStream> objectInput; + rv = sc->GetBuffer(id, &buf2Ptr, &len2); + if (NS_FAILED(rv)) { + fail("failed to retrieve buffer"); + return rv; + } + buf2 = buf2Ptr; + + rv = NS_NewObjectInputStreamFromBuffer(buf2, len2, getter_AddRefs(objectInput)); + if (NS_FAILED(rv)) { + fail("failed to created input stream"); + return rv; + } + buf2.forget(); + + nsCOMPtr<nsISupports> deserialized; + rv = objectInput->ReadObject(PR_TRUE, getter_AddRefs(deserialized)); + if (NS_FAILED(rv)) { + fail("failed to read object"); + return rv; + } + + PRBool match = false; + nsCOMPtr<nsIURI> uri(do_QueryInterface(deserialized)); + if (uri) { + nsCString outSpec; + uri->GetSpec(outSpec); + match = outSpec.Equals(spec); + } + if (!match) { + fail("deserialized object has incorrect information"); + return rv; + } + + passed("write object"); + return NS_OK; +} + +nsresult +TestEarlyShutdown() { + nsresult rv; + nsCOMPtr<nsIStartupCache> sc + = do_GetService("@mozilla.org/startupcache/cache;1", &rv); + sc->InvalidateCache(); + + char* buf = "Find your soul beardmate on BeardBook"; + char* id = "id"; + PRUint32 len; + char* outbuf = NULL; + + sc->ResetStartupWriteTimer(); + rv = sc->PutBuffer(buf, id, strlen(buf) + 1); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIObserver> obs; + sc->GetObserver(getter_AddRefs(obs)); + obs->Observe(nsnull, "xpcom-shutdown", nsnull); + rv = WaitForStartupTimer(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = sc->GetBuffer(id, &outbuf, &len); + delete[] outbuf; + + if (rv == NS_ERROR_NOT_AVAILABLE) { + passed("buffer not available after early shutdown"); + } else if (NS_SUCCEEDED(rv)) { + fail("GetBuffer succeeded unexpectedly after early shutdown"); + return NS_ERROR_UNEXPECTED; + } else { + fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE"); + return rv; + } + + return NS_OK; +} + + +int main(int argc, char** argv) +{ + int rv = 0; + nsresult rv2; + ScopedXPCOM xpcom("Startup Cache"); + + if (NS_FAILED(TestStartupWriteRead())) + rv = 1; + if (NS_FAILED(TestWriteInvalidateRead())) + rv = 1; + if (NS_FAILED(TestWriteObject())) + rv = 1; + if (NS_FAILED(TestEarlyShutdown())) + rv = 1; + + return rv; +}
--- a/toolkit/library/libxul-config.mk +++ b/toolkit/library/libxul-config.mk @@ -134,16 +134,17 @@ endif # component libraries COMPONENT_LIBS += \ necko \ uconv \ i18n \ chardet \ jar$(VERSION_NUMBER) \ + startupcache \ pref \ htmlpars \ imglib2 \ gklayout \ docshell \ embedcomponents \ webbrwsr \ nsappshell \
--- a/toolkit/library/nsStaticXULComponents.cpp +++ b/toolkit/library/nsStaticXULComponents.cpp @@ -236,16 +236,17 @@ MODULE(nsI18nModule) \ MODULE(nsChardetModule) \ UNIVERSALCHARDET_MODULE \ MODULE(necko) \ PERMISSIONS_MODULES \ AUTH_MODULE \ MODULE(nsJarModule) \ ZIPWRITER_MODULE \ + MODULE(StartupCacheModule) \ MODULE(nsPrefModule) \ RDF_MODULES \ MODULE(nsParserModule) \ GFX_MODULES \ WIDGET_MODULES \ MODULE(nsImageLib2Module) \ ICON_MODULE \ JETPACK_MODULES \
--- a/toolkit/toolkit-makefiles.sh +++ b/toolkit/toolkit-makefiles.sh @@ -774,16 +774,20 @@ MAKEFILES_extensions=" extensions/cookie/Makefile extensions/permissions/Makefile extensions/pref/Makefile extensions/pref/autoconfig/Makefile extensions/pref/autoconfig/public/Makefile extensions/pref/autoconfig/src/Makefile " +MAKEFILES_startupcache=" + startupcache/Makefile +" + add_makefiles " $MAKEFILES_db $MAKEFILES_dom $MAKEFILES_editor $MAKEFILES_xmlparser $MAKEFILES_gfx $MAKEFILES_htmlparser $MAKEFILES_intl @@ -815,16 +819,17 @@ add_makefiles " $MAKEFILES_embedding $MAKEFILES_xulapp $MAKEFILES_libpr0n $MAKEFILES_accessible $MAKEFILES_zlib $MAKEFILES_libmar $MAKEFILES_lib7z $MAKEFILES_extensions + $MAKEFILES_startupcache " # # Conditional makefiles # if [ "$ENABLE_TESTS" ]; then add_makefiles " @@ -921,16 +926,17 @@ if [ "$ENABLE_TESTS" ]; then modules/libpref/test/Makefile modules/plugin/test/Makefile modules/plugin/test/mochitest/Makefile modules/plugin/test/testplugin/Makefile netwerk/test/httpserver/Makefile parser/htmlparser/tests/mochitest/Makefile parser/xml/test/Makefile rdf/tests/triplescat/Makefile + startupcache/tests/Makefile testing/mochitest/Makefile testing/mochitest/MochiKit/Makefile testing/mochitest/chrome/Makefile testing/mochitest/ssltunnel/Makefile testing/mochitest/static/Makefile testing/mochitest/tests/Makefile testing/mochitest/tests/MochiKit-1.4.2/Makefile testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Makefile
--- a/toolkit/toolkit-tiers.mk +++ b/toolkit/toolkit-tiers.mk @@ -235,16 +235,20 @@ tier_platform_dirs += extensions/java/xp endif ifndef BUILD_STATIC_LIBS ifneq (,$(MOZ_ENABLE_GTK2)) tier_platform_dirs += embedding/browser/gtk endif endif +ifdef MOZ_ENABLE_LIBXUL +tier_platform_dirs += startupcache +endif + ifndef BUILD_STATIC_LIBS tier_platform_dirs += toolkit/library endif ifdef MOZ_ENABLE_LIBXUL tier_platform_dirs += xpcom/stub endif
--- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -2486,16 +2486,19 @@ static void RemoveComponentRegistries(ns if (!file) return; file->AppendNative(NS_LITERAL_CSTRING("XUL" PLATFORM_FASL_SUFFIX)); file->Remove(PR_FALSE); file->SetNativeLeafName(NS_LITERAL_CSTRING("XPC" PLATFORM_FASL_SUFFIX)); file->Remove(PR_FALSE); + + file->SetNativeLeafName(NS_LITERAL_CSTRING("startupCache")); + file->Remove(PR_TRUE); } // To support application initiated restart via nsIAppStartup.quit, we // need to save various environment variables, and then restore them // before re-launching the application. static struct { const char *name;
--- a/xpcom/build/nsXPComInit.cpp +++ b/xpcom/build/nsXPComInit.cpp @@ -142,16 +142,20 @@ extern nsresult nsStringInputStreamConst #include <locale.h> #include "mozilla/Services.h" #include "mozilla/FunctionTimer.h" #include "mozilla/Omnijar.h" #include "nsChromeRegistry.h" #include "nsChromeProtocolHandler.h" +#ifdef MOZ_ENABLE_LIBXUL +#include "mozilla/scache/StartupCache.h" +#endif + #ifdef MOZ_IPC #include "base/at_exit.h" #include "base/command_line.h" #include "base/message_loop.h" #include "mozilla/ipc/BrowserProcessSubThread.h" using base::AtExitManager; @@ -537,16 +541,19 @@ NS_InitXPCOM2(nsIServiceManager* *result NS_TIME_FUNCTION_MARK("Next: register category providers"); // After autoreg, but before we actually instantiate any components, // add any services listed in the "xpcom-directory-providers" category // to the directory service. nsDirectoryService::gService->RegisterCategoryProviders(); +#ifdef MOZ_ENABLE_LIBXUL + mozilla::scache::StartupCache::GetSingleton(); +#endif NS_TIME_FUNCTION_MARK("Next: create services from category"); // Notify observers of xpcom autoregistration start NS_CreateServicesFromCategory(NS_XPCOM_STARTUP_CATEGORY, nsnull, NS_XPCOM_STARTUP_OBSERVER_ID); return NS_OK; @@ -614,17 +621,19 @@ ShutdownXPCOM(nsIServiceManager* servMgr { (void) observerService-> NotifyObservers(mgr, NS_XPCOM_SHUTDOWN_OBSERVER_ID, nsnull); } } NS_ProcessPendingEvents(thread); - +#ifdef MOZ_ENABLE_LIBXUL + mozilla::scache::StartupCache::DeleteSingleton(); +#endif if (observerService) (void) observerService-> NotifyObservers(nsnull, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, nsnull); NS_ProcessPendingEvents(thread); // Shutdown the timer thread and all timers that might still be alive before
--- a/xpcom/tests/TestHarness.h +++ b/xpcom/tests/TestHarness.h @@ -50,16 +50,17 @@ #include "nsAutoPtr.h" #include "nsStringGlue.h" #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryServiceDefs.h" #include "nsDirectoryServiceUtils.h" #include "nsIDirectoryService.h" #include "nsIFile.h" #include "nsIProperties.h" +#include "nsXULAppAPI.h" #include <stdio.h> #include <stdlib.h> #include <stdarg.h> static PRUint32 gFailCount = 0; /** * Prints the given failure message and arguments using printf, prepending @@ -336,17 +337,18 @@ class ScopedXPCOM : public nsIDirectoryS if (mDirSvcProvider && NS_SUCCEEDED(mDirSvcProvider->GetFile(aProperty, _persistent, _result))) { return NS_OK; } // Otherwise, the test harness provides some directories automatically. if (0 == strcmp(aProperty, NS_APP_USER_PROFILE_50_DIR) || - 0 == strcmp(aProperty, NS_APP_USER_PROFILE_LOCAL_50_DIR)) { + 0 == strcmp(aProperty, NS_APP_USER_PROFILE_LOCAL_50_DIR) || + 0 == strcmp(aProperty, NS_APP_PROFILE_LOCAL_DIR_STARTUP)) { nsCOMPtr<nsIFile> profD = GetProfileDirectory(); NS_ENSURE_TRUE(profD, NS_ERROR_FAILURE); nsCOMPtr<nsIFile> clone; nsresult rv = profD->Clone(getter_AddRefs(clone)); NS_ENSURE_SUCCESS(rv, rv); *_persistent = PR_TRUE;