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