Bug 634366 - Remove broken CreateForNativePixmapSurface usage from CairoImageOGL...
[mozilla-central.git] / startupcache / StartupCache.cpp
blobdaca67c3dff0fabad3c175fb95f3759759c1cd9b
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"
65 #include "mozilla/Omnijar.h"
66 #include "prenv.h"
67 #include "mozilla/FunctionTimer.h"
69 #ifdef IS_BIG_ENDIAN
70 #define SC_ENDIAN "big"
71 #else
72 #define SC_ENDIAN "little"
73 #endif
75 #if PR_BYTES_PER_WORD == 4
76 #define SC_WORDSIZE "4"
77 #else
78 #define SC_WORDSIZE "8"
79 #endif
81 namespace mozilla {
82 namespace scache {
84 static const char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN;
85 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
87 StartupCache*
88 StartupCache::GetSingleton()
90 if (!gStartupCache)
91 StartupCache::InitSingleton();
93 return StartupCache::gStartupCache;
96 void
97 StartupCache::DeleteSingleton()
99 delete StartupCache::gStartupCache;
102 nsresult
103 StartupCache::InitSingleton()
105 nsresult rv;
106 StartupCache::gStartupCache = new StartupCache();
108 rv = StartupCache::gStartupCache->Init();
109 if (NS_FAILED(rv)) {
110 delete StartupCache::gStartupCache;
111 StartupCache::gStartupCache = nsnull;
113 return rv;
116 StartupCache* StartupCache::gStartupCache;
117 PRBool StartupCache::gShutdownInitiated;
119 StartupCache::StartupCache()
120 : mArchive(NULL), mStartupWriteInitiated(PR_FALSE), mWriteThread(NULL) {}
122 StartupCache::~StartupCache()
124 if (mTimer) {
125 mTimer->Cancel();
128 // Generally, the in-memory table should be empty here,
129 // but in special cases (like Talos Ts tests) we
130 // could shut down before we write.
131 // This mechanism will change when IO is moved off-thread
132 // (bug 586859) or when Talos first-run is changed to allow
133 // our timer to work (bug 591471).
134 WriteToDisk();
135 gStartupCache = nsnull;
138 nsresult
139 StartupCache::Init()
141 nsresult rv;
142 mTable.Init();
143 #ifdef DEBUG
144 mWriteObjectMap.Init();
145 #endif
147 // This allows to override the startup cache filename
148 // which is useful from xpcshell, when there is no ProfLDS directory to keep cache in.
149 char *env = PR_GetEnv("MOZ_STARTUP_CACHE");
150 if (env) {
151 rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), PR_FALSE, getter_AddRefs(mFile));
152 } else {
153 nsCOMPtr<nsIFile> file;
154 rv = NS_GetSpecialDirectory("ProfLDS",
155 getter_AddRefs(file));
156 if (NS_FAILED(rv)) {
157 // return silently, this will fail in mochitests's xpcshell process.
158 return rv;
161 rv = file->AppendNative(NS_LITERAL_CSTRING("startupCache"));
162 NS_ENSURE_SUCCESS(rv, rv);
164 // Try to create the directory if it's not there yet
165 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0777);
166 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
167 return rv;
169 rv = file->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName));
171 NS_ENSURE_SUCCESS(rv, rv);
173 mFile = do_QueryInterface(file);
176 NS_ENSURE_TRUE(mFile, NS_ERROR_UNEXPECTED);
178 mObserverService = do_GetService("@mozilla.org/observer-service;1");
180 if (!mObserverService) {
181 NS_WARNING("Could not get observerService.");
182 return NS_ERROR_UNEXPECTED;
185 mListener = new StartupCacheListener();
186 rv = mObserverService->AddObserver(mListener, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
187 PR_FALSE);
188 NS_ENSURE_SUCCESS(rv, rv);
189 rv = mObserverService->AddObserver(mListener, "startupcache-invalidate",
190 PR_FALSE);
191 NS_ENSURE_SUCCESS(rv, rv);
193 rv = LoadArchive();
195 // Sometimes we don't have a cache yet, that's ok.
196 // If it's corrupted, just remove it and start over.
197 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
198 NS_WARNING("Failed to load startupcache file correctly, removing!");
199 InvalidateCache();
201 return NS_OK;
204 nsresult
205 StartupCache::LoadArchive()
207 WaitOnWriteThread();
208 PRBool exists;
209 mArchive = NULL;
210 nsresult rv = mFile->Exists(&exists);
211 if (NS_FAILED(rv) || !exists)
212 return NS_ERROR_FILE_NOT_FOUND;
214 mArchive = new nsZipArchive();
215 return mArchive->OpenArchive(mFile);
218 // NOTE: this will not find a new entry until it has been written to disk!
219 // Consumer should take ownership of the resulting buffer.
220 nsresult
221 StartupCache::GetBuffer(const char* id, char** outbuf, PRUint32* length)
223 WaitOnWriteThread();
224 if (!mStartupWriteInitiated) {
225 CacheEntry* entry;
226 nsDependentCString idStr(id);
227 mTable.Get(idStr, &entry);
228 if (entry) {
229 *outbuf = new char[entry->size];
230 memcpy(*outbuf, entry->data, entry->size);
231 *length = entry->size;
232 return NS_OK;
236 if (mArchive) {
237 nsZipItemPtr<char> zipItem(mArchive, id, true);
238 if (zipItem) {
239 *outbuf = zipItem.Forget();
240 *length = zipItem.Length();
241 return NS_OK;
245 #ifdef MOZ_OMNIJAR
246 if (mozilla::OmnijarReader()) {
247 // no need to checksum omnijarred entries
248 nsZipItemPtr<char> zipItem(mozilla::OmnijarReader(), id);
249 if (zipItem) {
250 *outbuf = zipItem.Forget();
251 *length = zipItem.Length();
252 return NS_OK;
255 #endif
256 return NS_ERROR_NOT_AVAILABLE;
259 // Makes a copy of the buffer, client retains ownership of inbuf.
260 nsresult
261 StartupCache::PutBuffer(const char* id, const char* inbuf, PRUint32 len)
263 WaitOnWriteThread();
264 if (StartupCache::gShutdownInitiated) {
265 return NS_ERROR_NOT_AVAILABLE;
268 nsAutoArrayPtr<char> data(new char[len]);
269 memcpy(data, inbuf, len);
271 nsDependentCString idStr(id);
272 // Cache it for now, we'll write all together later.
273 CacheEntry* entry;
275 #ifdef DEBUG
276 mTable.Get(idStr, &entry);
277 NS_ASSERTION(entry == nsnull, "Existing entry in StartupCache.");
279 if (mArchive) {
280 nsZipItem* zipItem = mArchive->GetItem(id);
281 NS_ASSERTION(zipItem == nsnull, "Existing entry in disk StartupCache.");
283 #endif
285 entry = new CacheEntry(data.forget(), len);
286 mTable.Put(idStr, entry);
287 return ResetStartupWriteTimer();
290 struct CacheWriteHolder
292 nsCOMPtr<nsIZipWriter> writer;
293 nsCOMPtr<nsIStringInputStream> stream;
294 PRTime time;
297 PLDHashOperator
298 CacheCloseHelper(const nsACString& key, nsAutoPtr<CacheEntry>& data,
299 void* closure)
301 nsresult rv;
303 CacheWriteHolder* holder = (CacheWriteHolder*) closure;
304 nsIStringInputStream* stream = holder->stream;
305 nsIZipWriter* writer = holder->writer;
307 stream->ShareData(data->data, data->size);
309 #ifdef DEBUG
310 PRBool hasEntry;
311 rv = writer->HasEntry(key, &hasEntry);
312 NS_ASSERTION(NS_SUCCEEDED(rv) && hasEntry == PR_FALSE,
313 "Existing entry in disk StartupCache.");
314 #endif
315 rv = writer->AddEntryStream(key, holder->time, PR_TRUE, stream, PR_FALSE);
317 if (NS_FAILED(rv)) {
318 NS_WARNING("cache entry deleted but not written to disk.");
320 return PL_DHASH_REMOVE;
323 void
324 StartupCache::WriteToDisk()
326 WaitOnWriteThread();
327 nsresult rv;
328 mStartupWriteInitiated = PR_TRUE;
330 if (mTable.Count() == 0)
331 return;
333 nsCOMPtr<nsIZipWriter> zipW = do_CreateInstance("@mozilla.org/zipwriter;1");
334 if (!zipW)
335 return;
337 rv = zipW->Open(mFile, PR_RDWR | PR_CREATE_FILE);
338 if (NS_FAILED(rv)) {
339 NS_WARNING("could not open zipfile for write");
340 return;
343 nsCOMPtr<nsIStringInputStream> stream
344 = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
345 if (NS_FAILED(rv)) {
346 NS_WARNING("Couldn't create string input stream.");
347 return;
350 CacheWriteHolder holder;
351 holder.stream = stream;
352 holder.writer = zipW;
353 holder.time = PR_Now();
355 mTable.Enumerate(CacheCloseHelper, &holder);
357 // Close the archive so Windows doesn't choke.
358 mArchive = NULL;
359 zipW->Close();
361 // our reader's view of the archive is outdated now, reload it.
362 LoadArchive();
364 return;
367 void
368 StartupCache::InvalidateCache()
370 WaitOnWriteThread();
371 mTable.Clear();
372 mArchive = NULL;
373 mFile->Remove(false);
374 LoadArchive();
378 * WaitOnWriteThread() is called from a main thread to wait for the worker
379 * thread to finish. However since the same code is used in the worker thread and
380 * main thread, the worker thread can also call WaitOnWriteThread() which is a no-op.
382 void
383 StartupCache::WaitOnWriteThread()
385 PRThread* writeThread = mWriteThread;
386 if (!writeThread || writeThread == PR_GetCurrentThread())
387 return;
389 NS_TIME_FUNCTION_MIN(30);
390 //NS_WARNING("Waiting on startupcache write");
391 PR_JoinThread(writeThread);
392 mWriteThread = NULL;
395 void
396 StartupCache::ThreadedWrite(void *aClosure)
398 gStartupCache->WriteToDisk();
402 * The write-thread is spawned on a timeout(which is reset with every write). This
403 * can avoid a slow shutdown. After writing out the cache, the zipreader is
404 * reloaded on the worker thread.
406 void
407 StartupCache::WriteTimeout(nsITimer *aTimer, void *aClosure)
409 gStartupCache->mWriteThread = PR_CreateThread(PR_USER_THREAD,
410 StartupCache::ThreadedWrite,
411 NULL,
412 PR_PRIORITY_NORMAL,
413 PR_LOCAL_THREAD,
414 PR_JOINABLE_THREAD,
418 // We don't want to refcount StartupCache, so we'll just
419 // hold a ref to this and pass it to observerService instead.
420 NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheListener, nsIObserver)
422 nsresult
423 StartupCacheListener::Observe(nsISupports *subject, const char* topic, const PRUnichar* data)
425 StartupCache* sc = StartupCache::GetSingleton();
426 if (!sc)
427 return NS_OK;
429 if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
430 // Do not leave the thread running past xpcom shutdown
431 sc->WaitOnWriteThread();
432 StartupCache::gShutdownInitiated = PR_TRUE;
433 } else if (strcmp(topic, "startupcache-invalidate") == 0) {
434 sc->InvalidateCache();
436 return NS_OK;
439 nsresult
440 StartupCache::GetDebugObjectOutputStream(nsIObjectOutputStream* aStream,
441 nsIObjectOutputStream** aOutStream)
443 NS_ENSURE_ARG_POINTER(aStream);
444 #ifdef DEBUG
445 StartupCacheDebugOutputStream* stream
446 = new StartupCacheDebugOutputStream(aStream, &mWriteObjectMap);
447 NS_ADDREF(*aOutStream = stream);
448 #else
449 NS_ADDREF(*aOutStream = aStream);
450 #endif
452 return NS_OK;
455 nsresult
456 StartupCache::ResetStartupWriteTimer()
458 mStartupWriteInitiated = PR_FALSE;
459 nsresult rv;
460 if (!mTimer)
461 mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
462 else
463 rv = mTimer->Cancel();
464 NS_ENSURE_SUCCESS(rv, rv);
465 // Wait for 10 seconds, then write out the cache.
466 mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, this, 60000,
467 nsITimer::TYPE_ONE_SHOT);
468 return NS_OK;
471 // StartupCacheDebugOutputStream implementation
472 #ifdef DEBUG
473 NS_IMPL_ISUPPORTS3(StartupCacheDebugOutputStream, nsIObjectOutputStream,
474 nsIBinaryOutputStream, nsIOutputStream)
476 PRBool
477 StartupCacheDebugOutputStream::CheckReferences(nsISupports* aObject)
479 nsresult rv;
481 nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject);
482 if (!classInfo) {
483 NS_ERROR("aObject must implement nsIClassInfo");
484 return PR_FALSE;
487 PRUint32 flags;
488 rv = classInfo->GetFlags(&flags);
489 NS_ENSURE_SUCCESS(rv, rv);
490 if (flags & nsIClassInfo::SINGLETON)
491 return PR_TRUE;
493 nsISupportsHashKey* key = mObjectMap->GetEntry(aObject);
494 if (key) {
495 NS_ERROR("non-singleton aObject is referenced multiple times in this"
496 "serialization, we don't support that.");
497 return PR_FALSE;
500 mObjectMap->PutEntry(aObject);
501 return PR_TRUE;
504 // nsIObjectOutputStream implementation
505 nsresult
506 StartupCacheDebugOutputStream::WriteObject(nsISupports* aObject, PRBool aIsStrongRef)
508 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
510 NS_ASSERTION(rootObject.get() == aObject,
511 "bad call to WriteObject -- call WriteCompoundObject!");
512 PRBool check = CheckReferences(aObject);
513 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
514 return mBinaryStream->WriteObject(aObject, aIsStrongRef);
517 nsresult
518 StartupCacheDebugOutputStream::WriteSingleRefObject(nsISupports* aObject)
520 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
522 NS_ASSERTION(rootObject.get() == aObject,
523 "bad call to WriteSingleRefObject -- call WriteCompoundObject!");
524 PRBool check = CheckReferences(aObject);
525 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
526 return mBinaryStream->WriteSingleRefObject(aObject);
529 nsresult
530 StartupCacheDebugOutputStream::WriteCompoundObject(nsISupports* aObject,
531 const nsIID& aIID,
532 PRBool aIsStrongRef)
534 nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
536 nsCOMPtr<nsISupports> roundtrip;
537 rootObject->QueryInterface(aIID, getter_AddRefs(roundtrip));
538 NS_ASSERTION(roundtrip.get() == aObject,
539 "bad aggregation or multiple inheritance detected by call to "
540 "WriteCompoundObject!");
542 PRBool check = CheckReferences(aObject);
543 NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
544 return mBinaryStream->WriteCompoundObject(aObject, aIID, aIsStrongRef);
547 nsresult
548 StartupCacheDebugOutputStream::WriteID(nsID const& aID)
550 return mBinaryStream->WriteID(aID);
553 char*
554 StartupCacheDebugOutputStream::GetBuffer(PRUint32 aLength, PRUint32 aAlignMask)
556 return mBinaryStream->GetBuffer(aLength, aAlignMask);
559 void
560 StartupCacheDebugOutputStream::PutBuffer(char* aBuffer, PRUint32 aLength)
562 mBinaryStream->PutBuffer(aBuffer, aLength);
564 #endif //DEBUG
566 StartupCacheWrapper* StartupCacheWrapper::gStartupCacheWrapper = nsnull;
568 NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheWrapper, nsIStartupCache)
570 StartupCacheWrapper* StartupCacheWrapper::GetSingleton()
572 if (!gStartupCacheWrapper)
573 gStartupCacheWrapper = new StartupCacheWrapper();
575 NS_ADDREF(gStartupCacheWrapper);
576 return gStartupCacheWrapper;
579 nsresult
580 StartupCacheWrapper::GetBuffer(const char* id, char** outbuf, PRUint32* length)
582 StartupCache* sc = StartupCache::GetSingleton();
583 if (!sc) {
584 return NS_ERROR_NOT_INITIALIZED;
586 return sc->GetBuffer(id, outbuf, length);
589 nsresult
590 StartupCacheWrapper::PutBuffer(const char* id, char* inbuf, PRUint32 length)
592 StartupCache* sc = StartupCache::GetSingleton();
593 if (!sc) {
594 return NS_ERROR_NOT_INITIALIZED;
596 return sc->PutBuffer(id, inbuf, length);
599 nsresult
600 StartupCacheWrapper::InvalidateCache()
602 StartupCache* sc = StartupCache::GetSingleton();
603 if (!sc) {
604 return NS_ERROR_NOT_INITIALIZED;
606 sc->InvalidateCache();
607 return NS_OK;
610 nsresult
611 StartupCacheWrapper::GetDebugObjectOutputStream(nsIObjectOutputStream* stream,
612 nsIObjectOutputStream** outStream)
614 StartupCache* sc = StartupCache::GetSingleton();
615 if (!sc) {
616 return NS_ERROR_NOT_INITIALIZED;
618 return sc->GetDebugObjectOutputStream(stream, outStream);
621 nsresult
622 StartupCacheWrapper::StartupWriteComplete(PRBool *complete)
624 StartupCache* sc = StartupCache::GetSingleton();
625 if (!sc) {
626 return NS_ERROR_NOT_INITIALIZED;
628 sc->WaitOnWriteThread();
629 *complete = sc->mStartupWriteInitiated && sc->mTable.Count() == 0;
630 return NS_OK;
633 nsresult
634 StartupCacheWrapper::ResetStartupWriteTimer()
636 StartupCache* sc = StartupCache::GetSingleton();
637 return sc ? sc->ResetStartupWriteTimer() : NS_ERROR_NOT_INITIALIZED;
640 nsresult
641 StartupCacheWrapper::GetObserver(nsIObserver** obv) {
642 StartupCache* sc = StartupCache::GetSingleton();
643 if (!sc) {
644 return NS_ERROR_NOT_INITIALIZED;
646 NS_ADDREF(*obv = sc->mListener);
647 return NS_OK;
650 } // namespace scache
651 } // namespace mozilla