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