Bug 724513 - Part 1 - Add StartupCache method for disregarding disk file. r=mwu
authorGraeme McCutcheon <graememcc_firefox@graeme-online.co.uk>
Thu, 11 Oct 2012 09:17:15 +0100
changeset 112150 eb5c570b8a0bf064a26b46cb6e8274b52b75eaa0
parent 112149 e692328bf6a33db14810fa19c51d7ba105db386a
child 112151 6ec71de7ba2d66438d9ee4ee03628ada27999c3c
push id23798
push userryanvm@gmail.com
push dateSat, 03 Nov 2012 00:06:35 +0000
treeherdermozilla-central@6134edeea902 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmwu
bugs724513
milestone19.0a1
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 724513 - Part 1 - Add StartupCache method for disregarding disk file. r=mwu
startupcache/StartupCache.cpp
startupcache/StartupCache.h
startupcache/nsIStartupCache.idl
startupcache/test/TestStartupCache.cpp
--- a/startupcache/StartupCache.cpp
+++ b/startupcache/StartupCache.cpp
@@ -117,16 +117,17 @@ StartupCache::InitSingleton()
     delete StartupCache::gStartupCache;
     StartupCache::gStartupCache = nullptr;
   }
   return rv;
 }
 
 StartupCache* StartupCache::gStartupCache;
 bool StartupCache::gShutdownInitiated;
+bool StartupCache::gIgnoreDiskCache;
 enum StartupCache::TelemetrifyAge StartupCache::gPostFlushAgeAction = StartupCache::IGNORE_AGE;
 
 StartupCache::StartupCache() 
   : mArchive(NULL), mStartupWriteInitiated(false), mWriteThread(NULL),
     mMappingMemoryReporter(nullptr), mDataMemoryReporter(nullptr) { }
 
 StartupCache::~StartupCache() 
 {
@@ -198,22 +199,22 @@ StartupCache::Init()
   
   mListener = new StartupCacheListener();  
   rv = mObserverService->AddObserver(mListener, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
                                      false);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mObserverService->AddObserver(mListener, "startupcache-invalidate",
                                      false);
   NS_ENSURE_SUCCESS(rv, rv);
-  
+
   rv = LoadArchive(RECORD_AGE);
   
   // 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) {
+  if (gIgnoreDiskCache || (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)) {
     NS_WARNING("Failed to load startupcache file correctly, removing!");
     InvalidateCache();
   }
 
   mMappingMemoryReporter = new NS_MEMORY_REPORTER_NAME(StartupCacheMapping);
   mDataMemoryReporter    = new NS_MEMORY_REPORTER_NAME(StartupCacheData);
   (void)::NS_RegisterMemoryReporter(mMappingMemoryReporter);
   (void)::NS_RegisterMemoryReporter(mDataMemoryReporter);
@@ -222,16 +223,19 @@ StartupCache::Init()
 }
 
 /** 
  * LoadArchive can be called from the main thread or while reloading cache on write thread.
  */
 nsresult
 StartupCache::LoadArchive(enum TelemetrifyAge flag)
 {
+  if (gIgnoreDiskCache)
+    return NS_ERROR_FAILURE;
+
   bool exists;
   mArchive = NULL;
   nsresult rv = mFile->Exists(&exists);
   if (NS_FAILED(rv) || !exists)
     return NS_ERROR_FILE_NOT_FOUND;
   
   mArchive = new nsZipArchive();
   rv = mArchive->OpenArchive(mFile);
@@ -453,32 +457,49 @@ StartupCache::WriteToDisk()
   holder.time = now;
 
   mTable.Enumerate(CacheCloseHelper, &holder);
 
   // Close the archive so Windows doesn't choke.
   mArchive = NULL;
   zipW->Close();
 
+  // We succesfully wrote the archive to disk; mark the disk file as trusted
+  gIgnoreDiskCache = false;
+
   // Our reader's view of the archive is outdated now, reload it.
   LoadArchive(gPostFlushAgeAction);
   
   return;
 }
 
 void
 StartupCache::InvalidateCache() 
 {
   WaitOnWriteThread();
   mTable.Clear();
   mArchive = NULL;
-  mFile->Remove(false);
+  nsresult rv = mFile->Remove(false);
+  if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
+      rv != NS_ERROR_FILE_NOT_FOUND) {
+    gIgnoreDiskCache = true;
+    return;
+  }
+  gIgnoreDiskCache = false;
   LoadArchive(gPostFlushAgeAction);
 }
 
