Tracer build fixes. (b=588021, r=dvander)
[mozilla-central.git] / startupcache / StartupCache.cpp
blobfb0c911725601ce76f9b8f9995d5febccb846226
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
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/
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.
15 * The Original Code is Startup Cache.
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.
22 * Contributor(s):
23 * Benedict Hsieh <bhsieh@mozilla.com>
24 * Taras Glek <tglek@mozilla.com>
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.
38 * ***** END LICENSE BLOCK ***** */
40 #include "prio.h"
41 #include "prtypes.h"
42 #include "pldhash.h"
43 #include "mozilla/scache/StartupCache.h"
45 #include "nsAutoPtr.h"
46 #include "nsClassHashtable.h"
47 #include "nsComponentManagerUtils.h"
48 #include "nsDirectoryServiceUtils.h"
49 #include "nsIClassInfo.h"
50 #include "nsIFile.h"
51 #include "nsILocalFile.h"
52 #include "nsIObserver.h"
53 #include "nsIObserverService.h"
54 #include "nsIOutputStream.h"
55 #include "nsIStartupCache.h"
56 #include "nsIStorageStream.h"
57 #include "nsIStreamBufferAccess.h"
58 #include "nsIStringStream.h"
59 #include "nsISupports.h"
60 #include "nsITimer.h"
61 #include "nsIZipWriter.h"
62 #include "nsIZipReader.h"
63 #include "nsWeakReference.h"
64 #include "nsZipArchive.h"
66 #ifdef IS_BIG_ENDIAN
67 #define SC_ENDIAN "big"
68 #else
69 #define SC_ENDIAN "little"
70 #endif
72 #if PR_BYTES_PER_WORD == 4
73 #define SC_WORDSIZE "4"
74 #else
75 #define SC_WORDSIZE "8"
76 #endif
78 namespace mozilla {
79 namespace scache {
81 static const char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN;
82 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
84 StartupCache*
85 StartupCache::GetSingleton()
87 if (!gStartupCache)
88 StartupCache::InitSingleton();
90 return StartupCache::gStartupCache;
93 void
94 StartupCache::DeleteSingleton()
96 delete StartupCache::gStartupCache;
99 nsresult
100 StartupCache::InitSingleton()
102 nsresult rv;
103 StartupCache::gStartupCache = new StartupCache();
105 rv = StartupCache::gStartupCache->Init();
106 if (NS_FAILED(rv)) {
107 delete StartupCache::gStartupCache;
108 StartupCache::gStartupCache = nsnull;
110 return rv;
113 StartupCache* StartupCache::gStartupCache;
114 PRBool StartupCache::gShutdownInitiated;
116 StartupCache::StartupCache()
117 : mArchive(NULL), mStartupWriteInitiated(PR_FALSE) { }
119 StartupCache::~StartupCache()
121 if (mTimer) {
122 mTimer->Cancel();
125 // Generally, the in-memory table should be empty here,
126 // but in special cases (like Talos Ts tests) we
127 // could shut down before we write.
128 // This mechanism will change when IO is moved off-thread
129 // (bug 586859) or when Talos first-run is changed to allow
130 // our timer to work (bug 591471).
131 WriteToDisk();
132 gStartupCache = nsnull;
135 nsresult
136 StartupCache::Init()
138 nsresult rv;
139 mTable.Init();
140 #ifdef DEBUG
141 mWriteObjectMap.Init();
142 #endif
144 mZipW = do_CreateInstance("@mozilla.org/zipwriter;1", &rv);
145 NS_ENSURE_SUCCESS(rv, rv);
146 nsCOMPtr<nsIFile> file;
147 rv = NS_GetSpecialDirectory("ProfLDS",
148 getter_AddRefs(file));
149 if (NS_FAILED(rv)) {
150 // return silently, this will fail in mochitests's xpcshell process.
151 return rv;
154 rv = file->AppendNative(NS_LITERAL_CSTRING("startupCache"));
155 NS_ENSURE_SUCCESS(rv, rv);
157 // Try to create the directory if it's not there yet
158 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0777);
159 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
160 return rv;
162 rv = file->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName));
163 NS_ENSURE_SUCCESS(rv, rv);
165 mFile = do_QueryInterface(file);
166 NS_ENSURE_TRUE(mFile, NS_ERROR_UNEXPECTED);
168 mObserverService = do_GetService("@mozilla.org/observer-service;1");
170 if (!mObserverService) {
171 NS_WARNING("Could not get observerService.");
172 return NS_ERROR_UNEXPECTED;
175 mListener = new StartupCacheListener();
176 rv = mObserverService->AddObserver(mListener, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
177 PR_FALSE);
178 NS_ENSURE_SUCCESS(rv, rv);
179 rv = mObserverService->AddObserver(mListener, "startupcache-invalidate",
180 PR_FALSE);
181 NS_ENSURE_SUCCESS(rv, rv);
183 rv = LoadArchive();
185 // Sometimes we don't have a cache yet, that's ok.
186 // If it's corrupted, just remove it and start over.
187 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
188 NS_WARNING("Failed to load startupcache file correctly, removing!");
189 InvalidateCache();
192 mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
193 NS_ENSURE_SUCCESS(rv, rv);
194 // Wait for 10 seconds, then write out the cache.
195 rv = mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, this, 600000,
196 nsITimer::TYPE_ONE_SHOT);
198 return rv;
201 nsresult
202 StartupCache::LoadArchive()
204 PRBool exists;
205 mArchive = NULL;
206 nsresult rv = mFile->Exists(&exists);
207 if (NS_FAILED(rv) || !exists)
208 return NS_ERROR_FILE_NOT_FOUND;
210 mArchive = new nsZipArchive();
211 return mArchive->OpenArchive(mFile);
214 // NOTE: this will not find a new entry until it has been written to disk!
215 // Consumer should take ownership of the resulting buffer.
216 nsresult
217 StartupCache::GetBuffer(const char* id, char** outbuf, PRUint32* length)
219 if (!mStartupWriteInitiated) {
220 CacheEntry* entry;
221 nsDependentCString idStr(id);
222 mTable.Get(idStr, &entry);
223 if (entry) {
224 *outbuf = new char[entry->size];
225 memcpy(*outbuf, entry->data, entry->size);
226 *length = entry->size;
227 return NS_OK;
231 if (mArchive) {
232 nsZipItemPtr<char> zipItem(mArchive, id, true);
233 if (zipItem) {
234 *outbuf = zipItem.Forget();
235 *length = zipItem.Length();
236 return NS_OK;
240 return NS_ERROR_NOT_AVAILABLE;
243 // Makes a copy of the buffer, client retains ownership of inbuf.
244 nsresult
245 StartupCache::PutBuffer(const char* id, const char* inbuf, PRUint32 len)
247 nsresult rv;
249 if (StartupCache::gShutdownInitiated) {
250 return NS_ERROR_NOT_AVAILABLE;
253 nsAutoArrayPtr<char> data(new char[len]);
254 memcpy(data, inbuf, len);
256 nsDependentCString idStr(id);
257 if (!mStartupWriteInitiated) {
258 // Cache it for now, we'll write all together later.
259 CacheEntry* entry;
261 #ifdef DEBUG
262 mTable.Get(idStr, &entry);
263 NS_ASSERTION(entry == nsnull, "Existing entry in StartupCache.");
265 if (mArchive) {
266 nsZipItem* zipItem = mArchive->GetItem(id);
267 NS_ASSERTION(zipItem == nsnull, "Existing entry in disk StartupCache.");
269 #endif
271 entry = new CacheEntry(data.forget(), len);
272 mTable.Put(idStr, entry);
273 return NS_OK;
276 rv = mZipW->Open(mFile, PR_RDWR | PR_CREATE_FILE);
277 NS_ENSURE_SUCCESS(rv, rv);
279 // XXX We need to think about whether to write this out every time,
280 // or somehow detect a good time to write. We need to finish writing
281 // before shutdown though, and writing also requires a reload of the
282 // reader's archive, which probably can't handle having the underlying
283 // file change underneath it. Potentially could reload on the next
284 // read request, if this is a problem. See Bug 586859.
285 #ifdef DEBUG
286 PRBool hasEntry;
287 rv = mZipW->HasEntry(idStr, &hasEntry);
288 NS_ENSURE_SUCCESS(rv, rv);
289 NS_ASSERTION(hasEntry == PR_FALSE, "Existing entry in disk StartupCache.");
290 #endif
292 nsCOMPtr<nsIStringInputStream> stream
293 = do_CreateInstance("@mozilla.org/io/string-input-stream;1",
294 &rv);
295 NS_ENSURE_SUCCESS(rv, rv);
297 rv = stream->AdoptData(data, len);
298 NS_ENSURE_SUCCESS(rv, rv);
299 data.forget();
301 rv = mZipW->AddEntryStream(idStr, 0, 0, stream, false);
302 NS_ENSURE_SUCCESS(rv, rv);
304 // Close the archive so Windows doesn't choke.
305 mArchive = NULL;
306 rv = mZipW->Close();
307 NS_ENSURE_SUCCESS(rv, rv);
309 // our reader's view of the archive is outdated now, reload it.
310 return LoadArchive();
313 struct CacheWriteHolder
315 nsCOMPtr<nsIZipWriter> writer;
316 nsCOMPtr<nsIStringInputStream> stream;
319 PLDHashOperator
320 CacheCloseHelper(const nsACString& key, nsAutoPtr<CacheEntry>& data,
321 void* closure)
323 nsresult rv;
325 CacheWriteHolder* holder = (CacheWriteHolder*) closure;
326 nsIStringInputStream* stream = holder->stream;
327 nsIZipWriter* writer = holder->writer;
329 stream->ShareData(data->data, data->size);
331 #ifdef DEBUG
332 PRBool hasEntry;
333 rv = writer->HasEntry(key, &hasEntry);
334 NS_ASSERTION(NS_SUCCEEDED(rv) && hasEntry == PR_FALSE,
335 "Existing entry in disk StartupCache.");
336 #endif
337 rv = writer->AddEntryStream(key, 0, 0, stream, false);
339 if (NS_FAILED(rv)) {
340 NS_WARNING("cache entry deleted but not written to disk.");
342 return PL_DHASH_REMOVE;
345 void
346 StartupCache::WriteToDisk()
348 nsresult rv;
349 mStartupWriteInitiated = PR_TRUE;
351 if (mTable.Count() == 0)
352 return;
354 rv = mZipW->Open(mFile, PR_RDWR | PR_CREATE_FILE);
355 if (NS_FAILED(rv)) {
356 NS_WARNING("could not open zipfile for write");
357 return;
360 nsCOMPtr<nsIStringInputStream> stream
361 = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
362 if (NS_FAILED(rv)) {
363 NS_WARNING("Couldn't create string input stream.");
364 return;
367 CacheWriteHolder holder;
368 holder.stream = stream;
369 holder.writer = mZipW;
371 mTable.Enumerate(CacheCloseHelper, &holder);
373 // Close the archive so Windows doesn't choke.
374 mArchive = NULL;
375 mZipW->Close();
377 // our reader's view of the archive is outdated now, reload it.
378 LoadArchive();
380 return;
383 void
384 StartupCache::InvalidateCache()
386 mTable.Clear();
387 mArchive = NULL;
389 // This is usually closed, but it's possible to get into
390 // an inconsistent state.
391 mZipW->Close();
392 mFile->Remove(false);
393 LoadArchive();
396 void
397 StartupCache::WriteTimeout(nsITimer *aTimer, void *aClosure)
399 StartupCache* sc = (StartupCache*) aClosure;
400 sc->WriteToDisk();
403 // We don't want to refcount StartupCache, so we'll just
404 // hold a ref to this and pass it to observerService instead.
405 NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheListener, nsIObserver)
407 nsresult
408 StartupCacheListener::Observe(nsISupports *subject, const char* topic, const PRUnichar* data)
410 nsresult rv = NS_OK;
411 if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
412 StartupCache::gShutdownInitiated = PR_TRUE;
413 } else if (strcmp(topic, "startupcache-invalidate") == 0) {
414 StartupCache* sc = StartupCache::GetSingleton();
415 if (sc)
416 sc->InvalidateCache();
418 return rv;
421 nsresult
422 StartupCache::GetDebugObjectOutputStream(nsIObjectOutputStream* aStream,
423 nsIObjectOutputStream** aOutStream)
425 NS_ENSURE_ARG_POINTER(aStream);
426 #ifdef DEBUG
427 StartupCacheDebugOutputStream* stream
428 = new StartupCacheDebugOutputStream(aStream, &mWriteObjectMap);
429 NS_ADDREF(*aOutStream = stream);
430 #else
431 NS_ADDREF(*aOutStream = aStream);
432 #endif
434 return NS_OK;
437 // StartupCacheDebugOutputStream implementation
438 #ifdef DEBUG
439 NS_IMPL_ISUPPORTS3(StartupCacheDebugOutputStream, nsIObjectOutputStream,
440 nsIBinaryOutputStream, nsIOutputStream)
442 PRBool
443 StartupCacheDebugOutputStream::CheckReferences(nsISupports* aObject)
445 nsresult rv;
447 nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject);
448 if (!classInfo) {
449 NS_ERROR("aObject must implement nsIClassInfo");
450 return PR_FALSE;
453 PRUint32 flags;
454 rv = classInfo->GetFlags(&flags);
455 NS_ENSURE_SUCCESS(rv, rv);
456 if (flags & nsIClassInfo::SINGLETON)
457 return PR_TRUE;
459 nsISupportsHashKey* key = mObjectMap->GetEntry(aObject);
460 if (key) {
461 NS_ERROR("non-singleton aObject is referenced multiple times in this"
462 "serialization, we don't support that.");
463 return PR_FALSE;
466 mObjectMap->PutEntry(aObject);
467 return PR_TRUE;
470 // nsIObjectOutputStream implementation
471 nsresult
472 StartupCacheDebugOutputStream::WriteObject(nsISupports* aObject, PRBool aIsStrongRef)
474 nsresult rv;
476 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
478 NS_ASSERTION(rootObject.get() == aObject,
479 "bad call to WriteObject -- call WriteCompoundObject!");
480 PRBool check = CheckReferences(aObject);
481 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
482 return mBinaryStream->WriteObject(aObject, aIsStrongRef);
485 nsresult
486 StartupCacheDebugOutputStream::WriteSingleRefObject(nsISupports* aObject)
488 nsresult rv;
489 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
491 NS_ASSERTION(rootObject.get() == aObject,
492 "bad call to WriteSingleRefObject -- call WriteCompoundObject!");
493 PRBool check = CheckReferences(aObject);
494 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
495 return mBinaryStream->WriteSingleRefObject(aObject);
498 nsresult
499 StartupCacheDebugOutputStream::WriteCompoundObject(nsISupports* aObject,
500 const nsIID& aIID,
501 PRBool aIsStrongRef)
503 nsresult rv;
504 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
506 nsCOMPtr<nsISupports> roundtrip;
507 rootObject->QueryInterface(aIID, getter_AddRefs(roundtrip));
508 NS_ASSERTION(roundtrip.get() == aObject,
509 "bad aggregation or multiple inheritance detected by call to "
510 "WriteCompoundObject!");
512 PRBool check = CheckReferences(aObject);
513 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
514 return mBinaryStream->WriteCompoundObject(aObject, aIID, aIsStrongRef);
517 nsresult
518 StartupCacheDebugOutputStream::WriteID(nsID const& aID)
520 return mBinaryStream->WriteID(aID);
523 char*
524 StartupCacheDebugOutputStream::GetBuffer(PRUint32 aLength, PRUint32 aAlignMask)
526 return mBinaryStream->GetBuffer(aLength, aAlignMask);
529 void
530 StartupCacheDebugOutputStream::PutBuffer(char* aBuffer, PRUint32 aLength)
532 mBinaryStream->PutBuffer(aBuffer, aLength);
534 #endif //DEBUG
536 StartupCacheWrapper* StartupCacheWrapper::gStartupCacheWrapper = nsnull;
538 NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheWrapper, nsIStartupCache)
540 StartupCacheWrapper* StartupCacheWrapper::GetSingleton()
542 if (!gStartupCacheWrapper)
543 gStartupCacheWrapper = new StartupCacheWrapper();
545 NS_ADDREF(gStartupCacheWrapper);
546 return gStartupCacheWrapper;
549 nsresult
550 StartupCacheWrapper::GetBuffer(const char* id, char** outbuf, PRUint32* length)
552 StartupCache* sc = StartupCache::GetSingleton();
553 if (!sc) {
554 return NS_ERROR_NOT_INITIALIZED;
556 return sc->GetBuffer(id, outbuf, length);
559 nsresult
560 StartupCacheWrapper::PutBuffer(const char* id, char* inbuf, PRUint32 length)
562 StartupCache* sc = StartupCache::GetSingleton();
563 if (!sc) {
564 return NS_ERROR_NOT_INITIALIZED;
566 return sc->PutBuffer(id, inbuf, length);
569 nsresult
570 StartupCacheWrapper::InvalidateCache()
572 StartupCache* sc = StartupCache::GetSingleton();
573 if (!sc) {
574 return NS_ERROR_NOT_INITIALIZED;
576 sc->InvalidateCache();
577 return NS_OK;
580 nsresult
581 StartupCacheWrapper::GetDebugObjectOutputStream(nsIObjectOutputStream* stream,
582 nsIObjectOutputStream** outStream)
584 StartupCache* sc = StartupCache::GetSingleton();
585 if (!sc) {
586 return NS_ERROR_NOT_INITIALIZED;
588 return sc->GetDebugObjectOutputStream(stream, outStream);
591 nsresult
592 StartupCacheWrapper::StartupWriteComplete(PRBool *complete)
594 StartupCache* sc = StartupCache::GetSingleton();
595 if (!sc) {
596 return NS_ERROR_NOT_INITIALIZED;
598 *complete = sc->mStartupWriteInitiated && sc->mTable.Count() == 0;
599 return NS_OK;
602 nsresult
603 StartupCacheWrapper::ResetStartupWriteTimer()
605 StartupCache* sc = StartupCache::GetSingleton();
606 if (!sc) {
607 return NS_ERROR_NOT_INITIALIZED;
609 sc->mStartupWriteInitiated = PR_FALSE;
611 // Init with a shorter timer, for testing convenience.
612 sc->mTimer->Cancel();
613 sc->mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, sc, 10000,
614 nsITimer::TYPE_ONE_SHOT);
615 return NS_OK;
618 nsresult
619 StartupCacheWrapper::GetObserver(nsIObserver** obv) {
620 StartupCache* sc = StartupCache::GetSingleton();
621 if (!sc) {
622 return NS_ERROR_NOT_INITIALIZED;
624 NS_ADDREF(*obv = sc->mListener);
625 return NS_OK;
628 } // namespace scache
629 } // namespace mozilla