Bug 586859: Move startup cache writing off main thread r=cjones a=blocking-final
authorTaras Glek <tglek@mozilla.com>
Fri, 07 Jan 2011 10:56:15 -0800
changeset 60134 c2cc892c5d5313e8888f64b13b3b3eb17cf9d2ab
parent 60133 908f598737d76952428b1d62eeacca896a7168fb
child 60135 30e1cfee35452d8a7789b15c618918d2b750746f
push idunknown
push userunknown
push dateunknown
reviewerscjones, blocking-final
bugs586859
milestone2.0b9pre
Bug 586859: Move startup cache writing off main thread r=cjones a=blocking-final
startupcache/StartupCache.cpp
startupcache/StartupCache.h
--- 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.