+void
+StartupCache::IgnoreDiskCache()
+{
+  gIgnoreDiskCache = true;
+  if (gStartupCache)
+    gStartupCache->InvalidateCache();
+}
+
 /*
  * 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()
 {
@@ -708,16 +729,23 @@ StartupCacheWrapper::InvalidateCache()
   StartupCache* sc = StartupCache::GetSingleton();
   if (!sc) {
     return NS_ERROR_NOT_INITIALIZED;
   }
   sc->InvalidateCache();
   return NS_OK;
 }
 
+nsresult
+StartupCacheWrapper::IgnoreDiskCache()
+{
+  StartupCache::IgnoreDiskCache();
+  return NS_OK;
+}
+
 nsresult 
 StartupCacheWrapper::GetDebugObjectOutputStream(nsIObjectOutputStream* stream,
                                                 nsIObjectOutputStream** outStream) 
 {
   StartupCache* sc = StartupCache::GetSingleton();
   if (!sc) {
     return NS_ERROR_NOT_INITIALIZED;
   }
--- a/startupcache/StartupCache.h
+++ b/startupcache/StartupCache.h
@@ -39,16 +39,22 @@
  * 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.
+ * Additionally, the static method IgnoreDiskCache() can be called if it is
+ * believed that the on-disk cache file is itself corrupt. This call implicitly
+ * calls InvalidateCache (if the singleton has been initialized) to ensure any
+ * data already read from disk is discarded. The cache will not load data from
+ * the disk file until a successful write occurs.
+ *
  * 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()
@@ -109,16 +115,19 @@ public:
   nsresult GetBuffer(const char* id, char** outbuf, uint32_t* length);
 
   // Stores a buffer. Caller keeps ownership, we make a copy.
   nsresult PutBuffer(const char* id, const char* inbuf, uint32_t length);
 
   // Removes the cache file.
   void InvalidateCache();
 
+  // Signal that data should not be loaded from the cache file
+  static void IgnoreDiskCache();
+
   // 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);
 
   nsresult RecordAgesAlways();
 
   static StartupCache* GetSingleton();
@@ -162,16 +171,17 @@ private:
   nsCOMPtr<nsIObserverService> mObserverService;
   nsRefPtr<StartupCacheListener> mListener;
   nsCOMPtr<nsITimer> mTimer;
 
   bool mStartupWriteInitiated;
 
   static StartupCache *gStartupCache;
   static bool gShutdownInitiated;
+  static bool gIgnoreDiskCache;
   PRThread *mWriteThread;
 #ifdef DEBUG
   nsTHashtable<nsISupportsHashKey> mWriteObjectMap;
 #endif
 
   nsIMemoryReporter* mMappingMemoryReporter;
   nsIMemoryReporter* mDataMemoryReporter;
 };
--- a/startupcache/nsIStartupCache.idl
+++ b/startupcache/nsIStartupCache.idl
@@ -4,31 +4,33 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIInputStream.idl"
 #include "nsISupports.idl"
 #include "nsIObserver.idl"
 #include "nsIObjectOutputStream.idl"
 
-[uuid(c1b3796b-33af-4ff0-b83d-8eb0ca2c080f)]
+[uuid(25957820-90a1-428c-8739-b0845d3cc534)]
 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. */
 
   uint32_t getBuffer(in string aID, out charPtr aBuffer);
   void putBuffer(in string aID, in string aBuffer, 
                             in uint32_t aLength);
  
   void invalidateCache();
   
