2010-08-12 19:37:44 +00:00
|
|
|
/* -*- 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>
|
2010-11-11 20:13:57 +00:00
|
|
|
* Taras Glek <tglek@mozilla.com>
|
2010-08-12 19:37:44 +00:00
|
|
|
*
|
|
|
|
* 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"
|
2011-01-07 19:04:33 +00:00
|
|
|
#include "mozilla/Omnijar.h"
|
|
|
|
#include "prenv.h"
|
2011-01-20 21:40:45 +00:00
|
|
|
#include "mozilla/FunctionTimer.h"
|
2011-04-01 05:24:16 +00:00
|
|
|
#include "nsThreadUtils.h"
|
|
|
|
#include "nsXULAppAPI.h"
|
2011-06-30 21:58:03 +00:00
|
|
|
#include "nsIProtocolHandler.h"
|
2011-04-01 05:24:16 +00:00
|
|
|
|
2010-08-12 19:37:44 +00:00
|
|
|
#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;
|
2010-09-23 21:45:26 +00:00
|
|
|
StartupCache::gStartupCache = nsnull;
|
2010-08-12 19:37:44 +00:00
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
StartupCache* StartupCache::gStartupCache;
|
|
|
|
PRBool StartupCache::gShutdownInitiated;
|
|
|
|
|
|
|
|
StartupCache::StartupCache()
|
2011-01-20 21:40:45 +00:00
|
|
|
: mArchive(NULL), mStartupWriteInitiated(PR_FALSE), mWriteThread(NULL) {}
|
2010-08-12 19:37:44 +00:00
|
|
|
|
|
|
|
StartupCache::~StartupCache()
|
|
|
|
{
|
2010-09-23 21:45:26 +00:00
|
|
|
if (mTimer) {
|
|
|
|
mTimer->Cancel();
|
|
|
|
}
|
|
|
|
|
2010-08-12 19:37:44 +00:00
|
|
|
// Generally, the in-memory table should be empty here,
|
2011-04-01 05:24:16 +00:00
|
|
|
// but an early shutdown means either mTimer didn't run
|
|
|
|
// or the write thread is still running.
|
|
|
|
WaitOnWriteThread();
|
2010-08-12 19:37:44 +00:00
|
|
|
WriteToDisk();
|
|
|
|
gStartupCache = nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
StartupCache::Init()
|
|
|
|
{
|
2011-04-01 05:24:16 +00:00
|
|
|
if (XRE_GetProcessType() != GeckoProcessType_Default) {
|
|
|
|
NS_WARNING("Startup cache is only available in the chrome process");
|
|
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
}
|
2011-06-30 21:58:03 +00:00
|
|
|
// workaround for bug 653936
|
|
|
|
nsCOMPtr<nsIProtocolHandler> jarInitializer(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "jar"));
|
|
|
|
|
2010-08-12 19:37:44 +00:00
|
|
|
nsresult rv;
|
|
|
|
mTable.Init();
|
|
|
|
#ifdef DEBUG
|
|
|
|
mWriteObjectMap.Init();
|
|
|
|
#endif
|
|
|
|
|
2011-01-07 19:04:33 +00:00
|
|
|
// 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), PR_FALSE, getter_AddRefs(mFile));
|
|
|
|
} else {
|
|
|
|
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;
|
|
|
|
}
|
2010-08-12 19:37:44 +00:00
|
|
|
|
2011-01-07 19:04:33 +00:00
|
|
|
rv = file->AppendNative(NS_LITERAL_CSTRING("startupCache"));
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
2010-08-12 19:37:44 +00:00
|
|
|
|
2011-01-07 19:04:33 +00:00
|
|
|
// 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);
|
|
|
|
}
|
2010-08-12 19:37:44 +00:00
|
|
|
|
|
|
|
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);
|
2010-09-15 00:39:07 +00:00
|
|
|
rv = mObserverService->AddObserver(mListener, "startupcache-invalidate",
|
|
|
|
PR_FALSE);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
2010-08-12 19:37:44 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
2011-01-07 18:55:14 +00:00
|
|
|
return NS_OK;
|
2010-08-12 19:37:44 +00:00
|
|
|
}
|
|
|
|
|
2011-04-01 05:24:16 +00:00
|
|
|
/**
|
|
|
|
* LoadArchive can be called from the main thread or while reloading cache on write thread.
|
|
|
|
*/
|
2010-08-12 19:37:44 +00:00
|
|
|
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)
|
|
|
|
{
|
2011-04-01 05:24:16 +00:00
|
|
|
NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread");
|
2011-01-20 21:40:45 +00:00
|
|
|
WaitOnWriteThread();
|
2010-08-12 19:37:44 +00:00
|
|
|
if (!mStartupWriteInitiated) {
|
|
|
|
CacheEntry* entry;
|
|
|
|
nsDependentCString idStr(id);
|
|
|
|
mTable.Get(idStr, &entry);
|
|
|
|
if (entry) {
|
2010-11-11 20:13:57 +00:00
|
|
|
*outbuf = new char[entry->size];
|
|
|
|
memcpy(*outbuf, entry->data, entry->size);
|
|
|
|
*length = entry->size;
|
|
|
|
return NS_OK;
|
2010-08-12 19:37:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-11-11 20:13:57 +00:00
|
|
|
if (mArchive) {
|
|
|
|
nsZipItemPtr<char> zipItem(mArchive, id, true);
|
2010-08-12 19:37:44 +00:00
|
|
|
if (zipItem) {
|
2010-11-11 20:13:57 +00:00
|
|
|
*outbuf = zipItem.Forget();
|
|
|
|
*length = zipItem.Length();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2010-08-12 19:37:44 +00:00
|
|
|
}
|
|
|
|
|
2011-02-25 11:53:36 +00:00
|
|
|
if (mozilla::Omnijar::GetReader(mozilla::Omnijar::APP)) {
|
2011-01-07 19:04:33 +00:00
|
|
|
// no need to checksum omnijarred entries
|
2011-07-20 07:35:44 +00:00
|
|
|
nsZipItemPtr<char> zipItem(mozilla::Omnijar::GetReader(mozilla::Omnijar::APP), id);
|
2011-01-07 19:04:33 +00:00
|
|
|
if (zipItem) {
|
|
|
|
*outbuf = zipItem.Forget();
|
|
|
|
*length = zipItem.Length();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
}
|
2011-02-25 11:53:36 +00:00
|
|
|
|
|
|
|
if (mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE)) {
|
|
|
|
// no need to checksum omnijarred entries
|
2011-07-20 07:35:44 +00:00
|
|
|
nsZipItemPtr<char> zipItem(mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE), id);
|
2011-02-25 11:53:36 +00:00
|
|
|
if (zipItem) {
|
|
|
|
*outbuf = zipItem.Forget();
|
|
|
|
*length = zipItem.Length();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-08-12 19:37:44 +00:00
|
|
|
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)
|
|
|
|
{
|
2011-04-01 05:24:16 +00:00
|
|
|
NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread");
|
2011-01-20 21:40:45 +00:00
|
|
|
WaitOnWriteThread();
|
2010-08-12 19:37:44 +00:00
|
|
|
if (StartupCache::gShutdownInitiated) {
|
|
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsAutoArrayPtr<char> data(new char[len]);
|
|
|
|
memcpy(data, inbuf, len);
|
|
|
|
|
|
|
|
nsDependentCString idStr(id);
|
2011-01-07 18:55:14 +00:00
|
|
|
// Cache it for now, we'll write all together later.
|
|
|
|
CacheEntry* entry;
|
2010-08-12 19:37:44 +00:00
|
|
|
|
|
|
|
#ifdef DEBUG
|
2011-01-07 18:55:14 +00:00
|
|
|
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.");
|
|
|
|
}
|
2010-08-12 19:37:44 +00:00
|
|
|
#endif
|
|
|
|
|
2011-01-07 18:55:14 +00:00
|
|
|
entry = new CacheEntry(data.forget(), len);
|
|
|
|
mTable.Put(idStr, entry);
|
|
|
|
return ResetStartupWriteTimer();
|
2010-08-12 19:37:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct CacheWriteHolder
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsIZipWriter> writer;
|
|
|
|
nsCOMPtr<nsIStringInputStream> stream;
|
2011-01-07 18:55:14 +00:00
|
|
|
PRTime time;
|
2010-08-12 19:37:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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
|
2011-01-20 21:40:45 +00:00
|
|
|
rv = writer->AddEntryStream(key, holder->time, PR_TRUE, stream, PR_FALSE);
|
2010-08-12 19:37:44 +00:00
|
|
|
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
NS_WARNING("cache entry deleted but not written to disk.");
|
|
|
|
}
|
|
|
|
return PL_DHASH_REMOVE;
|
|
|
|
}
|
|
|
|
|
2011-04-01 05:24:16 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2010-08-12 19:37:44 +00:00
|
|
|
void
|
|
|
|
StartupCache::WriteToDisk()
|
|
|
|
{
|
|
|
|
nsresult rv;
|
|
|
|
mStartupWriteInitiated = PR_TRUE;
|
|
|
|
|
|
|
|
if (mTable.Count() == 0)
|
|
|
|
return;
|
|
|
|
|
2011-01-07 18:55:14 +00:00
|
|
|
nsCOMPtr<nsIZipWriter> zipW = do_CreateInstance("@mozilla.org/zipwriter;1");
|
|
|
|
if (!zipW)
|
|
|
|
return;
|
|
|
|
|
|
|
|
rv = zipW->Open(mFile, PR_RDWR | PR_CREATE_FILE);
|
2010-08-12 19:37:44 +00:00
|
|
|
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;
|
2011-01-07 18:55:14 +00:00
|
|
|
holder.writer = zipW;
|
|
|
|
holder.time = PR_Now();
|
2010-08-12 19:37:44 +00:00
|
|
|
|
|
|
|
mTable.Enumerate(CacheCloseHelper, &holder);
|
|
|
|
|
|
|
|
// Close the archive so Windows doesn't choke.
|
|
|
|
mArchive = NULL;
|
2011-01-07 18:55:14 +00:00
|
|
|
zipW->Close();
|
2011-01-20 21:40:45 +00:00
|
|
|
|
2010-08-12 19:37:44 +00:00
|
|
|
// our reader's view of the archive is outdated now, reload it.
|
|
|
|
LoadArchive();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
StartupCache::InvalidateCache()
|
|
|
|
{
|
2011-01-20 21:40:45 +00:00
|
|
|
WaitOnWriteThread();
|
2010-08-12 19:37:44 +00:00
|
|
|
mTable.Clear();
|
|
|
|
mArchive = NULL;
|
|
|
|
mFile->Remove(false);
|
|
|
|
LoadArchive();
|
|
|
|
}
|
|
|
|
|
2011-01-20 21:40:45 +00:00
|
|
|
/*
|
|
|
|
* 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()
|
|
|
|
{
|
2011-04-01 05:24:16 +00:00
|
|
|
NS_ASSERTION(NS_IsMainThread(), "Startup cache should only wait for io thread on main thread");
|
|
|
|
if (!mWriteThread || mWriteThread == PR_GetCurrentThread())
|
2011-01-20 21:40:45 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
NS_TIME_FUNCTION_MIN(30);
|
2011-04-01 05:24:16 +00:00
|
|
|
PR_JoinThread(mWriteThread);
|
2011-01-20 21:40:45 +00:00
|
|
|
mWriteThread = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
StartupCache::ThreadedWrite(void *aClosure)
|
|
|
|
{
|
|
|
|
gStartupCache->WriteToDisk();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2010-08-12 19:37:44 +00:00
|
|
|
void
|
|
|
|
StartupCache::WriteTimeout(nsITimer *aTimer, void *aClosure)
|
|
|
|
{
|
2011-01-20 21:40:45 +00:00
|
|
|
gStartupCache->mWriteThread = PR_CreateThread(PR_USER_THREAD,
|
|
|
|
StartupCache::ThreadedWrite,
|
|
|
|
NULL,
|
|
|
|
PR_PRIORITY_NORMAL,
|
|
|
|
PR_LOCAL_THREAD,
|
|
|
|
PR_JOINABLE_THREAD,
|
|
|
|
0);
|
2010-08-12 19:37:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
{
|
2011-01-20 21:40:45 +00:00
|
|
|
StartupCache* sc = StartupCache::GetSingleton();
|
|
|
|
if (!sc)
|
|
|
|
return NS_OK;
|
|
|
|
|
2010-08-12 19:37:44 +00:00
|
|
|
if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
|
2011-01-20 21:40:45 +00:00
|
|
|
// Do not leave the thread running past xpcom shutdown
|
|
|
|
sc->WaitOnWriteThread();
|
2010-08-12 19:37:44 +00:00
|
|
|
StartupCache::gShutdownInitiated = PR_TRUE;
|
2010-09-15 00:39:07 +00:00
|
|
|
} else if (strcmp(topic, "startupcache-invalidate") == 0) {
|
2011-01-20 21:40:45 +00:00
|
|
|
sc->InvalidateCache();
|
2010-08-12 19:37:44 +00:00
|
|
|
}
|
2011-01-20 21:40:45 +00:00
|
|
|
return NS_OK;
|
2010-08-12 19:37:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-01-07 18:55:14 +00:00
|
|
|
nsresult
|
|
|
|
StartupCache::ResetStartupWriteTimer()
|
|
|
|
{
|
|
|
|
mStartupWriteInitiated = PR_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;
|
|
|
|
}
|
|
|
|
|
2010-08-12 19:37:44 +00:00
|
|
|
// 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)
|
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
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)
|
2011-01-07 18:55:14 +00:00
|
|
|
{
|
2010-08-12 19:37:44 +00:00
|
|
|
StartupCache* sc = StartupCache::GetSingleton();
|
|
|
|
if (!sc) {
|
|
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
}
|
2011-01-20 21:40:45 +00:00
|
|
|
sc->WaitOnWriteThread();
|
2010-08-12 19:37:44 +00:00
|
|
|
*complete = sc->mStartupWriteInitiated && sc->mTable.Count() == 0;
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
StartupCacheWrapper::ResetStartupWriteTimer()
|
|
|
|
{
|
|
|
|
StartupCache* sc = StartupCache::GetSingleton();
|
2011-01-07 18:55:14 +00:00
|
|
|
return sc ? sc->ResetStartupWriteTimer() : NS_ERROR_NOT_INITIALIZED;
|
2010-08-12 19:37:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|