startupcache/StartupCache.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "prio.h"
michael@0 8 #include "pldhash.h"
michael@0 9 #include "nsXPCOMStrings.h"
michael@0 10 #include "mozilla/IOInterposer.h"
michael@0 11 #include "mozilla/MemoryReporting.h"
michael@0 12 #include "mozilla/scache/StartupCache.h"
michael@0 13
michael@0 14 #include "nsAutoPtr.h"
michael@0 15 #include "nsClassHashtable.h"
michael@0 16 #include "nsComponentManagerUtils.h"
michael@0 17 #include "nsDirectoryServiceUtils.h"
michael@0 18 #include "nsIClassInfo.h"
michael@0 19 #include "nsIFile.h"
michael@0 20 #include "nsIObserver.h"
michael@0 21 #include "nsIObserverService.h"
michael@0 22 #include "nsIOutputStream.h"
michael@0 23 #include "nsIStartupCache.h"
michael@0 24 #include "nsIStorageStream.h"
michael@0 25 #include "nsIStreamBufferAccess.h"
michael@0 26 #include "nsIStringStream.h"
michael@0 27 #include "nsISupports.h"
michael@0 28 #include "nsITimer.h"
michael@0 29 #include "nsIZipWriter.h"
michael@0 30 #include "nsIZipReader.h"
michael@0 31 #include "nsWeakReference.h"
michael@0 32 #include "nsZipArchive.h"
michael@0 33 #include "mozilla/Omnijar.h"
michael@0 34 #include "prenv.h"
michael@0 35 #include "mozilla/Telemetry.h"
michael@0 36 #include "nsThreadUtils.h"
michael@0 37 #include "nsXULAppAPI.h"
michael@0 38 #include "nsIProtocolHandler.h"
michael@0 39
michael@0 40 #ifdef IS_BIG_ENDIAN
michael@0 41 #define SC_ENDIAN "big"
michael@0 42 #else
michael@0 43 #define SC_ENDIAN "little"
michael@0 44 #endif
michael@0 45
michael@0 46 #if PR_BYTES_PER_WORD == 4
michael@0 47 #define SC_WORDSIZE "4"
michael@0 48 #else
michael@0 49 #define SC_WORDSIZE "8"
michael@0 50 #endif
michael@0 51
michael@0 52 namespace mozilla {
michael@0 53 namespace scache {
michael@0 54
michael@0 55 MOZ_DEFINE_MALLOC_SIZE_OF(StartupCacheMallocSizeOf)
michael@0 56
michael@0 57 NS_IMETHODIMP
michael@0 58 StartupCache::CollectReports(nsIHandleReportCallback* aHandleReport,
michael@0 59 nsISupports* aData)
michael@0 60 {
michael@0 61 #define REPORT(_path, _kind, _amount, _desc) \
michael@0 62 do { \
michael@0 63 nsresult rv = \
michael@0 64 aHandleReport->Callback(EmptyCString(), \
michael@0 65 NS_LITERAL_CSTRING(_path), \
michael@0 66 _kind, UNITS_BYTES, _amount, \
michael@0 67 NS_LITERAL_CSTRING(_desc), aData); \
michael@0 68 NS_ENSURE_SUCCESS(rv, rv); \
michael@0 69 } while (0)
michael@0 70
michael@0 71 REPORT("explicit/startup-cache/mapping", KIND_NONHEAP,
michael@0 72 SizeOfMapping(),
michael@0 73 "Memory used to hold the mapping of the startup cache from file. "
michael@0 74 "This memory is likely to be swapped out shortly after start-up.");
michael@0 75
michael@0 76 REPORT("explicit/startup-cache/data", KIND_HEAP,
michael@0 77 HeapSizeOfIncludingThis(StartupCacheMallocSizeOf),
michael@0 78 "Memory used by the startup cache for things other than the file "
michael@0 79 "mapping.");
michael@0 80
michael@0 81 return NS_OK;
michael@0 82 }
michael@0 83
michael@0 84 static const char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN;
michael@0 85 #if defined(XP_WIN) && defined(MOZ_METRO)
michael@0 86 static const char sMetroStartupCacheName[] = "metroStartupCache." SC_WORDSIZE "." SC_ENDIAN;
michael@0 87 #endif
michael@0 88
michael@0 89 StartupCache*
michael@0 90 StartupCache::GetSingleton()
michael@0 91 {
michael@0 92 if (!gStartupCache) {
michael@0 93 if (XRE_GetProcessType() != GeckoProcessType_Default) {
michael@0 94 return nullptr;
michael@0 95 }
michael@0 96 #ifdef MOZ_B2G
michael@0 97 return nullptr;
michael@0 98 #endif
michael@0 99
michael@0 100 StartupCache::InitSingleton();
michael@0 101 }
michael@0 102
michael@0 103 return StartupCache::gStartupCache;
michael@0 104 }
michael@0 105
michael@0 106 void
michael@0 107 StartupCache::DeleteSingleton()
michael@0 108 {
michael@0 109 StartupCache::gStartupCache = nullptr;
michael@0 110 }
michael@0 111
michael@0 112 nsresult
michael@0 113 StartupCache::InitSingleton()
michael@0 114 {
michael@0 115 nsresult rv;
michael@0 116 StartupCache::gStartupCache = new StartupCache();
michael@0 117
michael@0 118 rv = StartupCache::gStartupCache->Init();
michael@0 119 if (NS_FAILED(rv)) {
michael@0 120 StartupCache::gStartupCache = nullptr;
michael@0 121 }
michael@0 122 return rv;
michael@0 123 }
michael@0 124
michael@0 125 StaticRefPtr<StartupCache> StartupCache::gStartupCache;
michael@0 126 bool StartupCache::gShutdownInitiated;
michael@0 127 bool StartupCache::gIgnoreDiskCache;
michael@0 128 enum StartupCache::TelemetrifyAge StartupCache::gPostFlushAgeAction = StartupCache::IGNORE_AGE;
michael@0 129
michael@0 130 NS_IMPL_ISUPPORTS(StartupCache, nsIMemoryReporter)
michael@0 131
michael@0 132 StartupCache::StartupCache()
michael@0 133 : mArchive(nullptr), mStartupWriteInitiated(false), mWriteThread(nullptr)
michael@0 134 { }
michael@0 135
michael@0 136 StartupCache::~StartupCache()
michael@0 137 {
michael@0 138 if (mTimer) {
michael@0 139 mTimer->Cancel();
michael@0 140 }
michael@0 141
michael@0 142 // Generally, the in-memory table should be empty here,
michael@0 143 // but an early shutdown means either mTimer didn't run
michael@0 144 // or the write thread is still running.
michael@0 145 WaitOnWriteThread();
michael@0 146
michael@0 147 // If we shutdown quickly timer wont have fired. Instead of writing
michael@0 148 // it on the main thread and block the shutdown we simply wont update
michael@0 149 // the startup cache. Always do this if the file doesn't exist since
michael@0 150 // we use it part of the package step.
michael@0 151 if (!mArchive) {
michael@0 152 WriteToDisk();
michael@0 153 }
michael@0 154
michael@0 155 UnregisterWeakMemoryReporter(this);
michael@0 156 }
michael@0 157
michael@0 158 nsresult
michael@0 159 StartupCache::Init()
michael@0 160 {
michael@0 161 // workaround for bug 653936
michael@0 162 nsCOMPtr<nsIProtocolHandler> jarInitializer(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "jar"));
michael@0 163
michael@0 164 nsresult rv;
michael@0 165
michael@0 166 // This allows to override the startup cache filename
michael@0 167 // which is useful from xpcshell, when there is no ProfLDS directory to keep cache in.
michael@0 168 char *env = PR_GetEnv("MOZ_STARTUP_CACHE");
michael@0 169 if (env) {
michael@0 170 rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, getter_AddRefs(mFile));
michael@0 171 } else {
michael@0 172 nsCOMPtr<nsIFile> file;
michael@0 173 rv = NS_GetSpecialDirectory("ProfLDS",
michael@0 174 getter_AddRefs(file));
michael@0 175 if (NS_FAILED(rv)) {
michael@0 176 // return silently, this will fail in mochitests's xpcshell process.
michael@0 177 return rv;
michael@0 178 }
michael@0 179
michael@0 180 nsCOMPtr<nsIFile> profDir;
michael@0 181 NS_GetSpecialDirectory("ProfDS", getter_AddRefs(profDir));
michael@0 182 if (profDir) {
michael@0 183 bool same;
michael@0 184 if (NS_SUCCEEDED(profDir->Equals(file, &same)) && !same) {
michael@0 185 // We no longer store the startup cache in the main profile
michael@0 186 // directory, so we should cleanup the old one.
michael@0 187 if (NS_SUCCEEDED(
michael@0 188 profDir->AppendNative(NS_LITERAL_CSTRING("startupCache")))) {
michael@0 189 profDir->Remove(true);
michael@0 190 }
michael@0 191 }
michael@0 192 }
michael@0 193
michael@0 194 rv = file->AppendNative(NS_LITERAL_CSTRING("startupCache"));
michael@0 195 NS_ENSURE_SUCCESS(rv, rv);
michael@0 196
michael@0 197 // Try to create the directory if it's not there yet
michael@0 198 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0777);
michael@0 199 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
michael@0 200 return rv;
michael@0 201
michael@0 202 #if defined(XP_WIN) && defined(MOZ_METRO)
michael@0 203 if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Metro) {
michael@0 204 rv = file->AppendNative(NS_LITERAL_CSTRING(sMetroStartupCacheName));
michael@0 205 } else
michael@0 206 #endif
michael@0 207 {
michael@0 208 rv = file->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName));
michael@0 209 }
michael@0 210
michael@0 211 NS_ENSURE_SUCCESS(rv, rv);
michael@0 212
michael@0 213 mFile = do_QueryInterface(file);
michael@0 214 }
michael@0 215
michael@0 216 NS_ENSURE_TRUE(mFile, NS_ERROR_UNEXPECTED);
michael@0 217
michael@0 218 mObserverService = do_GetService("@mozilla.org/observer-service;1");
michael@0 219
michael@0 220 if (!mObserverService) {
michael@0 221 NS_WARNING("Could not get observerService.");
michael@0 222 return NS_ERROR_UNEXPECTED;
michael@0 223 }
michael@0 224
michael@0 225 mListener = new StartupCacheListener();
michael@0 226 rv = mObserverService->AddObserver(mListener, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
michael@0 227 false);
michael@0 228 NS_ENSURE_SUCCESS(rv, rv);
michael@0 229 rv = mObserverService->AddObserver(mListener, "startupcache-invalidate",
michael@0 230 false);
michael@0 231 NS_ENSURE_SUCCESS(rv, rv);
michael@0 232
michael@0 233 rv = LoadArchive(RECORD_AGE);
michael@0 234
michael@0 235 // Sometimes we don't have a cache yet, that's ok.
michael@0 236 // If it's corrupted, just remove it and start over.
michael@0 237 if (gIgnoreDiskCache || (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)) {
michael@0 238 NS_WARNING("Failed to load startupcache file correctly, removing!");
michael@0 239 InvalidateCache();
michael@0 240 }
michael@0 241
michael@0 242 RegisterWeakMemoryReporter(this);
michael@0 243
michael@0 244 return NS_OK;
michael@0 245 }
michael@0 246
michael@0 247 /**
michael@0 248 * LoadArchive can be called from the main thread or while reloading cache on write thread.
michael@0 249 */
michael@0 250 nsresult
michael@0 251 StartupCache::LoadArchive(enum TelemetrifyAge flag)
michael@0 252 {
michael@0 253 if (gIgnoreDiskCache)
michael@0 254 return NS_ERROR_FAILURE;
michael@0 255
michael@0 256 bool exists;
michael@0 257 mArchive = nullptr;
michael@0 258 nsresult rv = mFile->Exists(&exists);
michael@0 259 if (NS_FAILED(rv) || !exists)
michael@0 260 return NS_ERROR_FILE_NOT_FOUND;
michael@0 261
michael@0 262 mArchive = new nsZipArchive();
michael@0 263 rv = mArchive->OpenArchive(mFile);
michael@0 264 if (NS_FAILED(rv) || flag == IGNORE_AGE)
michael@0 265 return rv;
michael@0 266
michael@0 267 nsCString comment;
michael@0 268 if (!mArchive->GetComment(comment)) {
michael@0 269 return rv;
michael@0 270 }
michael@0 271
michael@0 272 const char *data;
michael@0 273 size_t len = NS_CStringGetData(comment, &data);
michael@0 274 PRTime creationStamp;
michael@0 275 // We might not have a comment if the startup cache file was created
michael@0 276 // before we started recording creation times in the comment.
michael@0 277 if (len == sizeof(creationStamp)) {
michael@0 278 memcpy(&creationStamp, data, len);
michael@0 279 PRTime current = PR_Now();
michael@0 280 int64_t diff = current - creationStamp;
michael@0 281
michael@0 282 // We can't use AccumulateTimeDelta here because we have no way of
michael@0 283 // reifying a TimeStamp from creationStamp.
michael@0 284 int64_t usec_per_hour = PR_USEC_PER_SEC * int64_t(3600);
michael@0 285 int64_t hour_diff = (diff + usec_per_hour - 1) / usec_per_hour;
michael@0 286 mozilla::Telemetry::Accumulate(Telemetry::STARTUP_CACHE_AGE_HOURS,
michael@0 287 hour_diff);
michael@0 288 }
michael@0 289
michael@0 290 return rv;
michael@0 291 }
michael@0 292
michael@0 293 namespace {
michael@0 294
michael@0 295 nsresult
michael@0 296 GetBufferFromZipArchive(nsZipArchive *zip, bool doCRC, const char* id,
michael@0 297 char** outbuf, uint32_t* length)
michael@0 298 {
michael@0 299 if (!zip)
michael@0 300 return NS_ERROR_NOT_AVAILABLE;
michael@0 301
michael@0 302 nsZipItemPtr<char> zipItem(zip, id, doCRC);
michael@0 303 if (!zipItem)
michael@0 304 return NS_ERROR_NOT_AVAILABLE;
michael@0 305
michael@0 306 *outbuf = zipItem.Forget();
michael@0 307 *length = zipItem.Length();
michael@0 308 return NS_OK;
michael@0 309 }
michael@0 310
michael@0 311 } /* anonymous namespace */
michael@0 312
michael@0 313 // NOTE: this will not find a new entry until it has been written to disk!
michael@0 314 // Consumer should take ownership of the resulting buffer.
michael@0 315 nsresult
michael@0 316 StartupCache::GetBuffer(const char* id, char** outbuf, uint32_t* length)
michael@0 317 {
michael@0 318 NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread");
michael@0 319 WaitOnWriteThread();
michael@0 320 if (!mStartupWriteInitiated) {
michael@0 321 CacheEntry* entry;
michael@0 322 nsDependentCString idStr(id);
michael@0 323 mTable.Get(idStr, &entry);
michael@0 324 if (entry) {
michael@0 325 *outbuf = new char[entry->size];
michael@0 326 memcpy(*outbuf, entry->data, entry->size);
michael@0 327 *length = entry->size;
michael@0 328 return NS_OK;
michael@0 329 }
michael@0 330 }
michael@0 331
michael@0 332 nsresult rv = GetBufferFromZipArchive(mArchive, true, id, outbuf, length);
michael@0 333 if (NS_SUCCEEDED(rv))
michael@0 334 return rv;
michael@0 335
michael@0 336 nsRefPtr<nsZipArchive> omnijar = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
michael@0 337 // no need to checksum omnijarred entries
michael@0 338 rv = GetBufferFromZipArchive(omnijar, false, id, outbuf, length);
michael@0 339 if (NS_SUCCEEDED(rv))
michael@0 340 return rv;
michael@0 341
michael@0 342 omnijar = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
michael@0 343 // no need to checksum omnijarred entries
michael@0 344 return GetBufferFromZipArchive(omnijar, false, id, outbuf, length);
michael@0 345 }
michael@0 346
michael@0 347 // Makes a copy of the buffer, client retains ownership of inbuf.
michael@0 348 nsresult
michael@0 349 StartupCache::PutBuffer(const char* id, const char* inbuf, uint32_t len)
michael@0 350 {
michael@0 351 NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread");
michael@0 352 WaitOnWriteThread();
michael@0 353 if (StartupCache::gShutdownInitiated) {
michael@0 354 return NS_ERROR_NOT_AVAILABLE;
michael@0 355 }
michael@0 356
michael@0 357 nsAutoArrayPtr<char> data(new char[len]);
michael@0 358 memcpy(data, inbuf, len);
michael@0 359
michael@0 360 nsDependentCString idStr(id);
michael@0 361 // Cache it for now, we'll write all together later.
michael@0 362 CacheEntry* entry;
michael@0 363
michael@0 364 #ifdef DEBUG
michael@0 365 mTable.Get(idStr, &entry);
michael@0 366 NS_ASSERTION(entry == nullptr, "Existing entry in StartupCache.");
michael@0 367
michael@0 368 if (mArchive) {
michael@0 369 nsZipItem* zipItem = mArchive->GetItem(id);
michael@0 370 NS_ASSERTION(zipItem == nullptr, "Existing entry in disk StartupCache.");
michael@0 371 }
michael@0 372 #endif
michael@0 373
michael@0 374 entry = new CacheEntry(data.forget(), len);
michael@0 375 mTable.Put(idStr, entry);
michael@0 376 return ResetStartupWriteTimer();
michael@0 377 }
michael@0 378
michael@0 379 size_t
michael@0 380 StartupCache::SizeOfMapping()
michael@0 381 {
michael@0 382 return mArchive ? mArchive->SizeOfMapping() : 0;
michael@0 383 }
michael@0 384
michael@0 385 size_t
michael@0 386 StartupCache::HeapSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
michael@0 387 {
michael@0 388 // This function could measure more members, but they haven't been found by
michael@0 389 // DMD to be significant. They can be added later if necessary.
michael@0 390 return aMallocSizeOf(this) +
michael@0 391 mTable.SizeOfExcludingThis(SizeOfEntryExcludingThis, aMallocSizeOf);
michael@0 392 }
michael@0 393
michael@0 394 /* static */ size_t
michael@0 395 StartupCache::SizeOfEntryExcludingThis(const nsACString& key, const nsAutoPtr<CacheEntry>& data,
michael@0 396 mozilla::MallocSizeOf mallocSizeOf, void *)
michael@0 397 {
michael@0 398 return data->SizeOfExcludingThis(mallocSizeOf);
michael@0 399 }
michael@0 400
michael@0 401 struct CacheWriteHolder
michael@0 402 {
michael@0 403 nsCOMPtr<nsIZipWriter> writer;
michael@0 404 nsCOMPtr<nsIStringInputStream> stream;
michael@0 405 PRTime time;
michael@0 406 };
michael@0 407
michael@0 408 PLDHashOperator
michael@0 409 CacheCloseHelper(const nsACString& key, nsAutoPtr<CacheEntry>& data,
michael@0 410 void* closure)
michael@0 411 {
michael@0 412 nsresult rv;
michael@0 413
michael@0 414 CacheWriteHolder* holder = (CacheWriteHolder*) closure;
michael@0 415 nsIStringInputStream* stream = holder->stream;
michael@0 416 nsIZipWriter* writer = holder->writer;
michael@0 417
michael@0 418 stream->ShareData(data->data, data->size);
michael@0 419
michael@0 420 #ifdef DEBUG
michael@0 421 bool hasEntry;
michael@0 422 rv = writer->HasEntry(key, &hasEntry);
michael@0 423 NS_ASSERTION(NS_SUCCEEDED(rv) && hasEntry == false,
michael@0 424 "Existing entry in disk StartupCache.");
michael@0 425 #endif
michael@0 426 rv = writer->AddEntryStream(key, holder->time, true, stream, false);
michael@0 427
michael@0 428 if (NS_FAILED(rv)) {
michael@0 429 NS_WARNING("cache entry deleted but not written to disk.");
michael@0 430 }
michael@0 431 return PL_DHASH_REMOVE;
michael@0 432 }
michael@0 433
michael@0 434
michael@0 435 /**
michael@0 436 * WriteToDisk writes the cache out to disk. Callers of WriteToDisk need to call WaitOnWriteThread
michael@0 437 * to make sure there isn't a write happening on another thread
michael@0 438 */
michael@0 439 void
michael@0 440 StartupCache::WriteToDisk()
michael@0 441 {
michael@0 442 nsresult rv;
michael@0 443 mStartupWriteInitiated = true;
michael@0 444
michael@0 445 if (mTable.Count() == 0)
michael@0 446 return;
michael@0 447
michael@0 448 nsCOMPtr<nsIZipWriter> zipW = do_CreateInstance("@mozilla.org/zipwriter;1");
michael@0 449 if (!zipW)
michael@0 450 return;
michael@0 451
michael@0 452 rv = zipW->Open(mFile, PR_RDWR | PR_CREATE_FILE);
michael@0 453 if (NS_FAILED(rv)) {
michael@0 454 NS_WARNING("could not open zipfile for write");
michael@0 455 return;
michael@0 456 }
michael@0 457
michael@0 458 // If we didn't have an mArchive member, that means that we failed to
michael@0 459 // open the startup cache for reading. Therefore, we need to record
michael@0 460 // the time of creation in a zipfile comment; this will be useful for
michael@0 461 // Telemetry statistics.
michael@0 462 PRTime now = PR_Now();
michael@0 463 if (!mArchive) {
michael@0 464 nsCString comment;
michael@0 465 comment.Assign((char *)&now, sizeof(now));
michael@0 466 zipW->SetComment(comment);
michael@0 467 }
michael@0 468
michael@0 469 nsCOMPtr<nsIStringInputStream> stream
michael@0 470 = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
michael@0 471 if (NS_FAILED(rv)) {
michael@0 472 NS_WARNING("Couldn't create string input stream.");
michael@0 473 return;
michael@0 474 }
michael@0 475
michael@0 476 CacheWriteHolder holder;
michael@0 477 holder.stream = stream;
michael@0 478 holder.writer = zipW;
michael@0 479 holder.time = now;
michael@0 480
michael@0 481 mTable.Enumerate(CacheCloseHelper, &holder);
michael@0 482
michael@0 483 // Close the archive so Windows doesn't choke.
michael@0 484 mArchive = nullptr;
michael@0 485 zipW->Close();
michael@0 486
michael@0 487 // We succesfully wrote the archive to disk; mark the disk file as trusted
michael@0 488 gIgnoreDiskCache = false;
michael@0 489
michael@0 490 // Our reader's view of the archive is outdated now, reload it.
michael@0 491 LoadArchive(gPostFlushAgeAction);
michael@0 492
michael@0 493 return;
michael@0 494 }
michael@0 495
michael@0 496 void
michael@0 497 StartupCache::InvalidateCache()
michael@0 498 {
michael@0 499 WaitOnWriteThread();
michael@0 500 mTable.Clear();
michael@0 501 mArchive = nullptr;
michael@0 502 nsresult rv = mFile->Remove(false);
michael@0 503 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
michael@0 504 rv != NS_ERROR_FILE_NOT_FOUND) {
michael@0 505 gIgnoreDiskCache = true;
michael@0 506 mozilla::Telemetry::Accumulate(Telemetry::STARTUP_CACHE_INVALID, true);
michael@0 507 return;
michael@0 508 }
michael@0 509 gIgnoreDiskCache = false;
michael@0 510 LoadArchive(gPostFlushAgeAction);
michael@0 511 }
michael@0 512
michael@0 513 void
michael@0 514 StartupCache::IgnoreDiskCache()
michael@0 515 {
michael@0 516 gIgnoreDiskCache = true;
michael@0 517 if (gStartupCache)
michael@0 518 gStartupCache->InvalidateCache();
michael@0 519 }
michael@0 520
michael@0 521 /*
michael@0 522 * WaitOnWriteThread() is called from a main thread to wait for the worker
michael@0 523 * thread to finish. However since the same code is used in the worker thread and
michael@0 524 * main thread, the worker thread can also call WaitOnWriteThread() which is a no-op.
michael@0 525 */
michael@0 526 void
michael@0 527 StartupCache::WaitOnWriteThread()
michael@0 528 {
michael@0 529 NS_ASSERTION(NS_IsMainThread(), "Startup cache should only wait for io thread on main thread");
michael@0 530 if (!mWriteThread || mWriteThread == PR_GetCurrentThread())
michael@0 531 return;
michael@0 532
michael@0 533 PR_JoinThread(mWriteThread);
michael@0 534 mWriteThread = nullptr;
michael@0 535 }
michael@0 536
michael@0 537 void
michael@0 538 StartupCache::ThreadedWrite(void *aClosure)
michael@0 539 {
michael@0 540 PR_SetCurrentThreadName("StartupCache");
michael@0 541 mozilla::IOInterposer::RegisterCurrentThread();
michael@0 542 /*
michael@0 543 * It is safe to use the pointer passed in aClosure to reference the
michael@0 544 * StartupCache object because the thread's lifetime is tightly coupled to
michael@0 545 * the lifetime of the StartupCache object; this thread is joined in the
michael@0 546 * StartupCache destructor, guaranteeing that this function runs if and only
michael@0 547 * if the StartupCache object is valid.
michael@0 548 */
michael@0 549 StartupCache* startupCacheObj = static_cast<StartupCache*>(aClosure);
michael@0 550 startupCacheObj->WriteToDisk();
michael@0 551 mozilla::IOInterposer::UnregisterCurrentThread();
michael@0 552 }
michael@0 553
michael@0 554 /*
michael@0 555 * The write-thread is spawned on a timeout(which is reset with every write). This
michael@0 556 * can avoid a slow shutdown. After writing out the cache, the zipreader is
michael@0 557 * reloaded on the worker thread.
michael@0 558 */
michael@0 559 void
michael@0 560 StartupCache::WriteTimeout(nsITimer *aTimer, void *aClosure)
michael@0 561 {
michael@0 562 /*
michael@0 563 * It is safe to use the pointer passed in aClosure to reference the
michael@0 564 * StartupCache object because the timer's lifetime is tightly coupled to
michael@0 565 * the lifetime of the StartupCache object; this timer is canceled in the
michael@0 566 * StartupCache destructor, guaranteeing that this function runs if and only
michael@0 567 * if the StartupCache object is valid.
michael@0 568 */
michael@0 569 StartupCache* startupCacheObj = static_cast<StartupCache*>(aClosure);
michael@0 570 startupCacheObj->mWriteThread = PR_CreateThread(PR_USER_THREAD,
michael@0 571 StartupCache::ThreadedWrite,
michael@0 572 startupCacheObj,
michael@0 573 PR_PRIORITY_NORMAL,
michael@0 574 PR_GLOBAL_THREAD,
michael@0 575 PR_JOINABLE_THREAD,
michael@0 576 0);
michael@0 577 }
michael@0 578
michael@0 579 // We don't want to refcount StartupCache, so we'll just
michael@0 580 // hold a ref to this and pass it to observerService instead.
michael@0 581 NS_IMPL_ISUPPORTS(StartupCacheListener, nsIObserver)
michael@0 582
michael@0 583 nsresult
michael@0 584 StartupCacheListener::Observe(nsISupports *subject, const char* topic, const char16_t* data)
michael@0 585 {
michael@0 586 StartupCache* sc = StartupCache::GetSingleton();
michael@0 587 if (!sc)
michael@0 588 return NS_OK;
michael@0 589
michael@0 590 if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
michael@0 591 // Do not leave the thread running past xpcom shutdown
michael@0 592 sc->WaitOnWriteThread();
michael@0 593 StartupCache::gShutdownInitiated = true;
michael@0 594 } else if (strcmp(topic, "startupcache-invalidate") == 0) {
michael@0 595 sc->InvalidateCache();
michael@0 596 }
michael@0 597 return NS_OK;
michael@0 598 }
michael@0 599
michael@0 600 nsresult
michael@0 601 StartupCache::GetDebugObjectOutputStream(nsIObjectOutputStream* aStream,
michael@0 602 nsIObjectOutputStream** aOutStream)
michael@0 603 {
michael@0 604 NS_ENSURE_ARG_POINTER(aStream);
michael@0 605 #ifdef DEBUG
michael@0 606 StartupCacheDebugOutputStream* stream
michael@0 607 = new StartupCacheDebugOutputStream(aStream, &mWriteObjectMap);
michael@0 608 NS_ADDREF(*aOutStream = stream);
michael@0 609 #else
michael@0 610 NS_ADDREF(*aOutStream = aStream);
michael@0 611 #endif
michael@0 612
michael@0 613 return NS_OK;
michael@0 614 }
michael@0 615
michael@0 616 nsresult
michael@0 617 StartupCache::ResetStartupWriteTimer()
michael@0 618 {
michael@0 619 mStartupWriteInitiated = false;
michael@0 620 nsresult rv;
michael@0 621 if (!mTimer)
michael@0 622 mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
michael@0 623 else
michael@0 624 rv = mTimer->Cancel();
michael@0 625 NS_ENSURE_SUCCESS(rv, rv);
michael@0 626 // Wait for 10 seconds, then write out the cache.
michael@0 627 mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, this, 60000,
michael@0 628 nsITimer::TYPE_ONE_SHOT);
michael@0 629 return NS_OK;
michael@0 630 }
michael@0 631
michael@0 632 nsresult
michael@0 633 StartupCache::RecordAgesAlways()
michael@0 634 {
michael@0 635 gPostFlushAgeAction = RECORD_AGE;
michael@0 636 return NS_OK;
michael@0 637 }
michael@0 638
michael@0 639 // StartupCacheDebugOutputStream implementation
michael@0 640 #ifdef DEBUG
michael@0 641 NS_IMPL_ISUPPORTS(StartupCacheDebugOutputStream, nsIObjectOutputStream,
michael@0 642 nsIBinaryOutputStream, nsIOutputStream)
michael@0 643
michael@0 644 bool
michael@0 645 StartupCacheDebugOutputStream::CheckReferences(nsISupports* aObject)
michael@0 646 {
michael@0 647 nsresult rv;
michael@0 648
michael@0 649 nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject);
michael@0 650 if (!classInfo) {
michael@0 651 NS_ERROR("aObject must implement nsIClassInfo");
michael@0 652 return false;
michael@0 653 }
michael@0 654
michael@0 655 uint32_t flags;
michael@0 656 rv = classInfo->GetFlags(&flags);
michael@0 657 NS_ENSURE_SUCCESS(rv, false);
michael@0 658 if (flags & nsIClassInfo::SINGLETON)
michael@0 659 return true;
michael@0 660
michael@0 661 nsISupportsHashKey* key = mObjectMap->GetEntry(aObject);
michael@0 662 if (key) {
michael@0 663 NS_ERROR("non-singleton aObject is referenced multiple times in this"
michael@0 664 "serialization, we don't support that.");
michael@0 665 return false;
michael@0 666 }
michael@0 667
michael@0 668 mObjectMap->PutEntry(aObject);
michael@0 669 return true;
michael@0 670 }
michael@0 671
michael@0 672 // nsIObjectOutputStream implementation
michael@0 673 nsresult
michael@0 674 StartupCacheDebugOutputStream::WriteObject(nsISupports* aObject, bool aIsStrongRef)
michael@0 675 {
michael@0 676 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
michael@0 677
michael@0 678 NS_ASSERTION(rootObject.get() == aObject,
michael@0 679 "bad call to WriteObject -- call WriteCompoundObject!");
michael@0 680 bool check = CheckReferences(aObject);
michael@0 681 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
michael@0 682 return mBinaryStream->WriteObject(aObject, aIsStrongRef);
michael@0 683 }
michael@0 684
michael@0 685 nsresult
michael@0 686 StartupCacheDebugOutputStream::WriteSingleRefObject(nsISupports* aObject)
michael@0 687 {
michael@0 688 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
michael@0 689
michael@0 690 NS_ASSERTION(rootObject.get() == aObject,
michael@0 691 "bad call to WriteSingleRefObject -- call WriteCompoundObject!");
michael@0 692 bool check = CheckReferences(aObject);
michael@0 693 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
michael@0 694 return mBinaryStream->WriteSingleRefObject(aObject);
michael@0 695 }
michael@0 696
michael@0 697 nsresult
michael@0 698 StartupCacheDebugOutputStream::WriteCompoundObject(nsISupports* aObject,
michael@0 699 const nsIID& aIID,
michael@0 700 bool aIsStrongRef)
michael@0 701 {
michael@0 702 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
michael@0 703
michael@0 704 nsCOMPtr<nsISupports> roundtrip;
michael@0 705 rootObject->QueryInterface(aIID, getter_AddRefs(roundtrip));
michael@0 706 NS_ASSERTION(roundtrip.get() == aObject,
michael@0 707 "bad aggregation or multiple inheritance detected by call to "
michael@0 708 "WriteCompoundObject!");
michael@0 709
michael@0 710 bool check = CheckReferences(aObject);
michael@0 711 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
michael@0 712 return mBinaryStream->WriteCompoundObject(aObject, aIID, aIsStrongRef);
michael@0 713 }
michael@0 714
michael@0 715 nsresult
michael@0 716 StartupCacheDebugOutputStream::WriteID(nsID const& aID)
michael@0 717 {
michael@0 718 return mBinaryStream->WriteID(aID);
michael@0 719 }
michael@0 720
michael@0 721 char*
michael@0 722 StartupCacheDebugOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask)
michael@0 723 {
michael@0 724 return mBinaryStream->GetBuffer(aLength, aAlignMask);
michael@0 725 }
michael@0 726
michael@0 727 void
michael@0 728 StartupCacheDebugOutputStream::PutBuffer(char* aBuffer, uint32_t aLength)
michael@0 729 {
michael@0 730 mBinaryStream->PutBuffer(aBuffer, aLength);
michael@0 731 }
michael@0 732 #endif //DEBUG
michael@0 733
michael@0 734 StartupCacheWrapper* StartupCacheWrapper::gStartupCacheWrapper = nullptr;
michael@0 735
michael@0 736 NS_IMPL_ISUPPORTS(StartupCacheWrapper, nsIStartupCache)
michael@0 737
michael@0 738 StartupCacheWrapper* StartupCacheWrapper::GetSingleton()
michael@0 739 {
michael@0 740 if (!gStartupCacheWrapper)
michael@0 741 gStartupCacheWrapper = new StartupCacheWrapper();
michael@0 742
michael@0 743 NS_ADDREF(gStartupCacheWrapper);
michael@0 744 return gStartupCacheWrapper;
michael@0 745 }
michael@0 746
michael@0 747 nsresult
michael@0 748 StartupCacheWrapper::GetBuffer(const char* id, char** outbuf, uint32_t* length)
michael@0 749 {
michael@0 750 StartupCache* sc = StartupCache::GetSingleton();
michael@0 751 if (!sc) {
michael@0 752 return NS_ERROR_NOT_INITIALIZED;
michael@0 753 }
michael@0 754 return sc->GetBuffer(id, outbuf, length);
michael@0 755 }
michael@0 756
michael@0 757 nsresult
michael@0 758 StartupCacheWrapper::PutBuffer(const char* id, const char* inbuf, uint32_t length)
michael@0 759 {
michael@0 760 StartupCache* sc = StartupCache::GetSingleton();
michael@0 761 if (!sc) {
michael@0 762 return NS_ERROR_NOT_INITIALIZED;
michael@0 763 }
michael@0 764 return sc->PutBuffer(id, inbuf, length);
michael@0 765 }
michael@0 766
michael@0 767 nsresult
michael@0 768 StartupCacheWrapper::InvalidateCache()
michael@0 769 {
michael@0 770 StartupCache* sc = StartupCache::GetSingleton();
michael@0 771 if (!sc) {
michael@0 772 return NS_ERROR_NOT_INITIALIZED;
michael@0 773 }
michael@0 774 sc->InvalidateCache();
michael@0 775 return NS_OK;
michael@0 776 }
michael@0 777
michael@0 778 nsresult
michael@0 779 StartupCacheWrapper::IgnoreDiskCache()
michael@0 780 {
michael@0 781 StartupCache::IgnoreDiskCache();
michael@0 782 return NS_OK;
michael@0 783 }
michael@0 784
michael@0 785 nsresult
michael@0 786 StartupCacheWrapper::GetDebugObjectOutputStream(nsIObjectOutputStream* stream,
michael@0 787 nsIObjectOutputStream** outStream)
michael@0 788 {
michael@0 789 StartupCache* sc = StartupCache::GetSingleton();
michael@0 790 if (!sc) {
michael@0 791 return NS_ERROR_NOT_INITIALIZED;
michael@0 792 }
michael@0 793 return sc->GetDebugObjectOutputStream(stream, outStream);
michael@0 794 }
michael@0 795
michael@0 796 nsresult
michael@0 797 StartupCacheWrapper::StartupWriteComplete(bool *complete)
michael@0 798 {
michael@0 799 StartupCache* sc = StartupCache::GetSingleton();
michael@0 800 if (!sc) {
michael@0 801 return NS_ERROR_NOT_INITIALIZED;
michael@0 802 }
michael@0 803 sc->WaitOnWriteThread();
michael@0 804 *complete = sc->mStartupWriteInitiated && sc->mTable.Count() == 0;
michael@0 805 return NS_OK;
michael@0 806 }
michael@0 807
michael@0 808 nsresult
michael@0 809 StartupCacheWrapper::ResetStartupWriteTimer()
michael@0 810 {
michael@0 811 StartupCache* sc = StartupCache::GetSingleton();
michael@0 812 return sc ? sc->ResetStartupWriteTimer() : NS_ERROR_NOT_INITIALIZED;
michael@0 813 }
michael@0 814
michael@0 815 nsresult
michael@0 816 StartupCacheWrapper::GetObserver(nsIObserver** obv) {
michael@0 817 StartupCache* sc = StartupCache::GetSingleton();
michael@0 818 if (!sc) {
michael@0 819 return NS_ERROR_NOT_INITIALIZED;
michael@0 820 }
michael@0 821 NS_ADDREF(*obv = sc->mListener);
michael@0 822 return NS_OK;
michael@0 823 }
michael@0 824
michael@0 825 nsresult
michael@0 826 StartupCacheWrapper::RecordAgesAlways() {
michael@0 827 StartupCache *sc = StartupCache::GetSingleton();
michael@0 828 return sc ? sc->RecordAgesAlways() : NS_ERROR_NOT_INITIALIZED;
michael@0 829 }
michael@0 830
michael@0 831 } // namespace scache
michael@0 832 } // namespace mozilla

mercurial