Fixed register allocation bug in left-shift operations (bug 606063, r=dmandelin).
[mozilla-central.git] / startupcache / StartupCache.cpp
blobb31a99c8243613b663d89d0082d8bfbd2592b66b
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>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "prio.h"
40 #include "prtypes.h"
41 #include "pldhash.h"
42 #include "mozilla/scache/StartupCache.h"
44 #include "nsAutoPtr.h"
45 #include "nsClassHashtable.h"
46 #include "nsComponentManagerUtils.h"
47 #include "nsDirectoryServiceUtils.h"
48 #include "nsIClassInfo.h"
49 #include "nsIFile.h"
50 #include "nsILocalFile.h"
51 #include "nsIObserver.h"
52 #include "nsIObserverService.h"
53 #include "nsIOutputStream.h"
54 #include "nsIStartupCache.h"
55 #include "nsIStorageStream.h"
56 #include "nsIStreamBufferAccess.h"
57 #include "nsIStringStream.h"
58 #include "nsISupports.h"
59 #include "nsITimer.h"
60 #include "nsIZipWriter.h"
61 #include "nsIZipReader.h"
62 #include "nsWeakReference.h"
63 #include "nsZipArchive.h"
65 #ifdef IS_BIG_ENDIAN
66 #define SC_ENDIAN "big"
67 #else
68 #define SC_ENDIAN "little"
69 #endif
71 #if PR_BYTES_PER_WORD == 4
72 #define SC_WORDSIZE "4"
73 #else
74 #define SC_WORDSIZE "8"
75 #endif
77 namespace mozilla {
78 namespace scache {
80 static const char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN;
81 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
83 StartupCache*
84 StartupCache::GetSingleton()
86 if (!gStartupCache)
87 StartupCache::InitSingleton();
89 return StartupCache::gStartupCache;
92 void
93 StartupCache::DeleteSingleton()
95 delete StartupCache::gStartupCache;
98 nsresult
99 StartupCache::InitSingleton()
101 nsresult rv;
102 StartupCache::gStartupCache = new StartupCache();
104 rv = StartupCache::gStartupCache->Init();
105 if (NS_FAILED(rv)) {
106 delete StartupCache::gStartupCache;
107 StartupCache::gStartupCache = nsnull;
109 return rv;
112 StartupCache* StartupCache::gStartupCache;
113 PRBool StartupCache::gShutdownInitiated;
115 StartupCache::StartupCache()
116 : mArchive(NULL), mStartupWriteInitiated(PR_FALSE) { }
118 StartupCache::~StartupCache()
120 if (mTimer) {
121 mTimer->Cancel();
124 // Generally, the in-memory table should be empty here,
125 // but in special cases (like Talos Ts tests) we
126 // could shut down before we write.
127 // This mechanism will change when IO is moved off-thread
128 // (bug 586859) or when Talos first-run is changed to allow
129 // our timer to work (bug 591471).
130 WriteToDisk();
131 gStartupCache = nsnull;
134 nsresult
135 StartupCache::Init()
137 nsresult rv;
138 mTable.Init();
139 #ifdef DEBUG
140 mWriteObjectMap.Init();
141 #endif
143 mZipW = do_CreateInstance("@mozilla.org/zipwriter;1", &rv);
144 NS_ENSURE_SUCCESS(rv, rv);
145 nsCOMPtr<nsIFile> file;
146 rv = NS_GetSpecialDirectory("ProfLDS",
147 getter_AddRefs(file));
148 if (NS_FAILED(rv)) {
149 // return silently, this will fail in mochitests's xpcshell process.
150 return rv;
153 rv = file->AppendNative(NS_LITERAL_CSTRING("startupCache"));
154 NS_ENSURE_SUCCESS(rv, rv);
156 // Try to create the directory if it's not there yet
157 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0777);
158 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
159 return rv;
161 rv = file->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName));
162 NS_ENSURE_SUCCESS(rv, rv);
164 mFile = do_QueryInterface(file);
165 NS_ENSURE_TRUE(mFile, NS_ERROR_UNEXPECTED);
167 mObserverService = do_GetService("@mozilla.org/observer-service;1");
169 if (!mObserverService) {
170 NS_WARNING("Could not get observerService.");
171 return NS_ERROR_UNEXPECTED;
174 mListener = new StartupCacheListener();
175 rv = mObserverService->AddObserver(mListener, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
176 PR_FALSE);
177 NS_ENSURE_SUCCESS(rv, rv);
178 rv = mObserverService->AddObserver(mListener, "startupcache-invalidate",
179 PR_FALSE);
180 NS_ENSURE_SUCCESS(rv, rv);
182 rv = LoadArchive();
184 // Sometimes we don't have a cache yet, that's ok.
185 // If it's corrupted, just remove it and start over.
186 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
187 NS_WARNING("Failed to load startupcache file correctly, removing!");
188 InvalidateCache();
191 mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
192 NS_ENSURE_SUCCESS(rv, rv);
193 // Wait for 10 seconds, then write out the cache.
194 rv = mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, this, 600000,
195 nsITimer::TYPE_ONE_SHOT);
197 return rv;
200 nsresult
201 StartupCache::LoadArchive()
203 PRBool exists;
204 mArchive = NULL;
205 nsresult rv = mFile->Exists(&exists);
206 if (NS_FAILED(rv) || !exists)
207 return NS_ERROR_FILE_NOT_FOUND;
209 mArchive = new nsZipArchive();
210 return mArchive->OpenArchive(mFile);
213 // NOTE: this will not find a new entry until it has been written to disk!
214 // Consumer should take ownership of the resulting buffer.
215 nsresult
216 StartupCache::GetBuffer(const char* id, char** outbuf, PRUint32* length)
218 char* data = NULL;
219 PRUint32 len;
221 if (!mStartupWriteInitiated) {
222 CacheEntry* entry;
223 nsDependentCString idStr(id);
224 mTable.Get(idStr, &entry);
225 if (entry) {
226 data = entry->data;
227 len = entry->size;
231 if (!data && mArchive) {
232 nsZipItem* zipItem = mArchive->GetItem(id);
233 if (zipItem) {
234 const PRUint8* itemData = mArchive->GetData(zipItem);
235 if (!itemData || !mArchive->CheckCRC(zipItem, itemData)) {
236 NS_WARNING("StartupCache file corrupted!");
237 InvalidateCache();
238 return NS_ERROR_FILE_CORRUPTED;
241 len = zipItem->Size();
242 data = (char*) itemData;
246 if (data) {
247 *outbuf = new char[len];
248 memcpy(*outbuf, data, len);
249 *length = len;
250 return NS_OK;
253 return NS_ERROR_NOT_AVAILABLE;
256 // Makes a copy of the buffer, client retains ownership of inbuf.
257 nsresult
258 StartupCache::PutBuffer(const char* id, const char* inbuf, PRUint32 len)
260 nsresult rv;
262 if (StartupCache::gShutdownInitiated) {
263 return NS_ERROR_NOT_AVAILABLE;
266 nsAutoArrayPtr<char> data(new char[len]);
267 memcpy(data, inbuf, len);
269 nsDependentCString idStr(id);
270 if (!mStartupWriteInitiated) {
271 // Cache it for now, we'll write all together later.
272 CacheEntry* entry;
274 #ifdef DEBUG
275 mTable.Get(idStr, &entry);
276 NS_ASSERTION(entry == nsnull, "Existing entry in StartupCache.");
278 if (mArchive) {
279 nsZipItem* zipItem = mArchive->GetItem(id);
280 NS_ASSERTION(zipItem == nsnull, "Existing entry in disk StartupCache.");
282 #endif
284 entry = new CacheEntry(data.forget(), len);
285 mTable.Put(idStr, entry);
286 return NS_OK;
289 rv = mZipW->Open(mFile, PR_RDWR | PR_CREATE_FILE);
290 NS_ENSURE_SUCCESS(rv, rv);
292 // XXX We need to think about whether to write this out every time,
293 // or somehow detect a good time to write. We need to finish writing
294 // before shutdown though, and writing also requires a reload of the
295 // reader's archive, which probably can't handle having the underlying
296 // file change underneath it. Potentially could reload on the next
297 // read request, if this is a problem. See Bug 586859.
298 #ifdef DEBUG
299 PRBool hasEntry;
300 rv = mZipW->HasEntry(idStr, &hasEntry);
301 NS_ENSURE_SUCCESS(rv, rv);
302 NS_ASSERTION(hasEntry == PR_FALSE, "Existing entry in disk StartupCache.");
303 #endif
305 nsCOMPtr<nsIStringInputStream> stream
306 = do_CreateInstance("@mozilla.org/io/string-input-stream;1",
307 &rv);
308 NS_ENSURE_SUCCESS(rv, rv);
310 rv = stream->AdoptData(data, len);
311 NS_ENSURE_SUCCESS(rv, rv);
312 data.forget();
314 rv = mZipW->AddEntryStream(idStr, 0, 0, stream, false);
315 NS_ENSURE_SUCCESS(rv, rv);
317 // Close the archive so Windows doesn't choke.
318 mArchive = NULL;
319 rv = mZipW->Close();
320 NS_ENSURE_SUCCESS(rv, rv);
322 // our reader's view of the archive is outdated now, reload it.
323 return LoadArchive();
326 struct CacheWriteHolder
328 nsCOMPtr<nsIZipWriter> writer;
329 nsCOMPtr<nsIStringInputStream> stream;
332 PLDHashOperator
333 CacheCloseHelper(const nsACString& key, nsAutoPtr<CacheEntry>& data,
334 void* closure)
336 nsresult rv;
338 CacheWriteHolder* holder = (CacheWriteHolder*) closure;
339 nsIStringInputStream* stream = holder->stream;
340 nsIZipWriter* writer = holder->writer;
342 stream->ShareData(data->data, data->size);
344 #ifdef DEBUG
345 PRBool hasEntry;
346 rv = writer->HasEntry(key, &hasEntry);
347 NS_ASSERTION(NS_SUCCEEDED(rv) && hasEntry == PR_FALSE,
348 "Existing entry in disk StartupCache.");
349 #endif
350 rv = writer->AddEntryStream(key, 0, 0, stream, false);
352 if (NS_FAILED(rv)) {
353 NS_WARNING("cache entry deleted but not written to disk.");
355 return PL_DHASH_REMOVE;
358 void
359 StartupCache::WriteToDisk()
361 nsresult rv;
362 mStartupWriteInitiated = PR_TRUE;
364 if (mTable.Count() == 0)
365 return;
367 rv = mZipW->Open(mFile, PR_RDWR | PR_CREATE_FILE);
368 if (NS_FAILED(rv)) {
369 NS_WARNING("could not open zipfile for write");
370 return;
373 nsCOMPtr<nsIStringInputStream> stream
374 = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
375 if (NS_FAILED(rv)) {
376 NS_WARNING("Couldn't create string input stream.");
377 return;
380 CacheWriteHolder holder;
381 holder.stream = stream;
382 holder.writer = mZipW;
384 mTable.Enumerate(CacheCloseHelper, &holder);
386 // Close the archive so Windows doesn't choke.
387 mArchive = NULL;
388 mZipW->Close();
390 // our reader's view of the archive is outdated now, reload it.
391 LoadArchive();
393 return;
396 void
397 StartupCache::InvalidateCache()
399 mTable.Clear();
400 mArchive = NULL;
402 // This is usually closed, but it's possible to get into
403 // an inconsistent state.
404 mZipW->Close();
405 mFile->Remove(false);
406 LoadArchive();
409 void
410 StartupCache::WriteTimeout(nsITimer *aTimer, void *aClosure)
412 StartupCache* sc = (StartupCache*) aClosure;
413 sc->WriteToDisk();
416 // We don't want to refcount StartupCache, so we'll just
417 // hold a ref to this and pass it to observerService instead.
418 NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheListener, nsIObserver)
420 nsresult
421 StartupCacheListener::Observe(nsISupports *subject, const char* topic, const PRUnichar* data)
423 nsresult rv = NS_OK;
424 if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
425 StartupCache::gShutdownInitiated = PR_TRUE;
426 } else if (strcmp(topic, "startupcache-invalidate") == 0) {
427 StartupCache* sc = StartupCache::GetSingleton();
428 if (sc)
429 sc->InvalidateCache();
431 return rv;
434 nsresult
435 StartupCache::GetDebugObjectOutputStream(nsIObjectOutputStream* aStream,
436 nsIObjectOutputStream** aOutStream)
438 NS_ENSURE_ARG_POINTER(aStream);
439 #ifdef DEBUG
440 StartupCacheDebugOutputStream* stream
441 = new StartupCacheDebugOutputStream(aStream, &mWriteObjectMap);
442 NS_ADDREF(*aOutStream = stream);
443 #else
444 NS_ADDREF(*aOutStream = aStream);
445 #endif
447 return NS_OK;
450 // StartupCacheDebugOutputStream implementation
451 #ifdef DEBUG
452 NS_IMPL_ISUPPORTS3(StartupCacheDebugOutputStream, nsIObjectOutputStream,
453 nsIBinaryOutputStream, nsIOutputStream)
455 PRBool
456 StartupCacheDebugOutputStream::CheckReferences(nsISupports* aObject)
458 nsresult rv;
460 nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject);
461 if (!classInfo) {
462 NS_ERROR("aObject must implement nsIClassInfo");
463 return PR_FALSE;
466 PRUint32 flags;
467 rv = classInfo->GetFlags(&flags);
468 NS_ENSURE_SUCCESS(rv, rv);
469 if (flags & nsIClassInfo::SINGLETON)
470 return PR_TRUE;
472 nsISupportsHashKey* key = mObjectMap->GetEntry(aObject);
473 if (key) {
474 NS_ERROR("non-singleton aObject is referenced multiple times in this"
475 "serialization, we don't support that.");
476 return PR_FALSE;
479 mObjectMap->PutEntry(aObject);
480 return PR_TRUE;
483 // nsIObjectOutputStream implementation
484 nsresult
485 StartupCacheDebugOutputStream::WriteObject(nsISupports* aObject, PRBool aIsStrongRef)
487 nsresult rv;
489 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
491 NS_ASSERTION(rootObject.get() == aObject,
492 "bad call to WriteObject -- call WriteCompoundObject!");
493 PRBool check = CheckReferences(aObject);
494 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
495 return mBinaryStream->WriteObject(aObject, aIsStrongRef);
498 nsresult
499 StartupCacheDebugOutputStream::WriteSingleRefObject(nsISupports* aObject)
501 nsresult rv;
502 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
504 NS_ASSERTION(rootObject.get() == aObject,
505 "bad call to WriteSingleRefObject -- call WriteCompoundObject!");
506 PRBool check = CheckReferences(aObject);
507 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
508 return mBinaryStream->WriteSingleRefObject(aObject);
511 nsresult
512 StartupCacheDebugOutputStream::WriteCompoundObject(nsISupports* aObject,
513 const nsIID& aIID,
514 PRBool aIsStrongRef)
516 nsresult rv;
517 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
519 nsCOMPtr<nsISupports> roundtrip;
520 rootObject->QueryInterface(aIID, getter_AddRefs(roundtrip));
521 NS_ASSERTION(roundtrip.get() == aObject,
522 "bad aggregation or multiple inheritance detected by call to "
523 "WriteCompoundObject!");
525 PRBool check = CheckReferences(aObject);
526 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
527 return mBinaryStream->WriteCompoundObject(aObject, aIID, aIsStrongRef);
530 nsresult
531 StartupCacheDebugOutputStream::WriteID(nsID const& aID)
533 return mBinaryStream->WriteID(aID);
536 char*
537 StartupCacheDebugOutputStream::GetBuffer(PRUint32 aLength, PRUint32 aAlignMask)
539 return mBinaryStream->GetBuffer(aLength, aAlignMask);
542 void
543 StartupCacheDebugOutputStream::PutBuffer(char* aBuffer, PRUint32 aLength)
545 mBinaryStream->PutBuffer(aBuffer, aLength);
547 #endif //DEBUG
549 StartupCacheWrapper* StartupCacheWrapper::gStartupCacheWrapper = nsnull;
551 NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheWrapper, nsIStartupCache)
553 StartupCacheWrapper* StartupCacheWrapper::GetSingleton()
555 if (!gStartupCacheWrapper)
556 gStartupCacheWrapper = new StartupCacheWrapper();
558 NS_ADDREF(gStartupCacheWrapper);
559 return gStartupCacheWrapper;
562 nsresult
563 StartupCacheWrapper::GetBuffer(const char* id, char** outbuf, PRUint32* length)
565 StartupCache* sc = StartupCache::GetSingleton();
566 if (!sc) {
567 return NS_ERROR_NOT_INITIALIZED;
569 return sc->GetBuffer(id, outbuf, length);
572 nsresult
573 StartupCacheWrapper::PutBuffer(const char* id, char* inbuf, PRUint32 length)
575 StartupCache* sc = StartupCache::GetSingleton();
576 if (!sc) {
577 return NS_ERROR_NOT_INITIALIZED;
579 return sc->PutBuffer(id, inbuf, length);
582 nsresult
583 StartupCacheWrapper::InvalidateCache()
585 StartupCache* sc = StartupCache::GetSingleton();
586 if (!sc) {
587 return NS_ERROR_NOT_INITIALIZED;
589 sc->InvalidateCache();
590 return NS_OK;
593 nsresult
594 StartupCacheWrapper::GetDebugObjectOutputStream(nsIObjectOutputStream* stream,
595 nsIObjectOutputStream** outStream)
597 StartupCache* sc = StartupCache::GetSingleton();
598 if (!sc) {
599 return NS_ERROR_NOT_INITIALIZED;
601 return sc->GetDebugObjectOutputStream(stream, outStream);
604 nsresult
605 StartupCacheWrapper::StartupWriteComplete(PRBool *complete)
607 StartupCache* sc = StartupCache::GetSingleton();
608 if (!sc) {
609 return NS_ERROR_NOT_INITIALIZED;
611 *complete = sc->mStartupWriteInitiated && sc->mTable.Count() == 0;
612 return NS_OK;
615 nsresult
616 StartupCacheWrapper::ResetStartupWriteTimer()
618 StartupCache* sc = StartupCache::GetSingleton();
619 if (!sc) {
620 return NS_ERROR_NOT_INITIALIZED;
622 sc->mStartupWriteInitiated = PR_FALSE;
624 // Init with a shorter timer, for testing convenience.
625 sc->mTimer->Cancel();
626 sc->mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, sc, 10000,
627 nsITimer::TYPE_ONE_SHOT);
628 return NS_OK;
631 nsresult
632 StartupCacheWrapper::GetObserver(nsIObserver** obv) {
633 StartupCache* sc = StartupCache::GetSingleton();
634 if (!sc) {
635 return NS_ERROR_NOT_INITIALIZED;
637 NS_ADDREF(*obv = sc->mListener);
638 return NS_OK;
641 } // namespace scache
642 } // namespace mozilla