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