1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/startupcache/StartupCache.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,832 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "prio.h" 1.11 +#include "pldhash.h" 1.12 +#include "nsXPCOMStrings.h" 1.13 +#include "mozilla/IOInterposer.h" 1.14 +#include "mozilla/MemoryReporting.h" 1.15 +#include "mozilla/scache/StartupCache.h" 1.16 + 1.17 +#include "nsAutoPtr.h" 1.18 +#include "nsClassHashtable.h" 1.19 +#include "nsComponentManagerUtils.h" 1.20 +#include "nsDirectoryServiceUtils.h" 1.21 +#include "nsIClassInfo.h" 1.22 +#include "nsIFile.h" 1.23 +#include "nsIObserver.h" 1.24 +#include "nsIObserverService.h" 1.25 +#include "nsIOutputStream.h" 1.26 +#include "nsIStartupCache.h" 1.27 +#include "nsIStorageStream.h" 1.28 +#include "nsIStreamBufferAccess.h" 1.29 +#include "nsIStringStream.h" 1.30 +#include "nsISupports.h" 1.31 +#include "nsITimer.h" 1.32 +#include "nsIZipWriter.h" 1.33 +#include "nsIZipReader.h" 1.34 +#include "nsWeakReference.h" 1.35 +#include "nsZipArchive.h" 1.36 +#include "mozilla/Omnijar.h" 1.37 +#include "prenv.h" 1.38 +#include "mozilla/Telemetry.h" 1.39 +#include "nsThreadUtils.h" 1.40 +#include "nsXULAppAPI.h" 1.41 +#include "nsIProtocolHandler.h" 1.42 + 1.43 +#ifdef IS_BIG_ENDIAN 1.44 +#define SC_ENDIAN "big" 1.45 +#else 1.46 +#define SC_ENDIAN "little" 1.47 +#endif 1.48 + 1.49 +#if PR_BYTES_PER_WORD == 4 1.50 +#define SC_WORDSIZE "4" 1.51 +#else 1.52 +#define SC_WORDSIZE "8" 1.53 +#endif 1.54 + 1.55 +namespace mozilla { 1.56 +namespace scache { 1.57 + 1.58 +MOZ_DEFINE_MALLOC_SIZE_OF(StartupCacheMallocSizeOf) 1.59 + 1.60 +NS_IMETHODIMP 1.61 +StartupCache::CollectReports(nsIHandleReportCallback* aHandleReport, 1.62 + nsISupports* aData) 1.63 +{ 1.64 +#define REPORT(_path, _kind, _amount, _desc) \ 1.65 + do { \ 1.66 + nsresult rv = \ 1.67 + aHandleReport->Callback(EmptyCString(), \ 1.68 + NS_LITERAL_CSTRING(_path), \ 1.69 + _kind, UNITS_BYTES, _amount, \ 1.70 + NS_LITERAL_CSTRING(_desc), aData); \ 1.71 + NS_ENSURE_SUCCESS(rv, rv); \ 1.72 + } while (0) 1.73 + 1.74 + REPORT("explicit/startup-cache/mapping", KIND_NONHEAP, 1.75 + SizeOfMapping(), 1.76 + "Memory used to hold the mapping of the startup cache from file. " 1.77 + "This memory is likely to be swapped out shortly after start-up."); 1.78 + 1.79 + REPORT("explicit/startup-cache/data", KIND_HEAP, 1.80 + HeapSizeOfIncludingThis(StartupCacheMallocSizeOf), 1.81 + "Memory used by the startup cache for things other than the file " 1.82 + "mapping."); 1.83 + 1.84 + return NS_OK; 1.85 +} 1.86 + 1.87 +static const char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN; 1.88 +#if defined(XP_WIN) && defined(MOZ_METRO) 1.89 +static const char sMetroStartupCacheName[] = "metroStartupCache." SC_WORDSIZE "." SC_ENDIAN; 1.90 +#endif 1.91 + 1.92 +StartupCache* 1.93 +StartupCache::GetSingleton() 1.94 +{ 1.95 + if (!gStartupCache) { 1.96 + if (XRE_GetProcessType() != GeckoProcessType_Default) { 1.97 + return nullptr; 1.98 + } 1.99 +#ifdef MOZ_B2G 1.100 + return nullptr; 1.101 +#endif 1.102 + 1.103 + StartupCache::InitSingleton(); 1.104 + } 1.105 + 1.106 + return StartupCache::gStartupCache; 1.107 +} 1.108 + 1.109 +void 1.110 +StartupCache::DeleteSingleton() 1.111 +{ 1.112 + StartupCache::gStartupCache = nullptr; 1.113 +} 1.114 + 1.115 +nsresult 1.116 +StartupCache::InitSingleton() 1.117 +{ 1.118 + nsresult rv; 1.119 + StartupCache::gStartupCache = new StartupCache(); 1.120 + 1.121 + rv = StartupCache::gStartupCache->Init(); 1.122 + if (NS_FAILED(rv)) { 1.123 + StartupCache::gStartupCache = nullptr; 1.124 + } 1.125 + return rv; 1.126 +} 1.127 + 1.128 +StaticRefPtr<StartupCache> StartupCache::gStartupCache; 1.129 +bool StartupCache::gShutdownInitiated; 1.130 +bool StartupCache::gIgnoreDiskCache; 1.131 +enum StartupCache::TelemetrifyAge StartupCache::gPostFlushAgeAction = StartupCache::IGNORE_AGE; 1.132 + 1.133 +NS_IMPL_ISUPPORTS(StartupCache, nsIMemoryReporter) 1.134 + 1.135 +StartupCache::StartupCache() 1.136 + : mArchive(nullptr), mStartupWriteInitiated(false), mWriteThread(nullptr) 1.137 +{ } 1.138 + 1.139 +StartupCache::~StartupCache() 1.140 +{ 1.141 + if (mTimer) { 1.142 + mTimer->Cancel(); 1.143 + } 1.144 + 1.145 + // Generally, the in-memory table should be empty here, 1.146 + // but an early shutdown means either mTimer didn't run 1.147 + // or the write thread is still running. 1.148 + WaitOnWriteThread(); 1.149 + 1.150 + // If we shutdown quickly timer wont have fired. Instead of writing 1.151 + // it on the main thread and block the shutdown we simply wont update 1.152 + // the startup cache. Always do this if the file doesn't exist since 1.153 + // we use it part of the package step. 1.154 + if (!mArchive) { 1.155 + WriteToDisk(); 1.156 + } 1.157 + 1.158 + UnregisterWeakMemoryReporter(this); 1.159 +} 1.160 + 1.161 +nsresult 1.162 +StartupCache::Init() 1.163 +{ 1.164 + // workaround for bug 653936 1.165 + nsCOMPtr<nsIProtocolHandler> jarInitializer(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "jar")); 1.166 + 1.167 + nsresult rv; 1.168 + 1.169 + // This allows to override the startup cache filename 1.170 + // which is useful from xpcshell, when there is no ProfLDS directory to keep cache in. 1.171 + char *env = PR_GetEnv("MOZ_STARTUP_CACHE"); 1.172 + if (env) { 1.173 + rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, getter_AddRefs(mFile)); 1.174 + } else { 1.175 + nsCOMPtr<nsIFile> file; 1.176 + rv = NS_GetSpecialDirectory("ProfLDS", 1.177 + getter_AddRefs(file)); 1.178 + if (NS_FAILED(rv)) { 1.179 + // return silently, this will fail in mochitests's xpcshell process. 1.180 + return rv; 1.181 + } 1.182 + 1.183 + nsCOMPtr<nsIFile> profDir; 1.184 + NS_GetSpecialDirectory("ProfDS", getter_AddRefs(profDir)); 1.185 + if (profDir) { 1.186 + bool same; 1.187 + if (NS_SUCCEEDED(profDir->Equals(file, &same)) && !same) { 1.188 + // We no longer store the startup cache in the main profile 1.189 + // directory, so we should cleanup the old one. 1.190 + if (NS_SUCCEEDED( 1.191 + profDir->AppendNative(NS_LITERAL_CSTRING("startupCache")))) { 1.192 + profDir->Remove(true); 1.193 + } 1.194 + } 1.195 + } 1.196 + 1.197 + rv = file->AppendNative(NS_LITERAL_CSTRING("startupCache")); 1.198 + NS_ENSURE_SUCCESS(rv, rv); 1.199 + 1.200 + // Try to create the directory if it's not there yet 1.201 + rv = file->Create(nsIFile::DIRECTORY_TYPE, 0777); 1.202 + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) 1.203 + return rv; 1.204 + 1.205 +#if defined(XP_WIN) && defined(MOZ_METRO) 1.206 + if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Metro) { 1.207 + rv = file->AppendNative(NS_LITERAL_CSTRING(sMetroStartupCacheName)); 1.208 + } else 1.209 +#endif 1.210 + { 1.211 + rv = file->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName)); 1.212 + } 1.213 + 1.214 + NS_ENSURE_SUCCESS(rv, rv); 1.215 + 1.216 + mFile = do_QueryInterface(file); 1.217 + } 1.218 + 1.219 + NS_ENSURE_TRUE(mFile, NS_ERROR_UNEXPECTED); 1.220 + 1.221 + mObserverService = do_GetService("@mozilla.org/observer-service;1"); 1.222 + 1.223 + if (!mObserverService) { 1.224 + NS_WARNING("Could not get observerService."); 1.225 + return NS_ERROR_UNEXPECTED; 1.226 + } 1.227 + 1.228 + mListener = new StartupCacheListener(); 1.229 + rv = mObserverService->AddObserver(mListener, NS_XPCOM_SHUTDOWN_OBSERVER_ID, 1.230 + false); 1.231 + NS_ENSURE_SUCCESS(rv, rv); 1.232 + rv = mObserverService->AddObserver(mListener, "startupcache-invalidate", 1.233 + false); 1.234 + NS_ENSURE_SUCCESS(rv, rv); 1.235 + 1.236 + rv = LoadArchive(RECORD_AGE); 1.237 + 1.238 + // Sometimes we don't have a cache yet, that's ok. 1.239 + // If it's corrupted, just remove it and start over. 1.240 + if (gIgnoreDiskCache || (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)) { 1.241 + NS_WARNING("Failed to load startupcache file correctly, removing!"); 1.242 + InvalidateCache(); 1.243 + } 1.244 + 1.245 + RegisterWeakMemoryReporter(this); 1.246 + 1.247 + return NS_OK; 1.248 +} 1.249 + 1.250 +/** 1.251 + * LoadArchive can be called from the main thread or while reloading cache on write thread. 1.252 + */ 1.253 +nsresult 1.254 +StartupCache::LoadArchive(enum TelemetrifyAge flag) 1.255 +{ 1.256 + if (gIgnoreDiskCache) 1.257 + return NS_ERROR_FAILURE; 1.258 + 1.259 + bool exists; 1.260 + mArchive = nullptr; 1.261 + nsresult rv = mFile->Exists(&exists); 1.262 + if (NS_FAILED(rv) || !exists) 1.263 + return NS_ERROR_FILE_NOT_FOUND; 1.264 + 1.265 + mArchive = new nsZipArchive(); 1.266 + rv = mArchive->OpenArchive(mFile); 1.267 + if (NS_FAILED(rv) || flag == IGNORE_AGE) 1.268 + return rv; 1.269 + 1.270 + nsCString comment; 1.271 + if (!mArchive->GetComment(comment)) { 1.272 + return rv; 1.273 + } 1.274 + 1.275 + const char *data; 1.276 + size_t len = NS_CStringGetData(comment, &data); 1.277 + PRTime creationStamp; 1.278 + // We might not have a comment if the startup cache file was created 1.279 + // before we started recording creation times in the comment. 1.280 + if (len == sizeof(creationStamp)) { 1.281 + memcpy(&creationStamp, data, len); 1.282 + PRTime current = PR_Now(); 1.283 + int64_t diff = current - creationStamp; 1.284 + 1.285 + // We can't use AccumulateTimeDelta here because we have no way of 1.286 + // reifying a TimeStamp from creationStamp. 1.287 + int64_t usec_per_hour = PR_USEC_PER_SEC * int64_t(3600); 1.288 + int64_t hour_diff = (diff + usec_per_hour - 1) / usec_per_hour; 1.289 + mozilla::Telemetry::Accumulate(Telemetry::STARTUP_CACHE_AGE_HOURS, 1.290 + hour_diff); 1.291 + } 1.292 + 1.293 + return rv; 1.294 +} 1.295 + 1.296 +namespace { 1.297 + 1.298 +nsresult 1.299 +GetBufferFromZipArchive(nsZipArchive *zip, bool doCRC, const char* id, 1.300 + char** outbuf, uint32_t* length) 1.301 +{ 1.302 + if (!zip) 1.303 + return NS_ERROR_NOT_AVAILABLE; 1.304 + 1.305 + nsZipItemPtr<char> zipItem(zip, id, doCRC); 1.306 + if (!zipItem) 1.307 + return NS_ERROR_NOT_AVAILABLE; 1.308 + 1.309 + *outbuf = zipItem.Forget(); 1.310 + *length = zipItem.Length(); 1.311 + return NS_OK; 1.312 +} 1.313 + 1.314 +} /* anonymous namespace */ 1.315 + 1.316 +// NOTE: this will not find a new entry until it has been written to disk! 1.317 +// Consumer should take ownership of the resulting buffer. 1.318 +nsresult 1.319 +StartupCache::GetBuffer(const char* id, char** outbuf, uint32_t* length) 1.320 +{ 1.321 + NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread"); 1.322 + WaitOnWriteThread(); 1.323 + if (!mStartupWriteInitiated) { 1.324 + CacheEntry* entry; 1.325 + nsDependentCString idStr(id); 1.326 + mTable.Get(idStr, &entry); 1.327 + if (entry) { 1.328 + *outbuf = new char[entry->size]; 1.329 + memcpy(*outbuf, entry->data, entry->size); 1.330 + *length = entry->size; 1.331 + return NS_OK; 1.332 + } 1.333 + } 1.334 + 1.335 + nsresult rv = GetBufferFromZipArchive(mArchive, true, id, outbuf, length); 1.336 + if (NS_SUCCEEDED(rv)) 1.337 + return rv; 1.338 + 1.339 + nsRefPtr<nsZipArchive> omnijar = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); 1.340 + // no need to checksum omnijarred entries 1.341 + rv = GetBufferFromZipArchive(omnijar, false, id, outbuf, length); 1.342 + if (NS_SUCCEEDED(rv)) 1.343 + return rv; 1.344 + 1.345 + omnijar = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); 1.346 + // no need to checksum omnijarred entries 1.347 + return GetBufferFromZipArchive(omnijar, false, id, outbuf, length); 1.348 +} 1.349 + 1.350 +// Makes a copy of the buffer, client retains ownership of inbuf. 1.351 +nsresult 1.352 +StartupCache::PutBuffer(const char* id, const char* inbuf, uint32_t len) 1.353 +{ 1.354 + NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread"); 1.355 + WaitOnWriteThread(); 1.356 + if (StartupCache::gShutdownInitiated) { 1.357 + return NS_ERROR_NOT_AVAILABLE; 1.358 + } 1.359 + 1.360 + nsAutoArrayPtr<char> data(new char[len]); 1.361 + memcpy(data, inbuf, len); 1.362 + 1.363 + nsDependentCString idStr(id); 1.364 + // Cache it for now, we'll write all together later. 1.365 + CacheEntry* entry; 1.366 + 1.367 +#ifdef DEBUG 1.368 + mTable.Get(idStr, &entry); 1.369 + NS_ASSERTION(entry == nullptr, "Existing entry in StartupCache."); 1.370 + 1.371 + if (mArchive) { 1.372 + nsZipItem* zipItem = mArchive->GetItem(id); 1.373 + NS_ASSERTION(zipItem == nullptr, "Existing entry in disk StartupCache."); 1.374 + } 1.375 +#endif 1.376 + 1.377 + entry = new CacheEntry(data.forget(), len); 1.378 + mTable.Put(idStr, entry); 1.379 + return ResetStartupWriteTimer(); 1.380 +} 1.381 + 1.382 +size_t 1.383 +StartupCache::SizeOfMapping() 1.384 +{ 1.385 + return mArchive ? mArchive->SizeOfMapping() : 0; 1.386 +} 1.387 + 1.388 +size_t 1.389 +StartupCache::HeapSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) 1.390 +{ 1.391 + // This function could measure more members, but they haven't been found by 1.392 + // DMD to be significant. They can be added later if necessary. 1.393 + return aMallocSizeOf(this) + 1.394 + mTable.SizeOfExcludingThis(SizeOfEntryExcludingThis, aMallocSizeOf); 1.395 +} 1.396 + 1.397 +/* static */ size_t 1.398 +StartupCache::SizeOfEntryExcludingThis(const nsACString& key, const nsAutoPtr<CacheEntry>& data, 1.399 + mozilla::MallocSizeOf mallocSizeOf, void *) 1.400 +{ 1.401 + return data->SizeOfExcludingThis(mallocSizeOf); 1.402 +} 1.403 + 1.404 +struct CacheWriteHolder 1.405 +{ 1.406 + nsCOMPtr<nsIZipWriter> writer; 1.407 + nsCOMPtr<nsIStringInputStream> stream; 1.408 + PRTime time; 1.409 +}; 1.410 + 1.411 +PLDHashOperator 1.412 +CacheCloseHelper(const nsACString& key, nsAutoPtr<CacheEntry>& data, 1.413 + void* closure) 1.414 +{ 1.415 + nsresult rv; 1.416 + 1.417 + CacheWriteHolder* holder = (CacheWriteHolder*) closure; 1.418 + nsIStringInputStream* stream = holder->stream; 1.419 + nsIZipWriter* writer = holder->writer; 1.420 + 1.421 + stream->ShareData(data->data, data->size); 1.422 + 1.423 +#ifdef DEBUG 1.424 + bool hasEntry; 1.425 + rv = writer->HasEntry(key, &hasEntry); 1.426 + NS_ASSERTION(NS_SUCCEEDED(rv) && hasEntry == false, 1.427 + "Existing entry in disk StartupCache."); 1.428 +#endif 1.429 + rv = writer->AddEntryStream(key, holder->time, true, stream, false); 1.430 + 1.431 + if (NS_FAILED(rv)) { 1.432 + NS_WARNING("cache entry deleted but not written to disk."); 1.433 + } 1.434 + return PL_DHASH_REMOVE; 1.435 +} 1.436 + 1.437 + 1.438 +/** 1.439 + * WriteToDisk writes the cache out to disk. Callers of WriteToDisk need to call WaitOnWriteThread 1.440 + * to make sure there isn't a write happening on another thread 1.441 + */ 1.442 +void 1.443 +StartupCache::WriteToDisk() 1.444 +{ 1.445 + nsresult rv; 1.446 + mStartupWriteInitiated = true; 1.447 + 1.448 + if (mTable.Count() == 0) 1.449 + return; 1.450 + 1.451 + nsCOMPtr<nsIZipWriter> zipW = do_CreateInstance("@mozilla.org/zipwriter;1"); 1.452 + if (!zipW) 1.453 + return; 1.454 + 1.455 + rv = zipW->Open(mFile, PR_RDWR | PR_CREATE_FILE); 1.456 + if (NS_FAILED(rv)) { 1.457 + NS_WARNING("could not open zipfile for write"); 1.458 + return; 1.459 + } 1.460 + 1.461 + // If we didn't have an mArchive member, that means that we failed to 1.462 + // open the startup cache for reading. Therefore, we need to record 1.463 + // the time of creation in a zipfile comment; this will be useful for 1.464 + // Telemetry statistics. 1.465 + PRTime now = PR_Now(); 1.466 + if (!mArchive) { 1.467 + nsCString comment; 1.468 + comment.Assign((char *)&now, sizeof(now)); 1.469 + zipW->SetComment(comment); 1.470 + } 1.471 + 1.472 + nsCOMPtr<nsIStringInputStream> stream 1.473 + = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); 1.474 + if (NS_FAILED(rv)) { 1.475 + NS_WARNING("Couldn't create string input stream."); 1.476 + return; 1.477 + } 1.478 + 1.479 + CacheWriteHolder holder; 1.480 + holder.stream = stream; 1.481 + holder.writer = zipW; 1.482 + holder.time = now; 1.483 + 1.484 + mTable.Enumerate(CacheCloseHelper, &holder); 1.485 + 1.486 + // Close the archive so Windows doesn't choke. 1.487 + mArchive = nullptr; 1.488 + zipW->Close(); 1.489 + 1.490 + // We succesfully wrote the archive to disk; mark the disk file as trusted 1.491 + gIgnoreDiskCache = false; 1.492 + 1.493 + // Our reader's view of the archive is outdated now, reload it. 1.494 + LoadArchive(gPostFlushAgeAction); 1.495 + 1.496 + return; 1.497 +} 1.498 + 1.499 +void 1.500 +StartupCache::InvalidateCache() 1.501 +{ 1.502 + WaitOnWriteThread(); 1.503 + mTable.Clear(); 1.504 + mArchive = nullptr; 1.505 + nsresult rv = mFile->Remove(false); 1.506 + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && 1.507 + rv != NS_ERROR_FILE_NOT_FOUND) { 1.508 + gIgnoreDiskCache = true; 1.509 + mozilla::Telemetry::Accumulate(Telemetry::STARTUP_CACHE_INVALID, true); 1.510 + return; 1.511 + } 1.512 + gIgnoreDiskCache = false; 1.513 + LoadArchive(gPostFlushAgeAction); 1.514 +} 1.515 + 1.516 +void 1.517 +StartupCache::IgnoreDiskCache() 1.518 +{ 1.519 + gIgnoreDiskCache = true; 1.520 + if (gStartupCache) 1.521 + gStartupCache->InvalidateCache(); 1.522 +} 1.523 + 1.524 +/* 1.525 + * WaitOnWriteThread() is called from a main thread to wait for the worker 1.526 + * thread to finish. However since the same code is used in the worker thread and 1.527 + * main thread, the worker thread can also call WaitOnWriteThread() which is a no-op. 1.528 + */ 1.529 +void 1.530 +StartupCache::WaitOnWriteThread() 1.531 +{ 1.532 + NS_ASSERTION(NS_IsMainThread(), "Startup cache should only wait for io thread on main thread"); 1.533 + if (!mWriteThread || mWriteThread == PR_GetCurrentThread()) 1.534 + return; 1.535 + 1.536 + PR_JoinThread(mWriteThread); 1.537 + mWriteThread = nullptr; 1.538 +} 1.539 + 1.540 +void 1.541 +StartupCache::ThreadedWrite(void *aClosure) 1.542 +{ 1.543 + PR_SetCurrentThreadName("StartupCache"); 1.544 + mozilla::IOInterposer::RegisterCurrentThread(); 1.545 + /* 1.546 + * It is safe to use the pointer passed in aClosure to reference the 1.547 + * StartupCache object because the thread's lifetime is tightly coupled to 1.548 + * the lifetime of the StartupCache object; this thread is joined in the 1.549 + * StartupCache destructor, guaranteeing that this function runs if and only 1.550 + * if the StartupCache object is valid. 1.551 + */ 1.552 + StartupCache* startupCacheObj = static_cast<StartupCache*>(aClosure); 1.553 + startupCacheObj->WriteToDisk(); 1.554 + mozilla::IOInterposer::UnregisterCurrentThread(); 1.555 +} 1.556 + 1.557 +/* 1.558 + * The write-thread is spawned on a timeout(which is reset with every write). This 1.559 + * can avoid a slow shutdown. After writing out the cache, the zipreader is 1.560 + * reloaded on the worker thread. 1.561 + */ 1.562 +void 1.563 +StartupCache::WriteTimeout(nsITimer *aTimer, void *aClosure) 1.564 +{ 1.565 + /* 1.566 + * It is safe to use the pointer passed in aClosure to reference the 1.567 + * StartupCache object because the timer's lifetime is tightly coupled to 1.568 + * the lifetime of the StartupCache object; this timer is canceled in the 1.569 + * StartupCache destructor, guaranteeing that this function runs if and only 1.570 + * if the StartupCache object is valid. 1.571 + */ 1.572 + StartupCache* startupCacheObj = static_cast<StartupCache*>(aClosure); 1.573 + startupCacheObj->mWriteThread = PR_CreateThread(PR_USER_THREAD, 1.574 + StartupCache::ThreadedWrite, 1.575 + startupCacheObj, 1.576 + PR_PRIORITY_NORMAL, 1.577 + PR_GLOBAL_THREAD, 1.578 + PR_JOINABLE_THREAD, 1.579 + 0); 1.580 +} 1.581 + 1.582 +// We don't want to refcount StartupCache, so we'll just 1.583 +// hold a ref to this and pass it to observerService instead. 1.584 +NS_IMPL_ISUPPORTS(StartupCacheListener, nsIObserver) 1.585 + 1.586 +nsresult 1.587 +StartupCacheListener::Observe(nsISupports *subject, const char* topic, const char16_t* data) 1.588 +{ 1.589 + StartupCache* sc = StartupCache::GetSingleton(); 1.590 + if (!sc) 1.591 + return NS_OK; 1.592 + 1.593 + if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { 1.594 + // Do not leave the thread running past xpcom shutdown 1.595 + sc->WaitOnWriteThread(); 1.596 + StartupCache::gShutdownInitiated = true; 1.597 + } else if (strcmp(topic, "startupcache-invalidate") == 0) { 1.598 + sc->InvalidateCache(); 1.599 + } 1.600 + return NS_OK; 1.601 +} 1.602 + 1.603 +nsresult 1.604 +StartupCache::GetDebugObjectOutputStream(nsIObjectOutputStream* aStream, 1.605 + nsIObjectOutputStream** aOutStream) 1.606 +{ 1.607 + NS_ENSURE_ARG_POINTER(aStream); 1.608 +#ifdef DEBUG 1.609 + StartupCacheDebugOutputStream* stream 1.610 + = new StartupCacheDebugOutputStream(aStream, &mWriteObjectMap); 1.611 + NS_ADDREF(*aOutStream = stream); 1.612 +#else 1.613 + NS_ADDREF(*aOutStream = aStream); 1.614 +#endif 1.615 + 1.616 + return NS_OK; 1.617 +} 1.618 + 1.619 +nsresult 1.620 +StartupCache::ResetStartupWriteTimer() 1.621 +{ 1.622 + mStartupWriteInitiated = false; 1.623 + nsresult rv; 1.624 + if (!mTimer) 1.625 + mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); 1.626 + else 1.627 + rv = mTimer->Cancel(); 1.628 + NS_ENSURE_SUCCESS(rv, rv); 1.629 + // Wait for 10 seconds, then write out the cache. 1.630 + mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, this, 60000, 1.631 + nsITimer::TYPE_ONE_SHOT); 1.632 + return NS_OK; 1.633 +} 1.634 + 1.635 +nsresult 1.636 +StartupCache::RecordAgesAlways() 1.637 +{ 1.638 + gPostFlushAgeAction = RECORD_AGE; 1.639 + return NS_OK; 1.640 +} 1.641 + 1.642 +// StartupCacheDebugOutputStream implementation 1.643 +#ifdef DEBUG 1.644 +NS_IMPL_ISUPPORTS(StartupCacheDebugOutputStream, nsIObjectOutputStream, 1.645 + nsIBinaryOutputStream, nsIOutputStream) 1.646 + 1.647 +bool 1.648 +StartupCacheDebugOutputStream::CheckReferences(nsISupports* aObject) 1.649 +{ 1.650 + nsresult rv; 1.651 + 1.652 + nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject); 1.653 + if (!classInfo) { 1.654 + NS_ERROR("aObject must implement nsIClassInfo"); 1.655 + return false; 1.656 + } 1.657 + 1.658 + uint32_t flags; 1.659 + rv = classInfo->GetFlags(&flags); 1.660 + NS_ENSURE_SUCCESS(rv, false); 1.661 + if (flags & nsIClassInfo::SINGLETON) 1.662 + return true; 1.663 + 1.664 + nsISupportsHashKey* key = mObjectMap->GetEntry(aObject); 1.665 + if (key) { 1.666 + NS_ERROR("non-singleton aObject is referenced multiple times in this" 1.667 + "serialization, we don't support that."); 1.668 + return false; 1.669 + } 1.670 + 1.671 + mObjectMap->PutEntry(aObject); 1.672 + return true; 1.673 +} 1.674 + 1.675 +// nsIObjectOutputStream implementation 1.676 +nsresult 1.677 +StartupCacheDebugOutputStream::WriteObject(nsISupports* aObject, bool aIsStrongRef) 1.678 +{ 1.679 + nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject)); 1.680 + 1.681 + NS_ASSERTION(rootObject.get() == aObject, 1.682 + "bad call to WriteObject -- call WriteCompoundObject!"); 1.683 + bool check = CheckReferences(aObject); 1.684 + NS_ENSURE_TRUE(check, NS_ERROR_FAILURE); 1.685 + return mBinaryStream->WriteObject(aObject, aIsStrongRef); 1.686 +} 1.687 + 1.688 +nsresult 1.689 +StartupCacheDebugOutputStream::WriteSingleRefObject(nsISupports* aObject) 1.690 +{ 1.691 + nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject)); 1.692 + 1.693 + NS_ASSERTION(rootObject.get() == aObject, 1.694 + "bad call to WriteSingleRefObject -- call WriteCompoundObject!"); 1.695 + bool check = CheckReferences(aObject); 1.696 + NS_ENSURE_TRUE(check, NS_ERROR_FAILURE); 1.697 + return mBinaryStream->WriteSingleRefObject(aObject); 1.698 +} 1.699 + 1.700 +nsresult 1.701 +StartupCacheDebugOutputStream::WriteCompoundObject(nsISupports* aObject, 1.702 + const nsIID& aIID, 1.703 + bool aIsStrongRef) 1.704 +{ 1.705 + nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject)); 1.706 + 1.707 + nsCOMPtr<nsISupports> roundtrip; 1.708 + rootObject->QueryInterface(aIID, getter_AddRefs(roundtrip)); 1.709 + NS_ASSERTION(roundtrip.get() == aObject, 1.710 + "bad aggregation or multiple inheritance detected by call to " 1.711 + "WriteCompoundObject!"); 1.712 + 1.713 + bool check = CheckReferences(aObject); 1.714 + NS_ENSURE_TRUE(check, NS_ERROR_FAILURE); 1.715 + return mBinaryStream->WriteCompoundObject(aObject, aIID, aIsStrongRef); 1.716 +} 1.717 + 1.718 +nsresult 1.719 +StartupCacheDebugOutputStream::WriteID(nsID const& aID) 1.720 +{ 1.721 + return mBinaryStream->WriteID(aID); 1.722 +} 1.723 + 1.724 +char* 1.725 +StartupCacheDebugOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) 1.726 +{ 1.727 + return mBinaryStream->GetBuffer(aLength, aAlignMask); 1.728 +} 1.729 + 1.730 +void 1.731 +StartupCacheDebugOutputStream::PutBuffer(char* aBuffer, uint32_t aLength) 1.732 +{ 1.733 + mBinaryStream->PutBuffer(aBuffer, aLength); 1.734 +} 1.735 +#endif //DEBUG 1.736 + 1.737 +StartupCacheWrapper* StartupCacheWrapper::gStartupCacheWrapper = nullptr; 1.738 + 1.739 +NS_IMPL_ISUPPORTS(StartupCacheWrapper, nsIStartupCache) 1.740 + 1.741 +StartupCacheWrapper* StartupCacheWrapper::GetSingleton() 1.742 +{ 1.743 + if (!gStartupCacheWrapper) 1.744 + gStartupCacheWrapper = new StartupCacheWrapper(); 1.745 + 1.746 + NS_ADDREF(gStartupCacheWrapper); 1.747 + return gStartupCacheWrapper; 1.748 +} 1.749 + 1.750 +nsresult 1.751 +StartupCacheWrapper::GetBuffer(const char* id, char** outbuf, uint32_t* length) 1.752 +{ 1.753 + StartupCache* sc = StartupCache::GetSingleton(); 1.754 + if (!sc) { 1.755 + return NS_ERROR_NOT_INITIALIZED; 1.756 + } 1.757 + return sc->GetBuffer(id, outbuf, length); 1.758 +} 1.759 + 1.760 +nsresult 1.761 +StartupCacheWrapper::PutBuffer(const char* id, const char* inbuf, uint32_t length) 1.762 +{ 1.763 + StartupCache* sc = StartupCache::GetSingleton(); 1.764 + if (!sc) { 1.765 + return NS_ERROR_NOT_INITIALIZED; 1.766 + } 1.767 + return sc->PutBuffer(id, inbuf, length); 1.768 +} 1.769 + 1.770 +nsresult 1.771 +StartupCacheWrapper::InvalidateCache() 1.772 +{ 1.773 + StartupCache* sc = StartupCache::GetSingleton(); 1.774 + if (!sc) { 1.775 + return NS_ERROR_NOT_INITIALIZED; 1.776 + } 1.777 + sc->InvalidateCache(); 1.778 + return NS_OK; 1.779 +} 1.780 + 1.781 +nsresult 1.782 +StartupCacheWrapper::IgnoreDiskCache() 1.783 +{ 1.784 + StartupCache::IgnoreDiskCache(); 1.785 + return NS_OK; 1.786 +} 1.787 + 1.788 +nsresult 1.789 +StartupCacheWrapper::GetDebugObjectOutputStream(nsIObjectOutputStream* stream, 1.790 + nsIObjectOutputStream** outStream) 1.791 +{ 1.792 + StartupCache* sc = StartupCache::GetSingleton(); 1.793 + if (!sc) { 1.794 + return NS_ERROR_NOT_INITIALIZED; 1.795 + } 1.796 + return sc->GetDebugObjectOutputStream(stream, outStream); 1.797 +} 1.798 + 1.799 +nsresult 1.800 +StartupCacheWrapper::StartupWriteComplete(bool *complete) 1.801 +{ 1.802 + StartupCache* sc = StartupCache::GetSingleton(); 1.803 + if (!sc) { 1.804 + return NS_ERROR_NOT_INITIALIZED; 1.805 + } 1.806 + sc->WaitOnWriteThread(); 1.807 + *complete = sc->mStartupWriteInitiated && sc->mTable.Count() == 0; 1.808 + return NS_OK; 1.809 +} 1.810 + 1.811 +nsresult 1.812 +StartupCacheWrapper::ResetStartupWriteTimer() 1.813 +{ 1.814 + StartupCache* sc = StartupCache::GetSingleton(); 1.815 + return sc ? sc->ResetStartupWriteTimer() : NS_ERROR_NOT_INITIALIZED; 1.816 +} 1.817 + 1.818 +nsresult 1.819 +StartupCacheWrapper::GetObserver(nsIObserver** obv) { 1.820 + StartupCache* sc = StartupCache::GetSingleton(); 1.821 + if (!sc) { 1.822 + return NS_ERROR_NOT_INITIALIZED; 1.823 + } 1.824 + NS_ADDREF(*obv = sc->mListener); 1.825 + return NS_OK; 1.826 +} 1.827 + 1.828 +nsresult 1.829 +StartupCacheWrapper::RecordAgesAlways() { 1.830 + StartupCache *sc = StartupCache::GetSingleton(); 1.831 + return sc ? sc->RecordAgesAlways() : NS_ERROR_NOT_INITIALIZED; 1.832 +} 1.833 + 1.834 +} // namespace scache 1.835 +} // namespace mozilla