+  void ignoreDiskCache();
+
   /** 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
--- a/startupcache/test/TestStartupCache.cpp
+++ b/startupcache/test/TestStartupCache.cpp
@@ -17,16 +17,17 @@
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIURI.h"
 #include "nsStringAPI.h"
 #include "nsIPrefBranch.h"
 #include "nsIPrefService.h"
 #include "nsITelemetry.h"
 #include "jsapi.h"
+#include "prio.h"
 
 namespace mozilla {
 namespace scache {
 
 NS_IMPORT nsresult
 NewObjectInputStreamFromBuffer(char* buffer, uint32_t len, 
                                nsIObjectInputStream** stream);
 
@@ -51,17 +52,17 @@ if (0 != strcmp(str1, str2)) {          
   return NS_ERROR_FAILURE;                         \
 }                                                  \
 passed("passed " testname);                        \
 PR_END_MACRO
 
 nsresult
 WaitForStartupTimer() {
   nsresult rv;
-  nsCOMPtr<nsIStartupCache> sc 
+  nsCOMPtr<nsIStartupCache> sc
     = do_GetService("@mozilla.org/startupcache/cache;1");
   PR_Sleep(10 * PR_TicksPerSecond());
   
   bool complete;
   while (true) {
     
     NS_ProcessPendingEvents(nullptr);
     rv = sc->StartupWriteComplete(&complete);
@@ -70,17 +71,17 @@ WaitForStartupTimer() {
     PR_Sleep(1 * PR_TicksPerSecond());
   }
   return rv;
 }
 
 nsresult
 TestStartupWriteRead() {
   nsresult rv;
-  nsCOMPtr<nsIStartupCache> sc 
+  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();
@@ -113,17 +114,17 @@ TestStartupWriteRead() {
 
 nsresult
 TestWriteInvalidateRead() {
   nsresult rv;
   const char* buf = "BeardBook competitive analysis";
   const char* id = "id";
   char* outbuf = NULL;
   uint32_t len;
-  nsCOMPtr<nsIStartupCache> sc 
+  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();
 
@@ -243,19 +244,119 @@ TestWriteObject() {
     return rv;
   }
   
   passed("write object");
   return NS_OK;
 }
 
 nsresult
+LockCacheFile(bool protect, nsIFile* profileDir) {
+  NS_ENSURE_ARG(profileDir);
+
+  nsCOMPtr<nsIFile> startupCache;
+  profileDir->Clone(getter_AddRefs(startupCache));
+  NS_ENSURE_STATE(startupCache);
+  startupCache->AppendNative(NS_LITERAL_CSTRING("startupCache"));
+
+  nsresult rv;
+#ifndef XP_WIN
+  static uint32_t oldPermissions;
+#else
+  static PRFileDesc* fd = nullptr;
+#endif
+
+  // To prevent deletion of the startupcache file, we change the containing
+  // directory's permissions on Linux/Mac, and hold the file open on Windows
+  if (protect) {
+#ifndef XP_WIN
+    rv = startupCache->GetPermissions(&oldPermissions);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = startupCache->SetPermissions(0555);
+    NS_ENSURE_SUCCESS(rv, rv);
+#else
+    // Filename logic from StartupCache.cpp
+    #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
+    char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN;
+    startupCache->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName));
+
+    rv = startupCache->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
+    NS_ENSURE_SUCCESS(rv, rv);
+#endif
+  } else {
+#ifndef XP_WIN
+    rv = startupCache->SetPermissions(oldPermissions);
+    NS_ENSURE_SUCCESS(rv, rv);
+#else
+   PR_Close(fd);
+#endif
+  }
+
+  return NS_OK;
+}
+
+nsresult
+TestIgnoreDiskCache(nsIFile* profileDir) {
+  nsresult rv;
+  nsCOMPtr<nsIStartupCache> sc
+    = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
+  sc->InvalidateCache();
+  
+  const char* buf = "Get a Beardbook app for your smartphone";
+  const char* id = "id";
+  char* outbuf = NULL;
+  PRUint32 len;
+  
+  rv = sc->PutBuffer(id, buf, strlen(buf) + 1);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = sc->ResetStartupWriteTimer();
+  rv = WaitForStartupTimer();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Prevent StartupCache::InvalidateCache from deleting the disk file
+  rv = LockCacheFile(true, profileDir);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  sc->IgnoreDiskCache();
+
+  rv = sc->GetBuffer(id, &outbuf, &len);
+
+  nsresult r = LockCacheFile(false, profileDir);
+  NS_ENSURE_SUCCESS(r, r);
+
+  delete[] outbuf;
+
+  if (rv == NS_ERROR_NOT_AVAILABLE) {
+    passed("buffer not available after ignoring disk cache");
+  } else if (NS_SUCCEEDED(rv)) {
+    fail("GetBuffer succeeded unexpectedly after ignoring disk cache");
+    return NS_ERROR_UNEXPECTED;
+  } else {
+    fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE");
+    return rv;
+  }
+
+  sc->InvalidateCache();
+  return NS_OK;
+}
+
+nsresult
 TestEarlyShutdown() {
   nsresult rv;
-  nsCOMPtr<nsIStartupCache> sc 
+  nsCOMPtr<nsIStartupCache> sc
     = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
   sc->InvalidateCache();
 
   const char* buf = "Find your soul beardmate on BeardBook";
   const char* id = "id";
   uint32_t len;
   char* outbuf = NULL;
   
@@ -411,16 +512,19 @@ int main(int argc, char** argv)
   else
     sc->RecordAgesAlways();
   if (NS_FAILED(TestStartupWriteRead()))
     rv = 1;
   if (NS_FAILED(TestWriteInvalidateRead()))
     rv = 1;
   if (NS_FAILED(TestWriteObject()))
     rv = 1;
+  nsCOMPtr<nsIFile> profileDir = xpcom.GetProfileDirectory();
+  if (NS_FAILED(TestIgnoreDiskCache(profileDir)))
+    rv = 1;
   if (NS_FAILED(TestEarlyShutdown()))
     rv = 1;
 
   JS::AutoValueRooter after_counts(cx);
   if (use_js && !GetHistogramCounts("STARTUP_CACHE_AGE_HOURS histogram after test",
                                     cx, after_counts.addr()))
     use_js = false;