author | Graeme 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 id | 23798 |
push user | ryanvm@gmail.com |
push date | Sat, 03 Nov 2012 00:06:35 +0000 |
treeherder | mozilla-central@6134edeea902 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mwu |
bugs | 724513 |
milestone | 19.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
|
--- 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;