Bug 586859 - Move startupcache write off the main thread r=cjones a=2.0
authorTaras Glek <tglek@mozilla.com>
Thu, 20 Jan 2011 13:40:45 -0800
changeset 61000 ff8275cbf3b15b7e27f690ff3229e2a8c62e89fb
parent 60999 663b7d2d4b6d909c7deb448640a0ea4fc2644d00
child 61001 22a635e15db1dd55a368869e271ea8068e0dee2b
push id18187
push usertglek@mozilla.com
push dateThu, 20 Jan 2011 21:41:03 +0000
treeherdermozilla-central@ff8275cbf3b1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjones, 2.0
bugs586859
milestone2.0b10pre
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
Bug 586859 - Move startupcache write off the main thread r=cjones a=2.0
startupcache/StartupCache.cpp
--- a/startupcache/StartupCache.cpp
+++ b/startupcache/StartupCache.cpp
@@ -59,16 +59,17 @@
 #include "nsISupports.h"
 #include "nsITimer.h"
 #include "nsIZipWriter.h"
 #include "nsIZipReader.h"
 #include "nsWeakReference.h"
 #include "nsZipArchive.h"
 #include "mozilla/Omnijar.h"
 #include "prenv.h"
+#include "mozilla/FunctionTimer.h"
  
 #ifdef IS_BIG_ENDIAN
 #define SC_ENDIAN "big"
 #else
 #define SC_ENDIAN "little"
 #endif
 
 #if PR_BYTES_PER_WORD == 4
@@ -111,17 +112,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,
@@ -198,31 +199,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;
@@ -252,16 +255,17 @@ StartupCache::GetBuffer(const char* id, 
 #endif
   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);
@@ -303,27 +307,28 @@ CacheCloseHelper(const nsACString& key, 
   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, holder->time, PR_FALSE, stream, false);
+  rv = writer->AddEntryStream(key, holder->time, PR_TRUE, stream, PR_FALSE);
   
   if (NS_FAILED(rv)) {
     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)
@@ -347,55 +352,93 @@ 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);
+  //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)
 {
-  nsresult rv = NS_OK;
+  StartupCache* sc = StartupCache::GetSingleton();
+  if (!sc)
+    return NS_OK;
+
   if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+    // Do not leave the thread running past xpcom shutdown
+    sc->WaitOnWriteThread();
     StartupCache::gShutdownInitiated = PR_TRUE;
   } else if (strcmp(topic, "startupcache-invalidate") == 0) {
-    StartupCache* sc = StartupCache::GetSingleton();
-    if (sc)
-      sc->InvalidateCache();
+    sc->InvalidateCache();
   }
-  return rv;
+  return NS_OK;
 } 
 
 nsresult
 StartupCache::GetDebugObjectOutputStream(nsIObjectOutputStream* aStream,
                                          nsIObjectOutputStream** aOutStream) 
 {
   NS_ENSURE_ARG_POINTER(aStream);
 #ifdef DEBUG
@@ -577,16 +620,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();