Bug 586859: Move startup cache writing off main thread r=cjones a=blocking-final
--- a/startupcache/StartupCache.cpp
+++ b/startupcache/StartupCache.cpp
@@ -57,16 +57,17 @@
#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/FunctionTimer.h"
#ifdef IS_BIG_ENDIAN
#define SC_ENDIAN "big"
#else
#define SC_ENDIAN "little"
#endif
#if PR_BYTES_PER_WORD == 4
@@ -109,17 +110,17 @@ StartupCache::InitSingleton()
}
return rv;
}
StartupCache* StartupCache::gStartupCache;
PRBool StartupCache::gShutdownInitiated;
StartupCache::StartupCache()
- : mArchive(NULL), mStartupWriteInitiated(PR_FALSE) { }
+ : mArchive(NULL), mStartupWriteInitiated(PR_FALSE), mWriteThread(NULL) {}
StartupCache::~StartupCache()
{
if (mTimer) {
mTimer->Cancel();
}
// Generally, the in-memory table should be empty here,
@@ -187,31 +188,33 @@ StartupCache::Init()
InvalidateCache();
}
return NS_OK;
}
nsresult
StartupCache::LoadArchive()
{
+ WaitOnWriteThread();
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)
{
+ 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;
@@ -230,16 +233,17 @@ StartupCache::GetBuffer(const char* id,
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)
{
+ WaitOnWriteThread();
if (StartupCache::gShutdownInitiated) {
return NS_ERROR_NOT_AVAILABLE;
}
nsAutoArrayPtr<char> data(new char[len]);
memcpy(data, inbuf, len);
nsDependentCString idStr(id);
@@ -292,16 +296,17 @@ CacheCloseHelper(const nsACString& key,
NS_WARNING("cache entry deleted but not written to disk.");
}
return PL_DHASH_REMOVE;
}
void
StartupCache::WriteToDisk()
{
+ WaitOnWriteThread();
nsresult rv;
mStartupWriteInitiated = PR_TRUE;
if (mTable.Count() == 0)
return;
nsCOMPtr<nsIZipWriter> zipW = do_CreateInstance("@mozilla.org/zipwriter;1");
if (!zipW)
@@ -325,37 +330,73 @@ StartupCache::WriteToDisk()
holder.writer = zipW;
holder.time = PR_Now();
mTable.Enumerate(CacheCloseHelper, &holder);
// Close the archive so Windows doesn't choke.
mArchive = NULL;
zipW->Close();
-
+
// our reader's view of the archive is outdated now, reload it.
LoadArchive();
return;
}
void
StartupCache::InvalidateCache()
{
+ WaitOnWriteThread();
mTable.Clear();
mArchive = NULL;
mFile->Remove(false);
LoadArchive();
}
+/*
+ * 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()
+{
+ PRThread* writeThread = mWriteThread;
+ if (!writeThread || writeThread == PR_GetCurrentThread())
+ return;
+
+ NS_TIME_FUNCTION_MIN(30);
+ //stick a functiontimer thing here
+ //NS_WARNING("Waiting on startupcache write");
+ PR_JoinThread(writeThread);
+ 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.
+ */
void
StartupCache::WriteTimeout(nsITimer *aTimer, void *aClosure)
{
- StartupCache* sc = (StartupCache*) aClosure;
- sc->WriteToDisk();
+ gStartupCache->mWriteThread = PR_CreateThread(PR_USER_THREAD,
+ StartupCache::ThreadedWrite,
+ NULL,
+ PR_PRIORITY_NORMAL,
+ PR_LOCAL_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_THREADSAFE_ISUPPORTS1(StartupCacheListener, nsIObserver)
nsresult
StartupCacheListener::Observe(nsISupports *subject, const char* topic, const PRUnichar* data)
@@ -555,16 +596,17 @@ StartupCacheWrapper::GetDebugObjectOutpu
nsresult
StartupCacheWrapper::StartupWriteComplete(PRBool *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();
--- a/startupcache/StartupCache.h
+++ b/startupcache/StartupCache.h
@@ -151,33 +151,35 @@ public:
private:
StartupCache();
~StartupCache();
nsresult LoadArchive();
nsresult Init();
void WriteToDisk();
nsresult ResetStartupWriteTimer();
+ void WaitOnWriteThread();
static nsresult InitSingleton();
static void WriteTimeout(nsITimer *aTimer, void *aClosure);
+ static void ThreadedWrite(void *aClosure);
nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
nsAutoPtr<nsZipArchive> mArchive;
nsCOMPtr<nsILocalFile> mFile;
nsCOMPtr<nsIObserverService> mObserverService;
nsRefPtr<StartupCacheListener> mListener;
nsCOMPtr<nsITimer> mTimer;
PRBool mStartupWriteInitiated;
static StartupCache *gStartupCache;
static PRBool gShutdownInitiated;
-
+ PRThread *mWriteThread;
#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.