Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "CacheIndex.h"
7 #include "CacheLog.h"
8 #include "CacheFileIOManager.h"
9 #include "CacheFileMetadata.h"
10 #include "CacheIndexIterator.h"
11 #include "CacheIndexContextIterator.h"
12 #include "nsThreadUtils.h"
13 #include "nsISimpleEnumerator.h"
14 #include "nsIDirectoryEnumerator.h"
15 #include "nsISizeOf.h"
16 #include "nsPrintfCString.h"
17 #include "mozilla/DebugOnly.h"
18 #include "prinrval.h"
19 #include "nsIFile.h"
20 #include "nsITimer.h"
21 #include "mozilla/AutoRestore.h"
22 #include <algorithm>
25 #define kMinUnwrittenChanges 300
26 #define kMinDumpInterval 20000 // in milliseconds
27 #define kMaxBufSize 16384
28 #define kIndexVersion 0x00000001
29 #define kUpdateIndexStartDelay 50000 // in milliseconds
31 const char kIndexName[] = "index";
32 const char kTempIndexName[] = "index.tmp";
33 const char kJournalName[] = "index.log";
35 namespace mozilla {
36 namespace net {
38 /**
39 * This helper class is responsible for keeping CacheIndex::mIndexStats,
40 * CacheIndex::mFrecencyArray and CacheIndex::mExpirationArray up to date.
41 */
42 class CacheIndexEntryAutoManage
43 {
44 public:
45 CacheIndexEntryAutoManage(const SHA1Sum::Hash *aHash, CacheIndex *aIndex)
46 : mIndex(aIndex)
47 , mOldRecord(nullptr)
48 , mOldFrecency(0)
49 , mOldExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME)
50 , mDoNotSearchInIndex(false)
51 , mDoNotSearchInUpdates(false)
52 {
53 mIndex->AssertOwnsLock();
55 mHash = aHash;
56 CacheIndexEntry *entry = FindEntry();
57 mIndex->mIndexStats.BeforeChange(entry);
58 if (entry && entry->IsInitialized() && !entry->IsRemoved()) {
59 mOldRecord = entry->mRec;
60 mOldFrecency = entry->mRec->mFrecency;
61 mOldExpirationTime = entry->mRec->mExpirationTime;
62 }
63 }
65 ~CacheIndexEntryAutoManage()
66 {
67 mIndex->AssertOwnsLock();
69 CacheIndexEntry *entry = FindEntry();
70 mIndex->mIndexStats.AfterChange(entry);
71 if (!entry || !entry->IsInitialized() || entry->IsRemoved()) {
72 entry = nullptr;
73 }
75 if (entry && !mOldRecord) {
76 mIndex->InsertRecordToFrecencyArray(entry->mRec);
77 mIndex->InsertRecordToExpirationArray(entry->mRec);
78 mIndex->AddRecordToIterators(entry->mRec);
79 } else if (!entry && mOldRecord) {
80 mIndex->RemoveRecordFromFrecencyArray(mOldRecord);
81 mIndex->RemoveRecordFromExpirationArray(mOldRecord);
82 mIndex->RemoveRecordFromIterators(mOldRecord);
83 } else if (entry && mOldRecord) {
84 bool replaceFrecency = false;
85 bool replaceExpiration = false;
87 if (entry->mRec != mOldRecord) {
88 // record has a different address, we have to replace it
89 replaceFrecency = replaceExpiration = true;
90 mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec);
91 } else {
92 if (entry->mRec->mFrecency == 0 &&
93 entry->mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME) {
94 // This is a special case when we want to make sure that the entry is
95 // placed at the end of the lists even when the values didn't change.
96 replaceFrecency = replaceExpiration = true;
97 } else {
98 if (entry->mRec->mFrecency != mOldFrecency) {
99 replaceFrecency = true;
100 }
101 if (entry->mRec->mExpirationTime != mOldExpirationTime) {
102 replaceExpiration = true;
103 }
104 }
105 }
107 if (replaceFrecency) {
108 mIndex->RemoveRecordFromFrecencyArray(mOldRecord);
109 mIndex->InsertRecordToFrecencyArray(entry->mRec);
110 }
111 if (replaceExpiration) {
112 mIndex->RemoveRecordFromExpirationArray(mOldRecord);
113 mIndex->InsertRecordToExpirationArray(entry->mRec);
114 }
115 } else {
116 // both entries were removed or not initialized, do nothing
117 }
118 }
120 // We cannot rely on nsTHashtable::GetEntry() in case we are enumerating the
121 // entries and returning PL_DHASH_REMOVE. Destructor is called before the
122 // entry is removed. Caller must call one of following methods to skip
123 // lookup in the hashtable.
124 void DoNotSearchInIndex() { mDoNotSearchInIndex = true; }
125 void DoNotSearchInUpdates() { mDoNotSearchInUpdates = true; }
127 private:
128 CacheIndexEntry * FindEntry()
129 {
130 CacheIndexEntry *entry = nullptr;
132 switch (mIndex->mState) {
133 case CacheIndex::READING:
134 case CacheIndex::WRITING:
135 if (!mDoNotSearchInUpdates) {
136 entry = mIndex->mPendingUpdates.GetEntry(*mHash);
137 }
138 // no break
139 case CacheIndex::BUILDING:
140 case CacheIndex::UPDATING:
141 case CacheIndex::READY:
142 if (!entry && !mDoNotSearchInIndex) {
143 entry = mIndex->mIndex.GetEntry(*mHash);
144 }
145 break;
146 case CacheIndex::INITIAL:
147 case CacheIndex::SHUTDOWN:
148 default:
149 MOZ_ASSERT(false, "Unexpected state!");
150 }
152 return entry;
153 }
155 const SHA1Sum::Hash *mHash;
156 nsRefPtr<CacheIndex> mIndex;
157 CacheIndexRecord *mOldRecord;
158 uint32_t mOldFrecency;
159 uint32_t mOldExpirationTime;
160 bool mDoNotSearchInIndex;
161 bool mDoNotSearchInUpdates;
162 };
164 class FileOpenHelper : public CacheFileIOListener
165 {
166 public:
167 NS_DECL_THREADSAFE_ISUPPORTS
169 FileOpenHelper(CacheIndex* aIndex)
170 : mIndex(aIndex)
171 , mCanceled(false)
172 {}
174 virtual ~FileOpenHelper() {}
176 void Cancel() {
177 mIndex->AssertOwnsLock();
178 mCanceled = true;
179 }
181 private:
182 NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult);
183 NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
184 nsresult aResult) {
185 MOZ_CRASH("FileOpenHelper::OnDataWritten should not be called!");
186 return NS_ERROR_UNEXPECTED;
187 }
188 NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf,
189 nsresult aResult) {
190 MOZ_CRASH("FileOpenHelper::OnDataRead should not be called!");
191 return NS_ERROR_UNEXPECTED;
192 }
193 NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) {
194 MOZ_CRASH("FileOpenHelper::OnFileDoomed should not be called!");
195 return NS_ERROR_UNEXPECTED;
196 }
197 NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) {
198 MOZ_CRASH("FileOpenHelper::OnEOFSet should not be called!");
199 return NS_ERROR_UNEXPECTED;
200 }
201 NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) {
202 MOZ_CRASH("FileOpenHelper::OnFileRenamed should not be called!");
203 return NS_ERROR_UNEXPECTED;
204 }
206 nsRefPtr<CacheIndex> mIndex;
207 bool mCanceled;
208 };
210 NS_IMETHODIMP FileOpenHelper::OnFileOpened(CacheFileHandle *aHandle,
211 nsresult aResult)
212 {
213 CacheIndexAutoLock lock(mIndex);
215 if (mCanceled) {
216 if (aHandle) {
217 CacheFileIOManager::DoomFile(aHandle, nullptr);
218 }
220 return NS_OK;
221 }
223 mIndex->OnFileOpenedInternal(this, aHandle, aResult);
225 return NS_OK;
226 }
228 NS_IMPL_ISUPPORTS(FileOpenHelper, CacheFileIOListener);
231 CacheIndex * CacheIndex::gInstance = nullptr;
234 NS_IMPL_ADDREF(CacheIndex)
235 NS_IMPL_RELEASE(CacheIndex)
237 NS_INTERFACE_MAP_BEGIN(CacheIndex)
238 NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
239 NS_INTERFACE_MAP_ENTRY(nsIRunnable)
240 NS_INTERFACE_MAP_END_THREADSAFE
243 CacheIndex::CacheIndex()
244 : mLock("CacheFile.mLock")
245 , mState(INITIAL)
246 , mShuttingDown(false)
247 , mIndexNeedsUpdate(false)
248 , mRemovingAll(false)
249 , mIndexOnDiskIsValid(false)
250 , mDontMarkIndexClean(false)
251 , mIndexTimeStamp(0)
252 , mUpdateEventPending(false)
253 , mSkipEntries(0)
254 , mProcessEntries(0)
255 , mRWBuf(nullptr)
256 , mRWBufSize(0)
257 , mRWBufPos(0)
258 , mJournalReadSuccessfully(false)
259 {
260 LOG(("CacheIndex::CacheIndex [this=%p]", this));
261 MOZ_COUNT_CTOR(CacheIndex);
262 MOZ_ASSERT(!gInstance, "multiple CacheIndex instances!");
263 }
265 CacheIndex::~CacheIndex()
266 {
267 LOG(("CacheIndex::~CacheIndex [this=%p]", this));
268 MOZ_COUNT_DTOR(CacheIndex);
270 ReleaseBuffer();
271 }
273 void
274 CacheIndex::Lock()
275 {
276 mLock.Lock();
278 MOZ_ASSERT(!mIndexStats.StateLogged());
279 }
281 void
282 CacheIndex::Unlock()
283 {
284 MOZ_ASSERT(!mIndexStats.StateLogged());
286 mLock.Unlock();
287 }
289 inline void
290 CacheIndex::AssertOwnsLock()
291 {
292 mLock.AssertCurrentThreadOwns();
293 }
295 // static
296 nsresult
297 CacheIndex::Init(nsIFile *aCacheDirectory)
298 {
299 LOG(("CacheIndex::Init()"));
301 MOZ_ASSERT(NS_IsMainThread());
303 if (gInstance) {
304 return NS_ERROR_ALREADY_INITIALIZED;
305 }
307 nsRefPtr<CacheIndex> idx = new CacheIndex();
309 CacheIndexAutoLock lock(idx);
311 nsresult rv = idx->InitInternal(aCacheDirectory);
312 NS_ENSURE_SUCCESS(rv, rv);
314 idx.swap(gInstance);
315 return NS_OK;
316 }
318 nsresult
319 CacheIndex::InitInternal(nsIFile *aCacheDirectory)
320 {
321 nsresult rv;
323 rv = aCacheDirectory->Clone(getter_AddRefs(mCacheDirectory));
324 NS_ENSURE_SUCCESS(rv, rv);
326 mStartTime = TimeStamp::NowLoRes();
328 ReadIndexFromDisk();
330 return NS_OK;
331 }
333 // static
334 nsresult
335 CacheIndex::PreShutdown()
336 {
337 LOG(("CacheIndex::PreShutdown() [gInstance=%p]", gInstance));
339 MOZ_ASSERT(NS_IsMainThread());
341 nsresult rv;
342 nsRefPtr<CacheIndex> index = gInstance;
344 if (!index) {
345 return NS_ERROR_NOT_INITIALIZED;
346 }
348 CacheIndexAutoLock lock(index);
350 LOG(("CacheIndex::PreShutdown() - [state=%d, indexOnDiskIsValid=%d, "
351 "dontMarkIndexClean=%d]", index->mState, index->mIndexOnDiskIsValid,
352 index->mDontMarkIndexClean));
354 LOG(("CacheIndex::PreShutdown() - Closing iterators."));
355 for (uint32_t i = 0; i < index->mIterators.Length(); ) {
356 rv = index->mIterators[i]->CloseInternal(NS_ERROR_FAILURE);
357 if (NS_FAILED(rv)) {
358 // CacheIndexIterator::CloseInternal() removes itself from mIteratos iff
359 // it returns success.
360 LOG(("CacheIndex::PreShutdown() - Failed to remove iterator %p. "
361 "[rv=0x%08x]", rv));
362 i++;
363 }
364 }
366 index->mShuttingDown = true;
368 if (index->mState == READY) {
369 return NS_OK; // nothing to do
370 }
372 nsCOMPtr<nsIRunnable> event;
373 event = NS_NewRunnableMethod(index, &CacheIndex::PreShutdownInternal);
375 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
376 MOZ_ASSERT(ioTarget);
378 // PreShutdownInternal() will be executed before any queued event on INDEX
379 // level. That's OK since we don't want to wait for any operation in progess.
380 // We need to interrupt it and save journal as quickly as possible.
381 rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
382 if (NS_FAILED(rv)) {
383 NS_WARNING("CacheIndex::PreShutdown() - Can't dispatch event");
384 LOG(("CacheIndex::PreShutdown() - Can't dispatch event" ));
385 return rv;
386 }
388 return NS_OK;
389 }
391 void
392 CacheIndex::PreShutdownInternal()
393 {
394 CacheIndexAutoLock lock(this);
396 LOG(("CacheIndex::PreShutdownInternal() - [state=%d, indexOnDiskIsValid=%d, "
397 "dontMarkIndexClean=%d]", mState, mIndexOnDiskIsValid,
398 mDontMarkIndexClean));
400 MOZ_ASSERT(mShuttingDown);
402 if (mUpdateTimer) {
403 mUpdateTimer = nullptr;
404 }
406 switch (mState) {
407 case WRITING:
408 FinishWrite(false);
409 break;
410 case READY:
411 // nothing to do, write the journal in Shutdown()
412 break;
413 case READING:
414 FinishRead(false);
415 break;
416 case BUILDING:
417 case UPDATING:
418 FinishUpdate(false);
419 break;
420 default:
421 MOZ_ASSERT(false, "Implement me!");
422 }
424 // We should end up in READY state
425 MOZ_ASSERT(mState == READY);
426 }
428 // static
429 nsresult
430 CacheIndex::Shutdown()
431 {
432 LOG(("CacheIndex::Shutdown() [gInstance=%p]", gInstance));
434 MOZ_ASSERT(NS_IsMainThread());
436 nsRefPtr<CacheIndex> index;
437 index.swap(gInstance);
439 if (!index) {
440 return NS_ERROR_NOT_INITIALIZED;
441 }
443 CacheIndexAutoLock lock(index);
445 bool sanitize = CacheObserver::ClearCacheOnShutdown();
447 LOG(("CacheIndex::Shutdown() - [state=%d, indexOnDiskIsValid=%d, "
448 "dontMarkIndexClean=%d, sanitize=%d]", index->mState,
449 index->mIndexOnDiskIsValid, index->mDontMarkIndexClean, sanitize));
451 MOZ_ASSERT(index->mShuttingDown);
453 EState oldState = index->mState;
454 index->ChangeState(SHUTDOWN);
456 if (oldState != READY) {
457 LOG(("CacheIndex::Shutdown() - Unexpected state. Did posting of "
458 "PreShutdownInternal() fail?"));
459 }
461 switch (oldState) {
462 case WRITING:
463 index->FinishWrite(false);
464 // no break
465 case READY:
466 if (index->mIndexOnDiskIsValid && !index->mDontMarkIndexClean) {
467 if (!sanitize && NS_FAILED(index->WriteLogToDisk())) {
468 index->RemoveIndexFromDisk();
469 }
470 } else {
471 index->RemoveIndexFromDisk();
472 }
473 break;
474 case READING:
475 index->FinishRead(false);
476 break;
477 case BUILDING:
478 case UPDATING:
479 index->FinishUpdate(false);
480 break;
481 default:
482 MOZ_ASSERT(false, "Unexpected state!");
483 }
485 if (sanitize) {
486 index->RemoveIndexFromDisk();
487 }
489 return NS_OK;
490 }
492 // static
493 nsresult
494 CacheIndex::AddEntry(const SHA1Sum::Hash *aHash)
495 {
496 LOG(("CacheIndex::AddEntry() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash)));
498 nsRefPtr<CacheIndex> index = gInstance;
500 if (!index) {
501 return NS_ERROR_NOT_INITIALIZED;
502 }
504 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
506 CacheIndexAutoLock lock(index);
508 if (!index->IsIndexUsable()) {
509 return NS_ERROR_NOT_AVAILABLE;
510 }
512 // Getters in CacheIndexStats assert when mStateLogged is true since the
513 // information is incomplete between calls to BeforeChange() and AfterChange()
514 // (i.e. while CacheIndexEntryAutoManage exists). We need to check whether
515 // non-fresh entries exists outside the scope of CacheIndexEntryAutoManage.
516 bool updateIfNonFreshEntriesExist = false;
518 {
519 CacheIndexEntryAutoManage entryMng(aHash, index);
521 CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
522 bool entryRemoved = entry && entry->IsRemoved();
524 if (index->mState == READY || index->mState == UPDATING ||
525 index->mState == BUILDING) {
526 MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
528 if (entry && !entryRemoved) {
529 // Found entry in index that shouldn't exist.
531 if (entry->IsFresh()) {
532 // Someone removed the file on disk while FF is running. Update
533 // process can fix only non-fresh entries (i.e. entries that were not
534 // added within this session). Start update only if we have such
535 // entries.
536 //
537 // TODO: This should be very rare problem. If it turns out not to be
538 // true, change the update process so that it also iterates all
539 // initialized non-empty entries and checks whether the file exists.
541 LOG(("CacheIndex::AddEntry() - Cache file was removed outside FF "
542 "process!"));
544 updateIfNonFreshEntriesExist = true;
545 } else if (index->mState == READY) {
546 // Index is outdated, update it.
547 LOG(("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
548 "update is needed"));
549 index->mIndexNeedsUpdate = true;
550 } else {
551 // We cannot be here when building index since all entries are fresh
552 // during building.
553 MOZ_ASSERT(index->mState == UPDATING);
554 }
555 }
557 if (!entry) {
558 entry = index->mIndex.PutEntry(*aHash);
559 }
560 } else { // WRITING, READING
561 CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
562 bool updatedRemoved = updated && updated->IsRemoved();
564 if ((updated && !updatedRemoved) ||
565 (!updated && entry && !entryRemoved && entry->IsFresh())) {
566 // Fresh entry found, so the file was removed outside FF
567 LOG(("CacheIndex::AddEntry() - Cache file was removed outside FF "
568 "process!"));
570 updateIfNonFreshEntriesExist = true;
571 } else if (!updated && entry && !entryRemoved) {
572 if (index->mState == WRITING) {
573 LOG(("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
574 "update is needed"));
575 index->mIndexNeedsUpdate = true;
576 }
577 // Ignore if state is READING since the index information is partial
578 }
580 updated = index->mPendingUpdates.PutEntry(*aHash);
581 entry = updated;
582 }
584 entry->InitNew();
585 entry->MarkDirty();
586 entry->MarkFresh();
587 }
589 if (updateIfNonFreshEntriesExist &&
590 index->mIndexStats.Count() != index->mIndexStats.Fresh()) {
591 index->mIndexNeedsUpdate = true;
592 }
594 index->StartUpdatingIndexIfNeeded();
595 index->WriteIndexToDiskIfNeeded();
597 return NS_OK;
598 }
600 // static
601 nsresult
602 CacheIndex::EnsureEntryExists(const SHA1Sum::Hash *aHash)
603 {
604 LOG(("CacheIndex::EnsureEntryExists() [hash=%08x%08x%08x%08x%08x]",
605 LOGSHA1(aHash)));
607 nsRefPtr<CacheIndex> index = gInstance;
609 if (!index) {
610 return NS_ERROR_NOT_INITIALIZED;
611 }
613 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
615 CacheIndexAutoLock lock(index);
617 if (!index->IsIndexUsable()) {
618 return NS_ERROR_NOT_AVAILABLE;
619 }
621 {
622 CacheIndexEntryAutoManage entryMng(aHash, index);
624 CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
625 bool entryRemoved = entry && entry->IsRemoved();
627 if (index->mState == READY || index->mState == UPDATING ||
628 index->mState == BUILDING) {
629 MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
631 if (!entry || entryRemoved) {
632 if (entryRemoved && entry->IsFresh()) {
633 // This could happen only if somebody copies files to the entries
634 // directory while FF is running.
635 LOG(("CacheIndex::EnsureEntryExists() - Cache file was added outside "
636 "FF process! Update is needed."));
637 index->mIndexNeedsUpdate = true;
638 } else if (index->mState == READY ||
639 (entryRemoved && !entry->IsFresh())) {
640 // Removed non-fresh entries can be present as a result of
641 // ProcessJournalEntry()
642 LOG(("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
643 " exist, update is needed"));
644 index->mIndexNeedsUpdate = true;
645 }
647 if (!entry) {
648 entry = index->mIndex.PutEntry(*aHash);
649 }
650 entry->InitNew();
651 entry->MarkDirty();
652 }
653 entry->MarkFresh();
654 } else { // WRITING, READING
655 CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
656 bool updatedRemoved = updated && updated->IsRemoved();
658 if (updatedRemoved ||
659 (!updated && entryRemoved && entry->IsFresh())) {
660 // Fresh information about missing entry found. This could happen only
661 // if somebody copies files to the entries directory while FF is running.
662 LOG(("CacheIndex::EnsureEntryExists() - Cache file was added outside "
663 "FF process! Update is needed."));
664 index->mIndexNeedsUpdate = true;
665 } else if (!updated && (!entry || entryRemoved)) {
666 if (index->mState == WRITING) {
667 LOG(("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
668 " exist, update is needed"));
669 index->mIndexNeedsUpdate = true;
670 }
671 // Ignore if state is READING since the index information is partial
672 }
674 // We don't need entryRemoved and updatedRemoved info anymore
675 if (entryRemoved) entry = nullptr;
676 if (updatedRemoved) updated = nullptr;
678 if (updated) {
679 updated->MarkFresh();
680 } else {
681 if (!entry) {
682 // Create a new entry
683 updated = index->mPendingUpdates.PutEntry(*aHash);
684 updated->InitNew();
685 updated->MarkFresh();
686 updated->MarkDirty();
687 } else {
688 if (!entry->IsFresh()) {
689 // To mark the entry fresh we must make a copy of index entry
690 // since the index is read-only.
691 updated = index->mPendingUpdates.PutEntry(*aHash);
692 *updated = *entry;
693 updated->MarkFresh();
694 }
695 }
696 }
697 }
698 }
700 index->StartUpdatingIndexIfNeeded();
701 index->WriteIndexToDiskIfNeeded();
703 return NS_OK;
704 }
706 // static
707 nsresult
708 CacheIndex::InitEntry(const SHA1Sum::Hash *aHash,
709 uint32_t aAppId,
710 bool aAnonymous,
711 bool aInBrowser)
712 {
713 LOG(("CacheIndex::InitEntry() [hash=%08x%08x%08x%08x%08x, appId=%u, "
714 "anonymous=%d, inBrowser=%d]", LOGSHA1(aHash), aAppId, aAnonymous,
715 aInBrowser));
717 nsRefPtr<CacheIndex> index = gInstance;
719 if (!index) {
720 return NS_ERROR_NOT_INITIALIZED;
721 }
723 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
725 CacheIndexAutoLock lock(index);
727 if (!index->IsIndexUsable()) {
728 return NS_ERROR_NOT_AVAILABLE;
729 }
731 {
732 CacheIndexEntryAutoManage entryMng(aHash, index);
734 CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
735 bool reinitEntry = false;
737 if (entry && entry->IsRemoved()) {
738 entry = nullptr;
739 }
741 if (index->mState == READY || index->mState == UPDATING ||
742 index->mState == BUILDING) {
743 MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
744 MOZ_ASSERT(entry);
745 MOZ_ASSERT(entry->IsFresh());
747 if (IsCollision(entry, aAppId, aAnonymous, aInBrowser)) {
748 index->mIndexNeedsUpdate = true; // TODO Does this really help in case of collision?
749 reinitEntry = true;
750 } else {
751 if (entry->IsInitialized()) {
752 return NS_OK;
753 }
754 }
755 } else {
756 CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
757 DebugOnly<bool> removed = updated && updated->IsRemoved();
759 MOZ_ASSERT(updated || !removed);
760 MOZ_ASSERT(updated || entry);
762 if (updated) {
763 MOZ_ASSERT(updated->IsFresh());
765 if (IsCollision(updated, aAppId, aAnonymous, aInBrowser)) {
766 index->mIndexNeedsUpdate = true;
767 reinitEntry = true;
768 } else {
769 if (updated->IsInitialized()) {
770 return NS_OK;
771 }
772 }
773 entry = updated;
774 } else {
775 MOZ_ASSERT(entry->IsFresh());
777 if (IsCollision(entry, aAppId, aAnonymous, aInBrowser)) {
778 index->mIndexNeedsUpdate = true;
779 reinitEntry = true;
780 } else {
781 if (entry->IsInitialized()) {
782 return NS_OK;
783 }
784 }
786 // make a copy of a read-only entry
787 updated = index->mPendingUpdates.PutEntry(*aHash);
788 *updated = *entry;
789 entry = updated;
790 }
791 }
793 if (reinitEntry) {
794 // There is a collision and we are going to rewrite this entry. Initialize
795 // it as a new entry.
796 entry->InitNew();
797 entry->MarkFresh();
798 }
799 entry->Init(aAppId, aAnonymous, aInBrowser);
800 entry->MarkDirty();
801 }
803 index->StartUpdatingIndexIfNeeded();
804 index->WriteIndexToDiskIfNeeded();
806 return NS_OK;
807 }
809 // static
810 nsresult
811 CacheIndex::RemoveEntry(const SHA1Sum::Hash *aHash)
812 {
813 LOG(("CacheIndex::RemoveEntry() [hash=%08x%08x%08x%08x%08x]",
814 LOGSHA1(aHash)));
816 nsRefPtr<CacheIndex> index = gInstance;
818 if (!index) {
819 return NS_ERROR_NOT_INITIALIZED;
820 }
822 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
824 CacheIndexAutoLock lock(index);
826 if (!index->IsIndexUsable()) {
827 return NS_ERROR_NOT_AVAILABLE;
828 }
830 {
831 CacheIndexEntryAutoManage entryMng(aHash, index);
833 CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
834 bool entryRemoved = entry && entry->IsRemoved();
836 if (index->mState == READY || index->mState == UPDATING ||
837 index->mState == BUILDING) {
838 MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
840 if (!entry || entryRemoved) {
841 if (entryRemoved && entry->IsFresh()) {
842 // This could happen only if somebody copies files to the entries
843 // directory while FF is running.
844 LOG(("CacheIndex::RemoveEntry() - Cache file was added outside FF "
845 "process! Update is needed."));
846 index->mIndexNeedsUpdate = true;
847 } else if (index->mState == READY ||
848 (entryRemoved && !entry->IsFresh())) {
849 // Removed non-fresh entries can be present as a result of
850 // ProcessJournalEntry()
851 LOG(("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
852 ", update is needed"));
853 index->mIndexNeedsUpdate = true;
854 }
855 } else {
856 if (entry) {
857 if (!entry->IsDirty() && entry->IsFileEmpty()) {
858 index->mIndex.RemoveEntry(*aHash);
859 entry = nullptr;
860 } else {
861 entry->MarkRemoved();
862 entry->MarkDirty();
863 entry->MarkFresh();
864 }
865 }
866 }
867 } else { // WRITING, READING
868 CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
869 bool updatedRemoved = updated && updated->IsRemoved();
871 if (updatedRemoved ||
872 (!updated && entryRemoved && entry->IsFresh())) {
873 // Fresh information about missing entry found. This could happen only
874 // if somebody copies files to the entries directory while FF is running.
875 LOG(("CacheIndex::RemoveEntry() - Cache file was added outside FF "
876 "process! Update is needed."));
877 index->mIndexNeedsUpdate = true;
878 } else if (!updated && (!entry || entryRemoved)) {
879 if (index->mState == WRITING) {
880 LOG(("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
881 ", update is needed"));
882 index->mIndexNeedsUpdate = true;
883 }
884 // Ignore if state is READING since the index information is partial
885 }
887 if (!updated) {
888 updated = index->mPendingUpdates.PutEntry(*aHash);
889 updated->InitNew();
890 }
892 updated->MarkRemoved();
893 updated->MarkDirty();
894 updated->MarkFresh();
895 }
896 }
898 index->StartUpdatingIndexIfNeeded();
899 index->WriteIndexToDiskIfNeeded();
901 return NS_OK;
902 }
904 // static
905 nsresult
906 CacheIndex::UpdateEntry(const SHA1Sum::Hash *aHash,
907 const uint32_t *aFrecency,
908 const uint32_t *aExpirationTime,
909 const uint32_t *aSize)
910 {
911 LOG(("CacheIndex::UpdateEntry() [hash=%08x%08x%08x%08x%08x, "
912 "frecency=%s, expirationTime=%s, size=%s]", LOGSHA1(aHash),
913 aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
914 aExpirationTime ? nsPrintfCString("%u", *aExpirationTime).get() : "",
915 aSize ? nsPrintfCString("%u", *aSize).get() : ""));
917 nsRefPtr<CacheIndex> index = gInstance;
919 if (!index) {
920 return NS_ERROR_NOT_INITIALIZED;
921 }
923 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
925 CacheIndexAutoLock lock(index);
927 if (!index->IsIndexUsable()) {
928 return NS_ERROR_NOT_AVAILABLE;
929 }
931 {
932 CacheIndexEntryAutoManage entryMng(aHash, index);
934 CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
936 if (entry && entry->IsRemoved()) {
937 entry = nullptr;
938 }
940 if (index->mState == READY || index->mState == UPDATING ||
941 index->mState == BUILDING) {
942 MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
943 MOZ_ASSERT(entry);
945 if (!HasEntryChanged(entry, aFrecency, aExpirationTime, aSize)) {
946 return NS_OK;
947 }
948 } else {
949 CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
950 DebugOnly<bool> removed = updated && updated->IsRemoved();
952 MOZ_ASSERT(updated || !removed);
953 MOZ_ASSERT(updated || entry);
955 if (!updated) {
956 if (entry &&
957 HasEntryChanged(entry, aFrecency, aExpirationTime, aSize)) {
958 // make a copy of a read-only entry
959 updated = index->mPendingUpdates.PutEntry(*aHash);
960 *updated = *entry;
961 entry = updated;
962 } else {
963 return NS_ERROR_NOT_AVAILABLE;
964 }
965 } else {
966 entry = updated;
967 }
968 }
970 MOZ_ASSERT(entry->IsFresh());
971 MOZ_ASSERT(entry->IsInitialized());
972 entry->MarkDirty();
974 if (aFrecency) {
975 entry->SetFrecency(*aFrecency);
976 }
978 if (aExpirationTime) {
979 entry->SetExpirationTime(*aExpirationTime);
980 }
982 if (aSize) {
983 entry->SetFileSize(*aSize);
984 }
985 }
987 index->WriteIndexToDiskIfNeeded();
989 return NS_OK;
990 }
992 // static
993 nsresult
994 CacheIndex::RemoveAll()
995 {
996 LOG(("CacheIndex::RemoveAll()"));
998 nsRefPtr<CacheIndex> index = gInstance;
1000 if (!index) {
1001 return NS_ERROR_NOT_INITIALIZED;
1002 }
1004 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
1006 nsCOMPtr<nsIFile> file;
1008 {
1009 CacheIndexAutoLock lock(index);
1011 MOZ_ASSERT(!index->mRemovingAll);
1013 if (!index->IsIndexUsable()) {
1014 return NS_ERROR_NOT_AVAILABLE;
1015 }
1017 AutoRestore<bool> saveRemovingAll(index->mRemovingAll);
1018 index->mRemovingAll = true;
1020 // Doom index and journal handles but don't null them out since this will be
1021 // done in FinishWrite/FinishRead methods.
1022 if (index->mIndexHandle) {
1023 CacheFileIOManager::DoomFile(index->mIndexHandle, nullptr);
1024 } else {
1025 // We don't have a handle to index file, so get the file here, but delete
1026 // it outside the lock. Ignore the result since this is not fatal.
1027 index->GetFile(NS_LITERAL_CSTRING(kIndexName), getter_AddRefs(file));
1028 }
1030 if (index->mJournalHandle) {
1031 CacheFileIOManager::DoomFile(index->mJournalHandle, nullptr);
1032 }
1034 switch (index->mState) {
1035 case WRITING:
1036 index->FinishWrite(false);
1037 break;
1038 case READY:
1039 // nothing to do
1040 break;
1041 case READING:
1042 index->FinishRead(false);
1043 break;
1044 case BUILDING:
1045 case UPDATING:
1046 index->FinishUpdate(false);
1047 break;
1048 default:
1049 MOZ_ASSERT(false, "Unexpected state!");
1050 }
1052 // We should end up in READY state
1053 MOZ_ASSERT(index->mState == READY);
1055 // There should not be any handle
1056 MOZ_ASSERT(!index->mIndexHandle);
1057 MOZ_ASSERT(!index->mJournalHandle);
1059 index->mIndexOnDiskIsValid = false;
1060 index->mIndexNeedsUpdate = false;
1062 index->mIndexStats.Clear();
1063 index->mFrecencyArray.Clear();
1064 index->mExpirationArray.Clear();
1065 index->mIndex.Clear();
1066 }
1068 if (file) {
1069 // Ignore the result. The file might not exist and the failure is not fatal.
1070 file->Remove(false);
1071 }
1073 return NS_OK;
1074 }
1076 // static
1077 nsresult
1078 CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval)
1079 {
1080 LOG(("CacheIndex::HasEntry() [key=%s]", PromiseFlatCString(aKey).get()));
1082 nsRefPtr<CacheIndex> index = gInstance;
1084 if (!index) {
1085 return NS_ERROR_NOT_INITIALIZED;
1086 }
1088 CacheIndexAutoLock lock(index);
1090 if (!index->IsIndexUsable()) {
1091 return NS_ERROR_NOT_AVAILABLE;
1092 }
1094 SHA1Sum sum;
1095 SHA1Sum::Hash hash;
1096 sum.update(aKey.BeginReading(), aKey.Length());
1097 sum.finish(hash);
1099 CacheIndexEntry *entry = nullptr;
1101 switch (index->mState) {
1102 case READING:
1103 case WRITING:
1104 entry = index->mPendingUpdates.GetEntry(hash);
1105 // no break
1106 case BUILDING:
1107 case UPDATING:
1108 case READY:
1109 if (!entry) {
1110 entry = index->mIndex.GetEntry(hash);
1111 }
1112 break;
1113 case INITIAL:
1114 case SHUTDOWN:
1115 MOZ_ASSERT(false, "Unexpected state!");
1116 }
1118 if (!entry) {
1119 if (index->mState == READY || index->mState == WRITING) {
1120 *_retval = DOES_NOT_EXIST;
1121 } else {
1122 *_retval = DO_NOT_KNOW;
1123 }
1124 } else {
1125 if (entry->IsRemoved()) {
1126 if (entry->IsFresh()) {
1127 *_retval = DOES_NOT_EXIST;
1128 } else {
1129 *_retval = DO_NOT_KNOW;
1130 }
1131 } else {
1132 *_retval = EXISTS;
1133 }
1134 }
1136 LOG(("CacheIndex::HasEntry() - result is %u", *_retval));
1137 return NS_OK;
1138 }
1140 // static
1141 nsresult
1142 CacheIndex::GetEntryForEviction(SHA1Sum::Hash *aHash, uint32_t *aCnt)
1143 {
1144 LOG(("CacheIndex::GetEntryForEviction()"));
1146 nsRefPtr<CacheIndex> index = gInstance;
1148 if (!index)
1149 return NS_ERROR_NOT_INITIALIZED;
1151 CacheIndexAutoLock lock(index);
1153 if (!index->IsIndexUsable()) {
1154 return NS_ERROR_NOT_AVAILABLE;
1155 }
1157 MOZ_ASSERT(index->mFrecencyArray.Length() ==
1158 index->mExpirationArray.Length());
1160 if (index->mExpirationArray.Length() == 0)
1161 return NS_ERROR_NOT_AVAILABLE;
1163 uint32_t now = PR_Now() / PR_USEC_PER_SEC;
1164 if (index->mExpirationArray[0]->mExpirationTime < now) {
1165 memcpy(aHash, &index->mExpirationArray[0]->mHash, sizeof(SHA1Sum::Hash));
1166 *aCnt = index->mExpirationArray.Length();
1167 LOG(("CacheIndex::GetEntryForEviction() - returning entry from expiration "
1168 "array [hash=%08x%08x%08x%08x%08x, cnt=%u, expTime=%u, now=%u, "
1169 "frecency=%u]", LOGSHA1(aHash), *aCnt,
1170 index->mExpirationArray[0]->mExpirationTime, now,
1171 index->mExpirationArray[0]->mFrecency));
1172 }
1173 else {
1174 memcpy(aHash, &index->mFrecencyArray[0]->mHash, sizeof(SHA1Sum::Hash));
1175 *aCnt = index->mFrecencyArray.Length();
1176 LOG(("CacheIndex::GetEntryForEviction() - returning entry from frecency "
1177 "array [hash=%08x%08x%08x%08x%08x, cnt=%u, expTime=%u, now=%u, "
1178 "frecency=%u]", LOGSHA1(aHash), *aCnt,
1179 index->mExpirationArray[0]->mExpirationTime, now,
1180 index->mExpirationArray[0]->mFrecency));
1181 }
1183 return NS_OK;
1184 }
1186 // static
1187 nsresult
1188 CacheIndex::GetCacheSize(uint32_t *_retval)
1189 {
1190 LOG(("CacheIndex::GetCacheSize()"));
1192 nsRefPtr<CacheIndex> index = gInstance;
1194 if (!index)
1195 return NS_ERROR_NOT_INITIALIZED;
1197 CacheIndexAutoLock lock(index);
1199 if (!index->IsIndexUsable()) {
1200 return NS_ERROR_NOT_AVAILABLE;
1201 }
1203 *_retval = index->mIndexStats.Size();
1204 LOG(("CacheIndex::GetCacheSize() - returning %u", *_retval));
1205 return NS_OK;
1206 }
1208 // static
1209 nsresult
1210 CacheIndex::AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserver)
1211 {
1212 LOG(("CacheIndex::AsyncGetDiskConsumption()"));
1214 nsRefPtr<CacheIndex> index = gInstance;
1216 if (!index) {
1217 return NS_ERROR_NOT_INITIALIZED;
1218 }
1220 CacheIndexAutoLock lock(index);
1222 if (!index->IsIndexUsable()) {
1223 return NS_ERROR_NOT_AVAILABLE;
1224 }
1226 nsRefPtr<DiskConsumptionObserver> observer =
1227 DiskConsumptionObserver::Init(aObserver);
1229 NS_ENSURE_ARG(observer);
1231 if (index->mState == READY || index->mState == WRITING) {
1232 LOG(("CacheIndex::AsyncGetDiskConsumption - calling immediately"));
1233 // Safe to call the callback under the lock,
1234 // we always post to the main thread.
1235 observer->OnDiskConsumption(index->mIndexStats.Size() << 10);
1236 return NS_OK;
1237 }
1239 LOG(("CacheIndex::AsyncGetDiskConsumption - remembering callback"));
1240 // Will be called when the index get to the READY state.
1241 index->mDiskConsumptionObservers.AppendElement(observer);
1243 return NS_OK;
1244 }
1246 // static
1247 nsresult
1248 CacheIndex::GetIterator(nsILoadContextInfo *aInfo, bool aAddNew,
1249 CacheIndexIterator **_retval)
1250 {
1251 LOG(("CacheIndex::GetIterator() [info=%p, addNew=%d]", aInfo, aAddNew));
1253 nsRefPtr<CacheIndex> index = gInstance;
1255 if (!index) {
1256 return NS_ERROR_NOT_INITIALIZED;
1257 }
1259 CacheIndexAutoLock lock(index);
1261 if (!index->IsIndexUsable()) {
1262 return NS_ERROR_NOT_AVAILABLE;
1263 }
1265 nsRefPtr<CacheIndexIterator> iter;
1266 if (aInfo) {
1267 iter = new CacheIndexContextIterator(index, aAddNew, aInfo);
1268 } else {
1269 iter = new CacheIndexIterator(index, aAddNew);
1270 }
1272 iter->AddRecords(index->mFrecencyArray);
1274 index->mIterators.AppendElement(iter);
1275 iter.swap(*_retval);
1276 return NS_OK;
1277 }
1279 // static
1280 nsresult
1281 CacheIndex::IsUpToDate(bool *_retval)
1282 {
1283 LOG(("CacheIndex::IsUpToDate()"));
1285 nsRefPtr<CacheIndex> index = gInstance;
1287 if (!index) {
1288 return NS_ERROR_NOT_INITIALIZED;
1289 }
1291 CacheIndexAutoLock lock(index);
1293 if (!index->IsIndexUsable()) {
1294 return NS_ERROR_NOT_AVAILABLE;
1295 }
1297 *_retval = (index->mState == READY || index->mState == WRITING) &&
1298 !index->mIndexNeedsUpdate && !index->mShuttingDown;
1300 LOG(("CacheIndex::IsUpToDate() - returning %p", *_retval));
1301 return NS_OK;
1302 }
1304 bool
1305 CacheIndex::IsIndexUsable()
1306 {
1307 MOZ_ASSERT(mState != INITIAL);
1309 switch (mState) {
1310 case INITIAL:
1311 case SHUTDOWN:
1312 return false;
1314 case READING:
1315 case WRITING:
1316 case BUILDING:
1317 case UPDATING:
1318 case READY:
1319 break;
1320 }
1322 return true;
1323 }
1325 // static
1326 bool
1327 CacheIndex::IsCollision(CacheIndexEntry *aEntry,
1328 uint32_t aAppId,
1329 bool aAnonymous,
1330 bool aInBrowser)
1331 {
1332 if (!aEntry->IsInitialized()) {
1333 return false;
1334 }
1336 if (aEntry->AppId() != aAppId || aEntry->Anonymous() != aAnonymous ||
1337 aEntry->InBrowser() != aInBrowser) {
1338 LOG(("CacheIndex::IsCollision() - Collision detected for entry hash=%08x"
1339 "%08x%08x%08x%08x, expected values: appId=%u, anonymous=%d, "
1340 "inBrowser=%d; actual values: appId=%u, anonymous=%d, inBrowser=%d]",
1341 LOGSHA1(aEntry->Hash()), aAppId, aAnonymous, aInBrowser,
1342 aEntry->AppId(), aEntry->Anonymous(), aEntry->InBrowser()));
1343 return true;
1344 }
1346 return false;
1347 }
1349 // static
1350 bool
1351 CacheIndex::HasEntryChanged(CacheIndexEntry *aEntry,
1352 const uint32_t *aFrecency,
1353 const uint32_t *aExpirationTime,
1354 const uint32_t *aSize)
1355 {
1356 if (aFrecency && *aFrecency != aEntry->GetFrecency()) {
1357 return true;
1358 }
1360 if (aExpirationTime && *aExpirationTime != aEntry->GetExpirationTime()) {
1361 return true;
1362 }
1364 if (aSize &&
1365 (*aSize & CacheIndexEntry::kFileSizeMask) != aEntry->GetFileSize()) {
1366 return true;
1367 }
1369 return false;
1370 }
1372 void
1373 CacheIndex::ProcessPendingOperations()
1374 {
1375 LOG(("CacheIndex::ProcessPendingOperations()"));
1377 AssertOwnsLock();
1379 mPendingUpdates.EnumerateEntries(&CacheIndex::UpdateEntryInIndex, this);
1381 MOZ_ASSERT(mPendingUpdates.Count() == 0);
1383 EnsureCorrectStats();
1384 }
1386 // static
1387 PLDHashOperator
1388 CacheIndex::UpdateEntryInIndex(CacheIndexEntry *aEntry, void* aClosure)
1389 {
1390 CacheIndex *index = static_cast<CacheIndex *>(aClosure);
1392 LOG(("CacheFile::UpdateEntryInIndex() [hash=%08x%08x%08x%08x%08x]",
1393 LOGSHA1(aEntry->Hash())));
1395 MOZ_ASSERT(aEntry->IsFresh());
1396 MOZ_ASSERT(aEntry->IsDirty());
1398 CacheIndexEntry *entry = index->mIndex.GetEntry(*aEntry->Hash());
1400 CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
1401 emng.DoNotSearchInUpdates();
1403 if (aEntry->IsRemoved()) {
1404 if (entry) {
1405 if (entry->IsRemoved()) {
1406 MOZ_ASSERT(entry->IsFresh());
1407 MOZ_ASSERT(entry->IsDirty());
1408 } else if (!entry->IsDirty() && entry->IsFileEmpty()) {
1409 // Entries with empty file are not stored in index on disk. Just remove
1410 // the entry, but only in case the entry is not dirty, i.e. the entry
1411 // file was empty when we wrote the index.
1412 index->mIndex.RemoveEntry(*aEntry->Hash());
1413 entry = nullptr;
1414 } else {
1415 entry->MarkRemoved();
1416 entry->MarkDirty();
1417 entry->MarkFresh();
1418 }
1419 }
1421 return PL_DHASH_REMOVE;
1422 }
1424 entry = index->mIndex.PutEntry(*aEntry->Hash());
1425 *entry = *aEntry;
1427 return PL_DHASH_REMOVE;
1428 }
1430 bool
1431 CacheIndex::WriteIndexToDiskIfNeeded()
1432 {
1433 if (mState != READY || mShuttingDown) {
1434 return false;
1435 }
1437 if (!mLastDumpTime.IsNull() &&
1438 (TimeStamp::NowLoRes() - mLastDumpTime).ToMilliseconds() <
1439 kMinDumpInterval) {
1440 return false;
1441 }
1443 if (mIndexStats.Dirty() < kMinUnwrittenChanges) {
1444 return false;
1445 }
1447 WriteIndexToDisk();
1448 return true;
1449 }
1451 void
1452 CacheIndex::WriteIndexToDisk()
1453 {
1454 LOG(("CacheIndex::WriteIndexToDisk()"));
1455 mIndexStats.Log();
1457 nsresult rv;
1459 AssertOwnsLock();
1460 MOZ_ASSERT(mState == READY);
1461 MOZ_ASSERT(!mRWBuf);
1462 MOZ_ASSERT(!mRWHash);
1464 ChangeState(WRITING);
1466 mProcessEntries = mIndexStats.ActiveEntriesCount();
1468 mIndexFileOpener = new FileOpenHelper(this);
1469 rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kTempIndexName),
1470 CacheFileIOManager::SPECIAL_FILE |
1471 CacheFileIOManager::CREATE,
1472 true,
1473 mIndexFileOpener);
1474 if (NS_FAILED(rv)) {
1475 LOG(("CacheIndex::WriteIndexToDisk() - Can't open file [rv=0x%08x]", rv));
1476 FinishWrite(false);
1477 return;
1478 }
1480 // Write index header to a buffer, it will be written to disk together with
1481 // records in WriteRecords() once we open the file successfully.
1482 AllocBuffer();
1483 mRWHash = new CacheHash();
1485 CacheIndexHeader *hdr = reinterpret_cast<CacheIndexHeader *>(mRWBuf);
1486 NetworkEndian::writeUint32(&hdr->mVersion, kIndexVersion);
1487 NetworkEndian::writeUint32(&hdr->mTimeStamp,
1488 static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC));
1489 NetworkEndian::writeUint32(&hdr->mIsDirty, 1);
1491 mRWBufPos = sizeof(CacheIndexHeader);
1492 mSkipEntries = 0;
1493 }
1495 namespace { // anon
1497 struct WriteRecordsHelper
1498 {
1499 char *mBuf;
1500 uint32_t mSkip;
1501 uint32_t mProcessMax;
1502 uint32_t mProcessed;
1503 #ifdef DEBUG
1504 bool mHasMore;
1505 #endif
1506 };
1508 } // anon
1510 void
1511 CacheIndex::WriteRecords()
1512 {
1513 LOG(("CacheIndex::WriteRecords()"));
1515 nsresult rv;
1517 AssertOwnsLock();
1518 MOZ_ASSERT(mState == WRITING);
1520 int64_t fileOffset;
1522 if (mSkipEntries) {
1523 MOZ_ASSERT(mRWBufPos == 0);
1524 fileOffset = sizeof(CacheIndexHeader);
1525 fileOffset += sizeof(CacheIndexRecord) * mSkipEntries;
1526 } else {
1527 MOZ_ASSERT(mRWBufPos == sizeof(CacheIndexHeader));
1528 fileOffset = 0;
1529 }
1530 uint32_t hashOffset = mRWBufPos;
1532 WriteRecordsHelper data;
1533 data.mBuf = mRWBuf + mRWBufPos;
1534 data.mSkip = mSkipEntries;
1535 data.mProcessMax = (mRWBufSize - mRWBufPos) / sizeof(CacheIndexRecord);
1536 MOZ_ASSERT(data.mProcessMax != 0 || mProcessEntries == 0); // TODO make sure we can write an empty index
1537 data.mProcessed = 0;
1538 #ifdef DEBUG
1539 data.mHasMore = false;
1540 #endif
1542 mIndex.EnumerateEntries(&CacheIndex::CopyRecordsToRWBuf, &data);
1543 MOZ_ASSERT(mRWBufPos != static_cast<uint32_t>(data.mBuf - mRWBuf) ||
1544 mProcessEntries == 0);
1545 mRWBufPos = data.mBuf - mRWBuf;
1546 mSkipEntries += data.mProcessed;
1547 MOZ_ASSERT(mSkipEntries <= mProcessEntries);
1549 mRWHash->Update(mRWBuf + hashOffset, mRWBufPos - hashOffset);
1551 if (mSkipEntries == mProcessEntries) {
1552 MOZ_ASSERT(!data.mHasMore);
1554 // We've processed all records
1555 if (mRWBufPos + sizeof(CacheHash::Hash32_t) > mRWBufSize) {
1556 // realloc buffer to spare another write cycle
1557 mRWBufSize = mRWBufPos + sizeof(CacheHash::Hash32_t);
1558 mRWBuf = static_cast<char *>(moz_xrealloc(mRWBuf, mRWBufSize));
1559 }
1561 NetworkEndian::writeUint32(mRWBuf + mRWBufPos, mRWHash->GetHash());
1562 mRWBufPos += sizeof(CacheHash::Hash32_t);
1563 } else {
1564 MOZ_ASSERT(data.mHasMore);
1565 }
1567 rv = CacheFileIOManager::Write(mIndexHandle, fileOffset, mRWBuf, mRWBufPos,
1568 mSkipEntries == mProcessEntries, this);
1569 if (NS_FAILED(rv)) {
1570 LOG(("CacheIndex::WriteRecords() - CacheFileIOManager::Write() failed "
1571 "synchronously [rv=0x%08x]", rv));
1572 FinishWrite(false);
1573 }
1575 mRWBufPos = 0;
1576 }
1578 void
1579 CacheIndex::FinishWrite(bool aSucceeded)
1580 {
1581 LOG(("CacheIndex::FinishWrite() [succeeded=%d]", aSucceeded));
1583 MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == WRITING);
1585 AssertOwnsLock();
1587 mIndexHandle = nullptr;
1588 mRWHash = nullptr;
1589 ReleaseBuffer();
1591 if (aSucceeded) {
1592 // Opening of the file must not be in progress if writing succeeded.
1593 MOZ_ASSERT(!mIndexFileOpener);
1595 mIndex.EnumerateEntries(&CacheIndex::ApplyIndexChanges, this);
1596 mIndexOnDiskIsValid = true;
1597 } else {
1598 if (mIndexFileOpener) {
1599 // If opening of the file is still in progress (e.g. WRITE process was
1600 // canceled by RemoveAll()) then we need to cancel the opener to make sure
1601 // that OnFileOpenedInternal() won't be called.
1602 mIndexFileOpener->Cancel();
1603 mIndexFileOpener = nullptr;
1604 }
1605 }
1607 ProcessPendingOperations();
1608 mIndexStats.Log();
1610 if (mState == WRITING) {
1611 ChangeState(READY);
1612 mLastDumpTime = TimeStamp::NowLoRes();
1613 }
1614 }
1616 // static
1617 PLDHashOperator
1618 CacheIndex::CopyRecordsToRWBuf(CacheIndexEntry *aEntry, void* aClosure)
1619 {
1620 if (aEntry->IsRemoved()) {
1621 return PL_DHASH_NEXT;
1622 }
1624 if (!aEntry->IsInitialized()) {
1625 return PL_DHASH_NEXT;
1626 }
1628 if (aEntry->IsFileEmpty()) {
1629 return PL_DHASH_NEXT;
1630 }
1632 WriteRecordsHelper *data = static_cast<WriteRecordsHelper *>(aClosure);
1633 if (data->mSkip) {
1634 data->mSkip--;
1635 return PL_DHASH_NEXT;
1636 }
1638 if (data->mProcessed == data->mProcessMax) {
1639 #ifdef DEBUG
1640 data->mHasMore = true;
1641 #endif
1642 return PL_DHASH_STOP;
1643 }
1645 aEntry->WriteToBuf(data->mBuf);
1646 data->mBuf += sizeof(CacheIndexRecord);
1647 data->mProcessed++;
1649 return PL_DHASH_NEXT;
1650 }
1652 // static
1653 PLDHashOperator
1654 CacheIndex::ApplyIndexChanges(CacheIndexEntry *aEntry, void* aClosure)
1655 {
1656 CacheIndex *index = static_cast<CacheIndex *>(aClosure);
1658 CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
1660 if (aEntry->IsRemoved()) {
1661 emng.DoNotSearchInIndex();
1662 return PL_DHASH_REMOVE;
1663 }
1665 if (aEntry->IsDirty()) {
1666 aEntry->ClearDirty();
1667 }
1669 return PL_DHASH_NEXT;
1670 }
1672 nsresult
1673 CacheIndex::GetFile(const nsACString &aName, nsIFile **_retval)
1674 {
1675 nsresult rv;
1677 nsCOMPtr<nsIFile> file;
1678 rv = mCacheDirectory->Clone(getter_AddRefs(file));
1679 NS_ENSURE_SUCCESS(rv, rv);
1681 rv = file->AppendNative(aName);
1682 NS_ENSURE_SUCCESS(rv, rv);
1684 file.swap(*_retval);
1685 return NS_OK;
1686 }
1688 nsresult
1689 CacheIndex::RemoveFile(const nsACString &aName)
1690 {
1691 MOZ_ASSERT(mState == SHUTDOWN);
1693 nsresult rv;
1695 nsCOMPtr<nsIFile> file;
1696 rv = GetFile(aName, getter_AddRefs(file));
1697 NS_ENSURE_SUCCESS(rv, rv);
1699 bool exists;
1700 rv = file->Exists(&exists);
1701 NS_ENSURE_SUCCESS(rv, rv);
1703 if (exists) {
1704 rv = file->Remove(false);
1705 if (NS_FAILED(rv)) {
1706 LOG(("CacheIndex::RemoveFile() - Cannot remove old entry file from disk."
1707 "[name=%s]", PromiseFlatCString(aName).get()));
1708 NS_WARNING("Cannot remove old entry file from the disk");
1709 return rv;
1710 }
1711 }
1713 return NS_OK;
1714 }
1716 void
1717 CacheIndex::RemoveIndexFromDisk()
1718 {
1719 LOG(("CacheIndex::RemoveIndexFromDisk()"));
1721 RemoveFile(NS_LITERAL_CSTRING(kIndexName));
1722 RemoveFile(NS_LITERAL_CSTRING(kTempIndexName));
1723 RemoveFile(NS_LITERAL_CSTRING(kJournalName));
1724 }
1726 class WriteLogHelper
1727 {
1728 public:
1729 WriteLogHelper(PRFileDesc *aFD)
1730 : mStatus(NS_OK)
1731 , mFD(aFD)
1732 , mBufSize(kMaxBufSize)
1733 , mBufPos(0)
1734 {
1735 mHash = new CacheHash();
1736 mBuf = static_cast<char *>(moz_xmalloc(mBufSize));
1737 }
1739 ~WriteLogHelper() {
1740 free(mBuf);
1741 }
1743 nsresult AddEntry(CacheIndexEntry *aEntry);
1744 nsresult Finish();
1746 private:
1748 nsresult FlushBuffer();
1750 nsresult mStatus;
1751 PRFileDesc *mFD;
1752 char *mBuf;
1753 uint32_t mBufSize;
1754 int32_t mBufPos;
1755 nsRefPtr<CacheHash> mHash;
1756 };
1758 nsresult
1759 WriteLogHelper::AddEntry(CacheIndexEntry *aEntry)
1760 {
1761 nsresult rv;
1763 if (NS_FAILED(mStatus)) {
1764 return mStatus;
1765 }
1767 if (mBufPos + sizeof(CacheIndexRecord) > mBufSize) {
1768 mHash->Update(mBuf, mBufPos);
1770 rv = FlushBuffer();
1771 if (NS_FAILED(rv)) {
1772 mStatus = rv;
1773 return rv;
1774 }
1775 MOZ_ASSERT(mBufPos + sizeof(CacheIndexRecord) <= mBufSize);
1776 }
1778 aEntry->WriteToBuf(mBuf + mBufPos);
1779 mBufPos += sizeof(CacheIndexRecord);
1781 return NS_OK;
1782 }
1784 nsresult
1785 WriteLogHelper::Finish()
1786 {
1787 nsresult rv;
1789 if (NS_FAILED(mStatus)) {
1790 return mStatus;
1791 }
1793 mHash->Update(mBuf, mBufPos);
1794 if (mBufPos + sizeof(CacheHash::Hash32_t) > mBufSize) {
1795 rv = FlushBuffer();
1796 if (NS_FAILED(rv)) {
1797 mStatus = rv;
1798 return rv;
1799 }
1800 MOZ_ASSERT(mBufPos + sizeof(CacheHash::Hash32_t) <= mBufSize);
1801 }
1803 NetworkEndian::writeUint32(mBuf + mBufPos, mHash->GetHash());
1804 mBufPos += sizeof(CacheHash::Hash32_t);
1806 rv = FlushBuffer();
1807 NS_ENSURE_SUCCESS(rv, rv);
1809 mStatus = NS_ERROR_UNEXPECTED; // Don't allow any other operation
1810 return NS_OK;
1811 }
1813 nsresult
1814 WriteLogHelper::FlushBuffer()
1815 {
1816 MOZ_ASSERT(NS_SUCCEEDED(mStatus));
1818 int32_t bytesWritten = PR_Write(mFD, mBuf, mBufPos);
1820 if (bytesWritten != mBufPos) {
1821 return NS_ERROR_FAILURE;
1822 }
1824 mBufPos = 0;
1825 return NS_OK;
1826 }
1828 nsresult
1829 CacheIndex::WriteLogToDisk()
1830 {
1831 LOG(("CacheIndex::WriteLogToDisk()"));
1833 nsresult rv;
1835 MOZ_ASSERT(mPendingUpdates.Count() == 0);
1836 MOZ_ASSERT(mState == SHUTDOWN);
1838 RemoveFile(NS_LITERAL_CSTRING(kTempIndexName));
1840 nsCOMPtr<nsIFile> indexFile;
1841 rv = GetFile(NS_LITERAL_CSTRING(kIndexName), getter_AddRefs(indexFile));
1842 NS_ENSURE_SUCCESS(rv, rv);
1844 nsCOMPtr<nsIFile> logFile;
1845 rv = GetFile(NS_LITERAL_CSTRING(kJournalName), getter_AddRefs(logFile));
1846 NS_ENSURE_SUCCESS(rv, rv);
1848 mIndexStats.Log();
1850 PRFileDesc *fd = nullptr;
1851 rv = logFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE,
1852 0600, &fd);
1853 NS_ENSURE_SUCCESS(rv, rv);
1855 WriteLogHelper wlh(fd);
1856 mIndex.EnumerateEntries(&CacheIndex::WriteEntryToLog, &wlh);
1858 rv = wlh.Finish();
1859 PR_Close(fd);
1860 NS_ENSURE_SUCCESS(rv, rv);
1862 rv = indexFile->OpenNSPRFileDesc(PR_RDWR, 0600, &fd);
1863 NS_ENSURE_SUCCESS(rv, rv);
1865 CacheIndexHeader header;
1866 int32_t bytesRead = PR_Read(fd, &header, sizeof(CacheIndexHeader));
1867 if (bytesRead != sizeof(CacheIndexHeader)) {
1868 PR_Close(fd);
1869 return NS_ERROR_FAILURE;
1870 }
1872 NetworkEndian::writeUint32(&header.mIsDirty, 0);
1874 int64_t offset = PR_Seek64(fd, 0, PR_SEEK_SET);
1875 if (offset == -1) {
1876 PR_Close(fd);
1877 return NS_ERROR_FAILURE;
1878 }
1880 int32_t bytesWritten = PR_Write(fd, &header, sizeof(CacheIndexHeader));
1881 PR_Close(fd);
1882 if (bytesWritten != sizeof(CacheIndexHeader)) {
1883 return NS_ERROR_FAILURE;
1884 }
1886 return NS_OK;
1887 }
1889 // static
1890 PLDHashOperator
1891 CacheIndex::WriteEntryToLog(CacheIndexEntry *aEntry, void* aClosure)
1892 {
1893 WriteLogHelper *wlh = static_cast<WriteLogHelper *>(aClosure);
1895 if (aEntry->IsRemoved() || aEntry->IsDirty()) {
1896 wlh->AddEntry(aEntry);
1897 }
1899 return PL_DHASH_REMOVE;
1900 }
1902 void
1903 CacheIndex::ReadIndexFromDisk()
1904 {
1905 LOG(("CacheIndex::ReadIndexFromDisk()"));
1907 nsresult rv;
1909 AssertOwnsLock();
1910 MOZ_ASSERT(mState == INITIAL);
1912 ChangeState(READING);
1914 mIndexFileOpener = new FileOpenHelper(this);
1915 rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kIndexName),
1916 CacheFileIOManager::SPECIAL_FILE |
1917 CacheFileIOManager::OPEN,
1918 true,
1919 mIndexFileOpener);
1920 if (NS_FAILED(rv)) {
1921 LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
1922 "failed [rv=0x%08x, file=%s]", rv, kIndexName));
1923 FinishRead(false);
1924 return;
1925 }
1927 mJournalFileOpener = new FileOpenHelper(this);
1928 rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kJournalName),
1929 CacheFileIOManager::SPECIAL_FILE |
1930 CacheFileIOManager::OPEN,
1931 true,
1932 mJournalFileOpener);
1933 if (NS_FAILED(rv)) {
1934 LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
1935 "failed [rv=0x%08x, file=%s]", rv, kJournalName));
1936 FinishRead(false);
1937 }
1939 mTmpFileOpener = new FileOpenHelper(this);
1940 rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kTempIndexName),
1941 CacheFileIOManager::SPECIAL_FILE |
1942 CacheFileIOManager::OPEN,
1943 true,
1944 mTmpFileOpener);
1945 if (NS_FAILED(rv)) {
1946 LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
1947 "failed [rv=0x%08x, file=%s]", rv, kTempIndexName));
1948 FinishRead(false);
1949 }
1950 }
1952 void
1953 CacheIndex::StartReadingIndex()
1954 {
1955 LOG(("CacheIndex::StartReadingIndex()"));
1957 nsresult rv;
1959 AssertOwnsLock();
1961 MOZ_ASSERT(mIndexHandle);
1962 MOZ_ASSERT(mState == READING);
1963 MOZ_ASSERT(!mIndexOnDiskIsValid);
1964 MOZ_ASSERT(!mDontMarkIndexClean);
1965 MOZ_ASSERT(!mJournalReadSuccessfully);
1966 MOZ_ASSERT(mIndexHandle->FileSize() >= 0);
1968 int64_t entriesSize = mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
1969 sizeof(CacheHash::Hash32_t);
1971 if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
1972 LOG(("CacheIndex::StartReadingIndex() - Index is corrupted"));
1973 FinishRead(false);
1974 return;
1975 }
1977 AllocBuffer();
1978 mSkipEntries = 0;
1979 mRWHash = new CacheHash();
1981 mRWBufPos = std::min(mRWBufSize,
1982 static_cast<uint32_t>(mIndexHandle->FileSize()));
1984 rv = CacheFileIOManager::Read(mIndexHandle, 0, mRWBuf, mRWBufPos, true, this);
1985 if (NS_FAILED(rv)) {
1986 LOG(("CacheIndex::StartReadingIndex() - CacheFileIOManager::Read() failed "
1987 "synchronously [rv=0x%08x]", rv));
1988 FinishRead(false);
1989 }
1990 }
1992 void
1993 CacheIndex::ParseRecords()
1994 {
1995 LOG(("CacheIndex::ParseRecords()"));
1997 nsresult rv;
1999 AssertOwnsLock();
2001 uint32_t entryCnt = (mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
2002 sizeof(CacheHash::Hash32_t)) / sizeof(CacheIndexRecord);
2003 uint32_t pos = 0;
2005 if (!mSkipEntries) {
2006 CacheIndexHeader *hdr = reinterpret_cast<CacheIndexHeader *>(
2007 moz_xmalloc(sizeof(CacheIndexHeader)));
2008 memcpy(hdr, mRWBuf, sizeof(CacheIndexHeader));
2010 if (NetworkEndian::readUint32(&hdr->mVersion) != kIndexVersion) {
2011 free(hdr);
2012 FinishRead(false);
2013 return;
2014 }
2016 mIndexTimeStamp = NetworkEndian::readUint32(&hdr->mTimeStamp);
2018 if (NetworkEndian::readUint32(&hdr->mIsDirty)) {
2019 if (mJournalHandle) {
2020 CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
2021 mJournalHandle = nullptr;
2022 }
2023 free(hdr);
2024 } else {
2025 NetworkEndian::writeUint32(&hdr->mIsDirty, 1);
2027 // Mark index dirty. The buffer is freed by CacheFileIOManager when
2028 // nullptr is passed as the listener and the call doesn't fail
2029 // synchronously.
2030 rv = CacheFileIOManager::Write(mIndexHandle, 0,
2031 reinterpret_cast<char *>(hdr),
2032 sizeof(CacheIndexHeader), true, nullptr);
2033 if (NS_FAILED(rv)) {
2034 // This is not fatal, just free the memory
2035 free(hdr);
2036 }
2037 }
2039 pos += sizeof(CacheIndexHeader);
2040 }
2042 uint32_t hashOffset = pos;
2044 while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
2045 mSkipEntries != entryCnt) {
2046 CacheIndexRecord *rec = reinterpret_cast<CacheIndexRecord *>(mRWBuf + pos);
2047 CacheIndexEntry tmpEntry(&rec->mHash);
2048 tmpEntry.ReadFromBuf(mRWBuf + pos);
2050 if (tmpEntry.IsDirty() || !tmpEntry.IsInitialized() ||
2051 tmpEntry.IsFileEmpty() || tmpEntry.IsFresh() || tmpEntry.IsRemoved()) {
2052 LOG(("CacheIndex::ParseRecords() - Invalid entry found in index, removing"
2053 " whole index [dirty=%d, initialized=%d, fileEmpty=%d, fresh=%d, "
2054 "removed=%d]", tmpEntry.IsDirty(), tmpEntry.IsInitialized(),
2055 tmpEntry.IsFileEmpty(), tmpEntry.IsFresh(), tmpEntry.IsRemoved()));
2056 FinishRead(false);
2057 return;
2058 }
2060 CacheIndexEntryAutoManage emng(tmpEntry.Hash(), this);
2062 CacheIndexEntry *entry = mIndex.PutEntry(*tmpEntry.Hash());
2063 *entry = tmpEntry;
2065 pos += sizeof(CacheIndexRecord);
2066 mSkipEntries++;
2067 }
2069 mRWHash->Update(mRWBuf + hashOffset, pos - hashOffset);
2071 if (pos != mRWBufPos) {
2072 memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
2073 mRWBufPos -= pos;
2074 pos = 0;
2075 }
2077 int64_t fileOffset = sizeof(CacheIndexHeader) +
2078 mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
2080 MOZ_ASSERT(fileOffset <= mIndexHandle->FileSize());
2081 if (fileOffset == mIndexHandle->FileSize()) {
2082 if (mRWHash->GetHash() != NetworkEndian::readUint32(mRWBuf)) {
2083 LOG(("CacheIndex::ParseRecords() - Hash mismatch, [is %x, should be %x]",
2084 mRWHash->GetHash(),
2085 NetworkEndian::readUint32(mRWBuf)));
2086 FinishRead(false);
2087 return;
2088 }
2090 mIndexOnDiskIsValid = true;
2091 mJournalReadSuccessfully = false;
2093 if (mJournalHandle) {
2094 StartReadingJournal();
2095 } else {
2096 FinishRead(false);
2097 }
2099 return;
2100 }
2102 pos = mRWBufPos;
2103 uint32_t toRead = std::min(mRWBufSize - pos,
2104 static_cast<uint32_t>(mIndexHandle->FileSize() -
2105 fileOffset));
2106 mRWBufPos = pos + toRead;
2108 rv = CacheFileIOManager::Read(mIndexHandle, fileOffset, mRWBuf + pos, toRead,
2109 true, this);
2110 if (NS_FAILED(rv)) {
2111 LOG(("CacheIndex::ParseRecords() - CacheFileIOManager::Read() failed "
2112 "synchronously [rv=0x%08x]", rv));
2113 FinishRead(false);
2114 return;
2115 }
2116 }
2118 void
2119 CacheIndex::StartReadingJournal()
2120 {
2121 LOG(("CacheIndex::StartReadingJournal()"));
2123 nsresult rv;
2125 AssertOwnsLock();
2127 MOZ_ASSERT(mJournalHandle);
2128 MOZ_ASSERT(mIndexOnDiskIsValid);
2129 MOZ_ASSERT(mTmpJournal.Count() == 0);
2130 MOZ_ASSERT(mJournalHandle->FileSize() >= 0);
2132 int64_t entriesSize = mJournalHandle->FileSize() -
2133 sizeof(CacheHash::Hash32_t);
2135 if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
2136 LOG(("CacheIndex::StartReadingJournal() - Journal is corrupted"));
2137 FinishRead(false);
2138 return;
2139 }
2141 mSkipEntries = 0;
2142 mRWHash = new CacheHash();
2144 mRWBufPos = std::min(mRWBufSize,
2145 static_cast<uint32_t>(mJournalHandle->FileSize()));
2147 rv = CacheFileIOManager::Read(mJournalHandle, 0, mRWBuf, mRWBufPos, true, this);
2148 if (NS_FAILED(rv)) {
2149 LOG(("CacheIndex::StartReadingJournal() - CacheFileIOManager::Read() failed"
2150 " synchronously [rv=0x%08x]", rv));
2151 FinishRead(false);
2152 }
2153 }
2155 void
2156 CacheIndex::ParseJournal()
2157 {
2158 LOG(("CacheIndex::ParseRecords()"));
2160 nsresult rv;
2162 AssertOwnsLock();
2164 uint32_t entryCnt = (mJournalHandle->FileSize() -
2165 sizeof(CacheHash::Hash32_t)) / sizeof(CacheIndexRecord);
2167 uint32_t pos = 0;
2169 while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
2170 mSkipEntries != entryCnt) {
2171 CacheIndexRecord *rec = reinterpret_cast<CacheIndexRecord *>(mRWBuf + pos);
2172 CacheIndexEntry tmpEntry(&rec->mHash);
2173 tmpEntry.ReadFromBuf(mRWBuf + pos);
2175 CacheIndexEntry *entry = mTmpJournal.PutEntry(*tmpEntry.Hash());
2176 *entry = tmpEntry;
2178 if (entry->IsDirty() || entry->IsFresh()) {
2179 LOG(("CacheIndex::ParseJournal() - Invalid entry found in journal, "
2180 "ignoring whole journal [dirty=%d, fresh=%d]", entry->IsDirty(),
2181 entry->IsFresh()));
2182 FinishRead(false);
2183 return;
2184 }
2186 pos += sizeof(CacheIndexRecord);
2187 mSkipEntries++;
2188 }
2190 mRWHash->Update(mRWBuf, pos);
2192 if (pos != mRWBufPos) {
2193 memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
2194 mRWBufPos -= pos;
2195 pos = 0;
2196 }
2198 int64_t fileOffset = mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
2200 MOZ_ASSERT(fileOffset <= mJournalHandle->FileSize());
2201 if (fileOffset == mJournalHandle->FileSize()) {
2202 if (mRWHash->GetHash() != NetworkEndian::readUint32(mRWBuf)) {
2203 LOG(("CacheIndex::ParseJournal() - Hash mismatch, [is %x, should be %x]",
2204 mRWHash->GetHash(),
2205 NetworkEndian::readUint32(mRWBuf)));
2206 FinishRead(false);
2207 return;
2208 }
2210 mJournalReadSuccessfully = true;
2211 FinishRead(true);
2212 return;
2213 }
2215 pos = mRWBufPos;
2216 uint32_t toRead = std::min(mRWBufSize - pos,
2217 static_cast<uint32_t>(mJournalHandle->FileSize() -
2218 fileOffset));
2219 mRWBufPos = pos + toRead;
2221 rv = CacheFileIOManager::Read(mJournalHandle, fileOffset, mRWBuf + pos,
2222 toRead, true, this);
2223 if (NS_FAILED(rv)) {
2224 LOG(("CacheIndex::ParseJournal() - CacheFileIOManager::Read() failed "
2225 "synchronously [rv=0x%08x]", rv));
2226 FinishRead(false);
2227 return;
2228 }
2229 }
2231 void
2232 CacheIndex::MergeJournal()
2233 {
2234 LOG(("CacheIndex::MergeJournal()"));
2236 AssertOwnsLock();
2238 mTmpJournal.EnumerateEntries(&CacheIndex::ProcessJournalEntry, this);
2240 MOZ_ASSERT(mTmpJournal.Count() == 0);
2241 }
2243 // static
2244 PLDHashOperator
2245 CacheIndex::ProcessJournalEntry(CacheIndexEntry *aEntry, void* aClosure)
2246 {
2247 CacheIndex *index = static_cast<CacheIndex *>(aClosure);
2249 LOG(("CacheFile::ProcessJournalEntry() [hash=%08x%08x%08x%08x%08x]",
2250 LOGSHA1(aEntry->Hash())));
2252 CacheIndexEntry *entry = index->mIndex.GetEntry(*aEntry->Hash());
2254 CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
2256 if (aEntry->IsRemoved()) {
2257 if (entry) {
2258 entry->MarkRemoved();
2259 entry->MarkDirty();
2260 }
2261 } else {
2262 if (!entry) {
2263 entry = index->mIndex.PutEntry(*aEntry->Hash());
2264 }
2266 *entry = *aEntry;
2267 entry->MarkDirty();
2268 }
2270 return PL_DHASH_REMOVE;
2271 }
2273 void
2274 CacheIndex::EnsureNoFreshEntry()
2275 {
2276 #ifdef DEBUG_STATS
2277 CacheIndexStats debugStats;
2278 debugStats.DisableLogging();
2279 mIndex.EnumerateEntries(&CacheIndex::SumIndexStats, &debugStats);
2280 MOZ_ASSERT(debugStats.Fresh() == 0);
2281 #endif
2282 }
2284 void
2285 CacheIndex::EnsureCorrectStats()
2286 {
2287 #ifdef DEBUG_STATS
2288 MOZ_ASSERT(mPendingUpdates.Count() == 0);
2289 CacheIndexStats debugStats;
2290 debugStats.DisableLogging();
2291 mIndex.EnumerateEntries(&CacheIndex::SumIndexStats, &debugStats);
2292 MOZ_ASSERT(debugStats == mIndexStats);
2293 #endif
2294 }
2296 // static
2297 PLDHashOperator
2298 CacheIndex::SumIndexStats(CacheIndexEntry *aEntry, void* aClosure)
2299 {
2300 CacheIndexStats *stats = static_cast<CacheIndexStats *>(aClosure);
2301 stats->BeforeChange(nullptr);
2302 stats->AfterChange(aEntry);
2303 return PL_DHASH_NEXT;
2304 }
2306 void
2307 CacheIndex::FinishRead(bool aSucceeded)
2308 {
2309 LOG(("CacheIndex::FinishRead() [succeeded=%d]", aSucceeded));
2310 AssertOwnsLock();
2312 MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == READING);
2314 MOZ_ASSERT(
2315 // -> rebuild
2316 (!aSucceeded && !mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
2317 // -> update
2318 (!aSucceeded && mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
2319 // -> ready
2320 (aSucceeded && mIndexOnDiskIsValid && mJournalReadSuccessfully));
2322 if (mState == SHUTDOWN) {
2323 RemoveFile(NS_LITERAL_CSTRING(kTempIndexName));
2324 RemoveFile(NS_LITERAL_CSTRING(kJournalName));
2325 } else {
2326 if (mIndexHandle && !mIndexOnDiskIsValid) {
2327 CacheFileIOManager::DoomFile(mIndexHandle, nullptr);
2328 }
2330 if (mJournalHandle) {
2331 CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
2332 }
2333 }
2335 if (mIndexFileOpener) {
2336 mIndexFileOpener->Cancel();
2337 mIndexFileOpener = nullptr;
2338 }
2339 if (mJournalFileOpener) {
2340 mJournalFileOpener->Cancel();
2341 mJournalFileOpener = nullptr;
2342 }
2343 if (mTmpFileOpener) {
2344 mTmpFileOpener->Cancel();
2345 mTmpFileOpener = nullptr;
2346 }
2348 mIndexHandle = nullptr;
2349 mJournalHandle = nullptr;
2350 mRWHash = nullptr;
2351 ReleaseBuffer();
2353 if (mState == SHUTDOWN) {
2354 return;
2355 }
2357 if (!mIndexOnDiskIsValid) {
2358 MOZ_ASSERT(mTmpJournal.Count() == 0);
2359 EnsureNoFreshEntry();
2360 ProcessPendingOperations();
2361 // Remove all entries that we haven't seen during this session
2362 mIndex.EnumerateEntries(&CacheIndex::RemoveNonFreshEntries, this);
2363 StartUpdatingIndex(true);
2364 return;
2365 }
2367 if (!mJournalReadSuccessfully) {
2368 mTmpJournal.Clear();
2369 EnsureNoFreshEntry();
2370 ProcessPendingOperations();
2371 StartUpdatingIndex(false);
2372 return;
2373 }
2375 MergeJournal();
2376 EnsureNoFreshEntry();
2377 ProcessPendingOperations();
2378 mIndexStats.Log();
2380 ChangeState(READY);
2381 mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
2382 }
2384 // static
2385 void
2386 CacheIndex::DelayedUpdate(nsITimer *aTimer, void *aClosure)
2387 {
2388 LOG(("CacheIndex::DelayedUpdate()"));
2390 nsresult rv;
2391 nsRefPtr<CacheIndex> index = gInstance;
2393 if (!index) {
2394 return;
2395 }
2397 CacheIndexAutoLock lock(index);
2399 index->mUpdateTimer = nullptr;
2401 if (!index->IsIndexUsable()) {
2402 return;
2403 }
2405 if (index->mState == READY && index->mShuttingDown) {
2406 return;
2407 }
2409 // mUpdateEventPending must be false here since StartUpdatingIndex() won't
2410 // schedule timer if it is true.
2411 MOZ_ASSERT(!index->mUpdateEventPending);
2412 if (index->mState != BUILDING && index->mState != UPDATING) {
2413 LOG(("CacheIndex::DelayedUpdate() - Update was canceled"));
2414 return;
2415 }
2417 // We need to redispatch to run with lower priority
2418 nsRefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
2419 MOZ_ASSERT(ioThread);
2421 index->mUpdateEventPending = true;
2422 rv = ioThread->Dispatch(index, CacheIOThread::INDEX);
2423 if (NS_FAILED(rv)) {
2424 index->mUpdateEventPending = false;
2425 NS_WARNING("CacheIndex::DelayedUpdate() - Can't dispatch event");
2426 LOG(("CacheIndex::DelayedUpdate() - Can't dispatch event" ));
2427 index->FinishUpdate(false);
2428 }
2429 }
2431 nsresult
2432 CacheIndex::ScheduleUpdateTimer(uint32_t aDelay)
2433 {
2434 LOG(("CacheIndex::ScheduleUpdateTimer() [delay=%u]", aDelay));
2436 MOZ_ASSERT(!mUpdateTimer);
2438 nsresult rv;
2440 nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
2441 NS_ENSURE_SUCCESS(rv, rv);
2443 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
2444 MOZ_ASSERT(ioTarget);
2446 rv = timer->SetTarget(ioTarget);
2447 NS_ENSURE_SUCCESS(rv, rv);
2449 rv = timer->InitWithFuncCallback(CacheIndex::DelayedUpdate, nullptr,
2450 aDelay, nsITimer::TYPE_ONE_SHOT);
2451 NS_ENSURE_SUCCESS(rv, rv);
2453 mUpdateTimer.swap(timer);
2454 return NS_OK;
2455 }
2457 nsresult
2458 CacheIndex::SetupDirectoryEnumerator()
2459 {
2460 MOZ_ASSERT(!NS_IsMainThread());
2461 MOZ_ASSERT(!mDirEnumerator);
2463 nsresult rv;
2464 nsCOMPtr<nsIFile> file;
2466 rv = mCacheDirectory->Clone(getter_AddRefs(file));
2467 NS_ENSURE_SUCCESS(rv, rv);
2469 rv = file->AppendNative(NS_LITERAL_CSTRING(kEntriesDir));
2470 NS_ENSURE_SUCCESS(rv, rv);
2472 bool exists;
2473 rv = file->Exists(&exists);
2474 NS_ENSURE_SUCCESS(rv, rv);
2476 if (!exists) {
2477 NS_WARNING("CacheIndex::SetupDirectoryEnumerator() - Entries directory "
2478 "doesn't exist!");
2479 LOG(("CacheIndex::SetupDirectoryEnumerator() - Entries directory doesn't "
2480 "exist!" ));
2481 return NS_ERROR_UNEXPECTED;
2482 }
2484 nsCOMPtr<nsISimpleEnumerator> enumerator;
2485 rv = file->GetDirectoryEntries(getter_AddRefs(enumerator));
2486 NS_ENSURE_SUCCESS(rv, rv);
2488 mDirEnumerator = do_QueryInterface(enumerator, &rv);
2489 NS_ENSURE_SUCCESS(rv, rv);
2491 return NS_OK;
2492 }
2494 void
2495 CacheIndex::InitEntryFromDiskData(CacheIndexEntry *aEntry,
2496 CacheFileMetadata *aMetaData,
2497 int64_t aFileSize)
2498 {
2499 aEntry->InitNew();
2500 aEntry->MarkDirty();
2501 aEntry->MarkFresh();
2502 aEntry->Init(aMetaData->AppId(), aMetaData->IsAnonymous(),
2503 aMetaData->IsInBrowser());
2505 uint32_t expirationTime;
2506 aMetaData->GetExpirationTime(&expirationTime);
2507 aEntry->SetExpirationTime(expirationTime);
2509 uint32_t frecency;
2510 aMetaData->GetFrecency(&frecency);
2511 aEntry->SetFrecency(frecency);
2513 aEntry->SetFileSize(static_cast<uint32_t>(
2514 std::min(static_cast<int64_t>(PR_UINT32_MAX),
2515 (aFileSize + 0x3FF) >> 10)));
2516 }
2518 bool
2519 CacheIndex::IsUpdatePending()
2520 {
2521 AssertOwnsLock();
2523 if (mUpdateTimer || mUpdateEventPending) {
2524 return true;
2525 }
2527 return false;
2528 }
2530 void
2531 CacheIndex::BuildIndex()
2532 {
2533 LOG(("CacheIndex::BuildIndex()"));
2535 AssertOwnsLock();
2537 MOZ_ASSERT(mPendingUpdates.Count() == 0);
2539 nsresult rv;
2541 if (!mDirEnumerator) {
2542 {
2543 // Do not do IO under the lock.
2544 CacheIndexAutoUnlock unlock(this);
2545 rv = SetupDirectoryEnumerator();
2546 }
2547 if (mState == SHUTDOWN) {
2548 // The index was shut down while we released the lock. FinishUpdate() was
2549 // already called from Shutdown(), so just simply return here.
2550 return;
2551 }
2553 if (NS_FAILED(rv)) {
2554 FinishUpdate(false);
2555 return;
2556 }
2557 }
2559 while (true) {
2560 if (CacheIOThread::YieldAndRerun()) {
2561 LOG(("CacheIndex::BuildIndex() - Breaking loop for higher level events."));
2562 mUpdateEventPending = true;
2563 return;
2564 }
2566 nsCOMPtr<nsIFile> file;
2567 {
2568 // Do not do IO under the lock.
2569 CacheIndexAutoUnlock unlock(this);
2570 rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
2571 }
2572 if (mState == SHUTDOWN) {
2573 return;
2574 }
2575 if (!file) {
2576 FinishUpdate(NS_SUCCEEDED(rv));
2577 return;
2578 }
2580 nsAutoCString leaf;
2581 rv = file->GetNativeLeafName(leaf);
2582 if (NS_FAILED(rv)) {
2583 LOG(("CacheIndex::BuildIndex() - GetNativeLeafName() failed! Skipping "
2584 "file."));
2585 mDontMarkIndexClean = true;
2586 continue;
2587 }
2589 SHA1Sum::Hash hash;
2590 rv = CacheFileIOManager::StrToHash(leaf, &hash);
2591 if (NS_FAILED(rv)) {
2592 LOG(("CacheIndex::BuildIndex() - Filename is not a hash, removing file. "
2593 "[name=%s]", leaf.get()));
2594 file->Remove(false);
2595 continue;
2596 }
2598 CacheIndexEntry *entry = mIndex.GetEntry(hash);
2599 if (entry && entry->IsRemoved()) {
2600 LOG(("CacheIndex::BuildIndex() - Found file that should not exist. "
2601 "[name=%s]", leaf.get()));
2602 entry->Log();
2603 MOZ_ASSERT(entry->IsFresh());
2604 entry = nullptr;
2605 }
2607 #ifdef DEBUG
2608 nsRefPtr<CacheFileHandle> handle;
2609 CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false,
2610 getter_AddRefs(handle));
2611 #endif
2613 if (entry) {
2614 // the entry is up to date
2615 LOG(("CacheIndex::BuildIndex() - Skipping file because the entry is up to"
2616 " date. [name=%s]", leaf.get()));
2617 entry->Log();
2618 MOZ_ASSERT(entry->IsFresh()); // The entry must be from this session
2619 // there must be an active CacheFile if the entry is not initialized
2620 MOZ_ASSERT(entry->IsInitialized() || handle);
2621 continue;
2622 }
2624 MOZ_ASSERT(!handle);
2626 nsRefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
2627 int64_t size = 0;
2629 {
2630 // Do not do IO under the lock.
2631 CacheIndexAutoUnlock unlock(this);
2632 rv = meta->SyncReadMetadata(file);
2634 if (NS_SUCCEEDED(rv)) {
2635 rv = file->GetFileSize(&size);
2636 if (NS_FAILED(rv)) {
2637 LOG(("CacheIndex::BuildIndex() - Cannot get filesize of file that was"
2638 " successfully parsed. [name=%s]", leaf.get()));
2639 }
2640 }
2641 }
2642 if (mState == SHUTDOWN) {
2643 return;
2644 }
2646 // Nobody could add the entry while the lock was released since we modify
2647 // the index only on IO thread and this loop is executed on IO thread too.
2648 entry = mIndex.GetEntry(hash);
2649 MOZ_ASSERT(!entry || entry->IsRemoved());
2651 if (NS_FAILED(rv)) {
2652 LOG(("CacheIndex::BuildIndex() - CacheFileMetadata::SyncReadMetadata() "
2653 "failed, removing file. [name=%s]", leaf.get()));
2654 file->Remove(false);
2655 } else {
2656 CacheIndexEntryAutoManage entryMng(&hash, this);
2657 entry = mIndex.PutEntry(hash);
2658 InitEntryFromDiskData(entry, meta, size);
2659 LOG(("CacheIndex::BuildIndex() - Added entry to index. [hash=%s]",
2660 leaf.get()));
2661 entry->Log();
2662 }
2663 }
2665 NS_NOTREACHED("We should never get here");
2666 }
2668 bool
2669 CacheIndex::StartUpdatingIndexIfNeeded(bool aSwitchingToReadyState)
2670 {
2671 // Start updating process when we are in or we are switching to READY state
2672 // and index needs update, but not during shutdown or when removing all
2673 // entries.
2674 if ((mState == READY || aSwitchingToReadyState) && mIndexNeedsUpdate &&
2675 !mShuttingDown && !mRemovingAll) {
2676 LOG(("CacheIndex::StartUpdatingIndexIfNeeded() - starting update process"));
2677 mIndexNeedsUpdate = false;
2678 StartUpdatingIndex(false);
2679 return true;
2680 }
2682 return false;
2683 }
2685 void
2686 CacheIndex::StartUpdatingIndex(bool aRebuild)
2687 {
2688 LOG(("CacheIndex::StartUpdatingIndex() [rebuild=%d]", aRebuild));
2690 AssertOwnsLock();
2692 nsresult rv;
2694 mIndexStats.Log();
2696 ChangeState(aRebuild ? BUILDING : UPDATING);
2697 mDontMarkIndexClean = false;
2699 if (mShuttingDown || mRemovingAll) {
2700 FinishUpdate(false);
2701 return;
2702 }
2704 if (IsUpdatePending()) {
2705 LOG(("CacheIndex::StartUpdatingIndex() - Update is already pending"));
2706 return;
2707 }
2709 uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds();
2710 if (elapsed < kUpdateIndexStartDelay) {
2711 LOG(("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
2712 "scheduling timer to fire in %u ms.", elapsed,
2713 kUpdateIndexStartDelay - elapsed));
2714 rv = ScheduleUpdateTimer(kUpdateIndexStartDelay - elapsed);
2715 if (NS_SUCCEEDED(rv)) {
2716 return;
2717 }
2719 LOG(("CacheIndex::StartUpdatingIndex() - ScheduleUpdateTimer() failed. "
2720 "Starting update immediately."));
2721 } else {
2722 LOG(("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
2723 "starting update now.", elapsed));
2724 }
2726 nsRefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
2727 MOZ_ASSERT(ioThread);
2729 // We need to dispatch an event even if we are on IO thread since we need to
2730 // update the index with the correct priority.
2731 mUpdateEventPending = true;
2732 rv = ioThread->Dispatch(this, CacheIOThread::INDEX);
2733 if (NS_FAILED(rv)) {
2734 mUpdateEventPending = false;
2735 NS_WARNING("CacheIndex::StartUpdatingIndex() - Can't dispatch event");
2736 LOG(("CacheIndex::StartUpdatingIndex() - Can't dispatch event" ));
2737 FinishUpdate(false);
2738 }
2739 }
2741 void
2742 CacheIndex::UpdateIndex()
2743 {
2744 LOG(("CacheIndex::UpdateIndex()"));
2746 AssertOwnsLock();
2748 MOZ_ASSERT(mPendingUpdates.Count() == 0);
2750 nsresult rv;
2752 if (!mDirEnumerator) {
2753 {
2754 // Do not do IO under the lock.
2755 CacheIndexAutoUnlock unlock(this);
2756 rv = SetupDirectoryEnumerator();
2757 }
2758 if (mState == SHUTDOWN) {
2759 // The index was shut down while we released the lock. FinishUpdate() was
2760 // already called from Shutdown(), so just simply return here.
2761 return;
2762 }
2764 if (NS_FAILED(rv)) {
2765 FinishUpdate(false);
2766 return;
2767 }
2768 }
2770 while (true) {
2771 if (CacheIOThread::YieldAndRerun()) {
2772 LOG(("CacheIndex::UpdateIndex() - Breaking loop for higher level "
2773 "events."));
2774 mUpdateEventPending = true;
2775 return;
2776 }
2778 nsCOMPtr<nsIFile> file;
2779 {
2780 // Do not do IO under the lock.
2781 CacheIndexAutoUnlock unlock(this);
2782 rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
2783 }
2784 if (mState == SHUTDOWN) {
2785 return;
2786 }
2787 if (!file) {
2788 FinishUpdate(NS_SUCCEEDED(rv));
2789 return;
2790 }
2792 nsAutoCString leaf;
2793 rv = file->GetNativeLeafName(leaf);
2794 if (NS_FAILED(rv)) {
2795 LOG(("CacheIndex::UpdateIndex() - GetNativeLeafName() failed! Skipping "
2796 "file."));
2797 mDontMarkIndexClean = true;
2798 continue;
2799 }
2801 SHA1Sum::Hash hash;
2802 rv = CacheFileIOManager::StrToHash(leaf, &hash);
2803 if (NS_FAILED(rv)) {
2804 LOG(("CacheIndex::UpdateIndex() - Filename is not a hash, removing file. "
2805 "[name=%s]", leaf.get()));
2806 file->Remove(false);
2807 continue;
2808 }
2810 CacheIndexEntry *entry = mIndex.GetEntry(hash);
2811 if (entry && entry->IsRemoved()) {
2812 if (entry->IsFresh()) {
2813 LOG(("CacheIndex::UpdateIndex() - Found file that should not exist. "
2814 "[name=%s]", leaf.get()));
2815 entry->Log();
2816 }
2817 entry = nullptr;
2818 }
2820 #ifdef DEBUG
2821 nsRefPtr<CacheFileHandle> handle;
2822 CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false,
2823 getter_AddRefs(handle));
2824 #endif
2826 if (entry && entry->IsFresh()) {
2827 // the entry is up to date
2828 LOG(("CacheIndex::UpdateIndex() - Skipping file because the entry is up "
2829 " to date. [name=%s]", leaf.get()));
2830 entry->Log();
2831 // there must be an active CacheFile if the entry is not initialized
2832 MOZ_ASSERT(entry->IsInitialized() || handle);
2833 continue;
2834 }
2836 MOZ_ASSERT(!handle);
2838 if (entry) {
2839 PRTime lastModifiedTime;
2840 {
2841 // Do not do IO under the lock.
2842 CacheIndexAutoUnlock unlock(this);
2843 rv = file->GetLastModifiedTime(&lastModifiedTime);
2844 }
2845 if (mState == SHUTDOWN) {
2846 return;
2847 }
2848 if (NS_FAILED(rv)) {
2849 LOG(("CacheIndex::UpdateIndex() - Cannot get lastModifiedTime. "
2850 "[name=%s]", leaf.get()));
2851 // Assume the file is newer than index
2852 } else {
2853 if (mIndexTimeStamp > (lastModifiedTime / PR_MSEC_PER_SEC)) {
2854 LOG(("CacheIndex::UpdateIndex() - Skipping file because of last "
2855 "modified time. [name=%s, indexTimeStamp=%u, "
2856 "lastModifiedTime=%u]", leaf.get(), mIndexTimeStamp,
2857 lastModifiedTime / PR_MSEC_PER_SEC));
2859 CacheIndexEntryAutoManage entryMng(&hash, this);
2860 entry->MarkFresh();
2861 continue;
2862 }
2863 }
2864 }
2866 nsRefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
2867 int64_t size = 0;
2869 {
2870 // Do not do IO under the lock.
2871 CacheIndexAutoUnlock unlock(this);
2872 rv = meta->SyncReadMetadata(file);
2874 if (NS_SUCCEEDED(rv)) {
2875 rv = file->GetFileSize(&size);
2876 if (NS_FAILED(rv)) {
2877 LOG(("CacheIndex::UpdateIndex() - Cannot get filesize of file that "
2878 "was successfully parsed. [name=%s]", leaf.get()));
2879 }
2880 }
2881 }
2882 if (mState == SHUTDOWN) {
2883 return;
2884 }
2886 // Nobody could add the entry while the lock was released since we modify
2887 // the index only on IO thread and this loop is executed on IO thread too.
2888 entry = mIndex.GetEntry(hash);
2889 MOZ_ASSERT(!entry || !entry->IsFresh());
2891 CacheIndexEntryAutoManage entryMng(&hash, this);
2893 if (NS_FAILED(rv)) {
2894 LOG(("CacheIndex::UpdateIndex() - CacheFileMetadata::SyncReadMetadata() "
2895 "failed, removing file. [name=%s]", leaf.get()));
2896 file->Remove(false);
2897 if (entry) {
2898 entry->MarkRemoved();
2899 entry->MarkFresh();
2900 entry->MarkDirty();
2901 }
2902 } else {
2903 entry = mIndex.PutEntry(hash);
2904 InitEntryFromDiskData(entry, meta, size);
2905 LOG(("CacheIndex::UpdateIndex() - Added/updated entry to/in index. "
2906 "[hash=%s]", leaf.get()));
2907 entry->Log();
2908 }
2909 }
2911 NS_NOTREACHED("We should never get here");
2912 }
2914 void
2915 CacheIndex::FinishUpdate(bool aSucceeded)
2916 {
2917 LOG(("CacheIndex::FinishUpdate() [succeeded=%d]", aSucceeded));
2919 MOZ_ASSERT(mState == UPDATING || mState == BUILDING ||
2920 (!aSucceeded && mState == SHUTDOWN));
2922 AssertOwnsLock();
2924 if (mDirEnumerator) {
2925 if (NS_IsMainThread()) {
2926 LOG(("CacheIndex::FinishUpdate() - posting of PreShutdownInternal failed?"
2927 " Cannot safely release mDirEnumerator, leaking it!"));
2928 NS_WARNING(("CacheIndex::FinishUpdate() - Leaking mDirEnumerator!"));
2929 // This can happen only in case dispatching event to IO thread failed in
2930 // CacheIndex::PreShutdown().
2931 mDirEnumerator.forget(); // Leak it since dir enumerator is not threadsafe
2932 } else {
2933 mDirEnumerator->Close();
2934 mDirEnumerator = nullptr;
2935 }
2936 }
2938 if (!aSucceeded) {
2939 mDontMarkIndexClean = true;
2940 }
2942 if (mState == SHUTDOWN) {
2943 return;
2944 }
2946 if (mState == UPDATING && aSucceeded) {
2947 // If we've iterated over all entries successfully then all entries that
2948 // really exist on the disk are now marked as fresh. All non-fresh entries
2949 // don't exist anymore and must be removed from the index.
2950 mIndex.EnumerateEntries(&CacheIndex::RemoveNonFreshEntries, this);
2951 }
2953 // Make sure we won't start update. If the build or update failed, there is no
2954 // reason to believe that it will succeed next time.
2955 mIndexNeedsUpdate = false;
2957 ChangeState(READY);
2958 mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
2959 }
2961 // static
2962 PLDHashOperator
2963 CacheIndex::RemoveNonFreshEntries(CacheIndexEntry *aEntry, void* aClosure)
2964 {
2965 if (aEntry->IsFresh()) {
2966 return PL_DHASH_NEXT;
2967 }
2969 LOG(("CacheFile::RemoveNonFreshEntries() - Removing entry. "
2970 "[hash=%08x%08x%08x%08x%08x]", LOGSHA1(aEntry->Hash())));
2972 CacheIndex *index = static_cast<CacheIndex *>(aClosure);
2974 CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
2975 emng.DoNotSearchInIndex();
2977 return PL_DHASH_REMOVE;
2978 }
2980 #ifdef PR_LOGGING
2981 // static
2982 char const *
2983 CacheIndex::StateString(EState aState)
2984 {
2985 switch (aState) {
2986 case INITIAL: return "INITIAL";
2987 case READING: return "READING";
2988 case WRITING: return "WRITING";
2989 case BUILDING: return "BUILDING";
2990 case UPDATING: return "UPDATING";
2991 case READY: return "READY";
2992 case SHUTDOWN: return "SHUTDOWN";
2993 }
2995 MOZ_ASSERT(false, "Unexpected state!");
2996 return "?";
2997 }
2998 #endif
3000 void
3001 CacheIndex::ChangeState(EState aNewState)
3002 {
3003 LOG(("CacheIndex::ChangeState() changing state %s -> %s", StateString(mState),
3004 StateString(aNewState)));
3006 // All pending updates should be processed before changing state
3007 MOZ_ASSERT(mPendingUpdates.Count() == 0);
3009 // PreShutdownInternal() should change the state to READY from every state. It
3010 // may go through different states, but once we are in READY state the only
3011 // possible transition is to SHUTDOWN state.
3012 MOZ_ASSERT(!mShuttingDown || mState != READY || aNewState == SHUTDOWN);
3014 // Start updating process when switching to READY state if needed
3015 if (aNewState == READY && StartUpdatingIndexIfNeeded(true)) {
3016 return;
3017 }
3019 // Try to evict entries over limit everytime we're leaving state READING,
3020 // BUILDING or UPDATING, but not during shutdown or when removing all
3021 // entries.
3022 if (!mShuttingDown && !mRemovingAll && aNewState != SHUTDOWN &&
3023 (mState == READING || mState == BUILDING || mState == UPDATING)) {
3024 CacheFileIOManager::EvictIfOverLimit();
3025 }
3027 mState = aNewState;
3029 if (mState != SHUTDOWN) {
3030 CacheFileIOManager::CacheIndexStateChanged();
3031 }
3033 if (mState == READY && mDiskConsumptionObservers.Length()) {
3034 for (uint32_t i = 0; i < mDiskConsumptionObservers.Length(); ++i) {
3035 DiskConsumptionObserver* o = mDiskConsumptionObservers[i];
3036 // Safe to call under the lock. We always post to the main thread.
3037 o->OnDiskConsumption(mIndexStats.Size() << 10);
3038 }
3040 mDiskConsumptionObservers.Clear();
3041 }
3042 }
3044 void
3045 CacheIndex::AllocBuffer()
3046 {
3047 switch (mState) {
3048 case WRITING:
3049 mRWBufSize = sizeof(CacheIndexHeader) + sizeof(CacheHash::Hash32_t) +
3050 mProcessEntries * sizeof(CacheIndexRecord);
3051 if (mRWBufSize > kMaxBufSize) {
3052 mRWBufSize = kMaxBufSize;
3053 }
3054 break;
3055 case READING:
3056 mRWBufSize = kMaxBufSize;
3057 break;
3058 default:
3059 MOZ_ASSERT(false, "Unexpected state!");
3060 }
3062 mRWBuf = static_cast<char *>(moz_xmalloc(mRWBufSize));
3063 }
3065 void
3066 CacheIndex::ReleaseBuffer()
3067 {
3068 if (!mRWBuf) {
3069 return;
3070 }
3072 free(mRWBuf);
3073 mRWBuf = nullptr;
3074 mRWBufSize = 0;
3075 mRWBufPos = 0;
3076 }
3078 namespace { // anon
3080 class FrecencyComparator
3081 {
3082 public:
3083 bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const {
3084 return a->mFrecency == b->mFrecency;
3085 }
3086 bool LessThan(CacheIndexRecord* a, CacheIndexRecord* b) const {
3087 // Place entries with frecency 0 at the end of the array.
3088 if (a->mFrecency == 0) {
3089 return false;
3090 }
3091 if (b->mFrecency == 0) {
3092 return true;
3093 }
3094 return a->mFrecency < b->mFrecency;
3095 }
3096 };
3098 class ExpirationComparator
3099 {
3100 public:
3101 bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const {
3102 return a->mExpirationTime == b->mExpirationTime;
3103 }
3104 bool LessThan(CacheIndexRecord* a, CacheIndexRecord* b) const {
3105 return a->mExpirationTime < b->mExpirationTime;
3106 }
3107 };
3109 } // anon
3111 void
3112 CacheIndex::InsertRecordToFrecencyArray(CacheIndexRecord *aRecord)
3113 {
3114 LOG(("CacheIndex::InsertRecordToFrecencyArray() [record=%p, hash=%08x%08x%08x"
3115 "%08x%08x]", aRecord, LOGSHA1(aRecord->mHash)));
3117 MOZ_ASSERT(!mFrecencyArray.Contains(aRecord));
3118 mFrecencyArray.InsertElementSorted(aRecord, FrecencyComparator());
3119 }
3121 void
3122 CacheIndex::InsertRecordToExpirationArray(CacheIndexRecord *aRecord)
3123 {
3124 LOG(("CacheIndex::InsertRecordToExpirationArray() [record=%p, hash=%08x%08x"
3125 "%08x%08x%08x]", aRecord, LOGSHA1(aRecord->mHash)));
3127 MOZ_ASSERT(!mExpirationArray.Contains(aRecord));
3128 mExpirationArray.InsertElementSorted(aRecord, ExpirationComparator());
3129 }
3131 void
3132 CacheIndex::RemoveRecordFromFrecencyArray(CacheIndexRecord *aRecord)
3133 {
3134 LOG(("CacheIndex::RemoveRecordFromFrecencyArray() [record=%p]", aRecord));
3136 DebugOnly<bool> removed;
3137 removed = mFrecencyArray.RemoveElement(aRecord);
3138 MOZ_ASSERT(removed);
3139 }
3141 void
3142 CacheIndex::RemoveRecordFromExpirationArray(CacheIndexRecord *aRecord)
3143 {
3144 LOG(("CacheIndex::RemoveRecordFromExpirationArray() [record=%p]", aRecord));
3146 DebugOnly<bool> removed;
3147 removed = mExpirationArray.RemoveElement(aRecord);
3148 MOZ_ASSERT(removed);
3149 }
3151 void
3152 CacheIndex::AddRecordToIterators(CacheIndexRecord *aRecord)
3153 {
3154 AssertOwnsLock();
3156 for (uint32_t i = 0; i < mIterators.Length(); ++i) {
3157 // Add a new record only when iterator is supposed to be updated.
3158 if (mIterators[i]->ShouldBeNewAdded()) {
3159 mIterators[i]->AddRecord(aRecord);
3160 }
3161 }
3162 }
3164 void
3165 CacheIndex::RemoveRecordFromIterators(CacheIndexRecord *aRecord)
3166 {
3167 AssertOwnsLock();
3169 for (uint32_t i = 0; i < mIterators.Length(); ++i) {
3170 // Remove the record from iterator always, it makes no sence to return
3171 // non-existing entries. Also the pointer to the record is no longer valid
3172 // once the entry is removed from index.
3173 mIterators[i]->RemoveRecord(aRecord);
3174 }
3175 }
3177 void
3178 CacheIndex::ReplaceRecordInIterators(CacheIndexRecord *aOldRecord,
3179 CacheIndexRecord *aNewRecord)
3180 {
3181 AssertOwnsLock();
3183 for (uint32_t i = 0; i < mIterators.Length(); ++i) {
3184 // We have to replace the record always since the pointer is no longer
3185 // valid after this point. NOTE: Replacing the record doesn't mean that
3186 // a new entry was added, it just means that the data in the entry was
3187 // changed (e.g. a file size) and we had to track this change in
3188 // mPendingUpdates since mIndex was read-only.
3189 mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord);
3190 }
3191 }
3193 nsresult
3194 CacheIndex::Run()
3195 {
3196 LOG(("CacheIndex::Run()"));
3198 CacheIndexAutoLock lock(this);
3200 if (!IsIndexUsable()) {
3201 return NS_ERROR_NOT_AVAILABLE;
3202 }
3204 if (mState == READY && mShuttingDown) {
3205 return NS_OK;
3206 }
3208 mUpdateEventPending = false;
3210 switch (mState) {
3211 case BUILDING:
3212 BuildIndex();
3213 break;
3214 case UPDATING:
3215 UpdateIndex();
3216 break;
3217 default:
3218 LOG(("CacheIndex::Run() - Update/Build was canceled"));
3219 }
3221 return NS_OK;
3222 }
3224 nsresult
3225 CacheIndex::OnFileOpenedInternal(FileOpenHelper *aOpener,
3226 CacheFileHandle *aHandle, nsresult aResult)
3227 {
3228 LOG(("CacheIndex::OnFileOpenedInternal() [opener=%p, handle=%p, "
3229 "result=0x%08x]", aOpener, aHandle, aResult));
3231 nsresult rv;
3233 AssertOwnsLock();
3235 if (!IsIndexUsable()) {
3236 return NS_ERROR_NOT_AVAILABLE;
3237 }
3239 if (mState == READY && mShuttingDown) {
3240 return NS_OK;
3241 }
3243 switch (mState) {
3244 case WRITING:
3245 MOZ_ASSERT(aOpener == mIndexFileOpener);
3246 mIndexFileOpener = nullptr;
3248 if (NS_FAILED(aResult)) {
3249 LOG(("CacheIndex::OnFileOpenedInternal() - Can't open index file for "
3250 "writing [rv=0x%08x]", aResult));
3251 FinishWrite(false);
3252 } else {
3253 mIndexHandle = aHandle;
3254 WriteRecords();
3255 }
3256 break;
3257 case READING:
3258 if (aOpener == mIndexFileOpener) {
3259 mIndexFileOpener = nullptr;
3261 if (NS_SUCCEEDED(aResult)) {
3262 if (aHandle->FileSize() == 0) {
3263 FinishRead(false);
3264 CacheFileIOManager::DoomFile(aHandle, nullptr);
3265 break;
3266 } else {
3267 mIndexHandle = aHandle;
3268 }
3269 } else {
3270 FinishRead(false);
3271 break;
3272 }
3273 } else if (aOpener == mJournalFileOpener) {
3274 mJournalFileOpener = nullptr;
3275 mJournalHandle = aHandle;
3276 } else if (aOpener == mTmpFileOpener) {
3277 mTmpFileOpener = nullptr;
3278 mTmpHandle = aHandle;
3279 } else {
3280 MOZ_ASSERT(false, "Unexpected state!");
3281 }
3283 if (mIndexFileOpener || mJournalFileOpener || mTmpFileOpener) {
3284 // Some opener still didn't finish
3285 break;
3286 }
3288 // We fail and cancel all other openers when we opening index file fails.
3289 MOZ_ASSERT(mIndexHandle);
3291 if (mTmpHandle) {
3292 CacheFileIOManager::DoomFile(mTmpHandle, nullptr);
3293 mTmpHandle = nullptr;
3295 if (mJournalHandle) { // this shouldn't normally happen
3296 LOG(("CacheIndex::OnFileOpenedInternal() - Unexpected state, all "
3297 "files [%s, %s, %s] should never exist. Removing whole index.",
3298 kIndexName, kJournalName, kTempIndexName));
3299 FinishRead(false);
3300 break;
3301 }
3302 }
3304 if (mJournalHandle) {
3305 // Rename journal to make sure we update index on next start in case
3306 // firefox crashes
3307 rv = CacheFileIOManager::RenameFile(
3308 mJournalHandle, NS_LITERAL_CSTRING(kTempIndexName), this);
3309 if (NS_FAILED(rv)) {
3310 LOG(("CacheIndex::OnFileOpenedInternal() - CacheFileIOManager::"
3311 "RenameFile() failed synchronously [rv=0x%08x]", rv));
3312 FinishRead(false);
3313 break;
3314 }
3315 } else {
3316 StartReadingIndex();
3317 }
3319 break;
3320 default:
3321 MOZ_ASSERT(false, "Unexpected state!");
3322 }
3324 return NS_OK;
3325 }
3327 nsresult
3328 CacheIndex::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
3329 {
3330 MOZ_CRASH("CacheIndex::OnFileOpened should not be called!");
3331 return NS_ERROR_UNEXPECTED;
3332 }
3334 nsresult
3335 CacheIndex::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
3336 nsresult aResult)
3337 {
3338 LOG(("CacheIndex::OnDataWritten() [handle=%p, result=0x%08x]", aHandle,
3339 aResult));
3341 nsresult rv;
3343 CacheIndexAutoLock lock(this);
3345 if (!IsIndexUsable()) {
3346 return NS_ERROR_NOT_AVAILABLE;
3347 }
3349 if (mState == READY && mShuttingDown) {
3350 return NS_OK;
3351 }
3353 switch (mState) {
3354 case WRITING:
3355 if (mIndexHandle != aHandle) {
3356 LOG(("CacheIndex::OnDataWritten() - ignoring notification since it "
3357 "belongs to previously canceled operation [state=%d]", mState));
3358 break;
3359 }
3361 if (NS_FAILED(aResult)) {
3362 FinishWrite(false);
3363 } else {
3364 if (mSkipEntries == mProcessEntries) {
3365 rv = CacheFileIOManager::RenameFile(mIndexHandle,
3366 NS_LITERAL_CSTRING(kIndexName),
3367 this);
3368 if (NS_FAILED(rv)) {
3369 LOG(("CacheIndex::OnDataWritten() - CacheFileIOManager::"
3370 "RenameFile() failed synchronously [rv=0x%08x]", rv));
3371 FinishWrite(false);
3372 }
3373 } else {
3374 WriteRecords();
3375 }
3376 }
3377 break;
3378 default:
3379 // Writing was canceled.
3380 LOG(("CacheIndex::OnDataWritten() - ignoring notification since the "
3381 "operation was previously canceled [state=%d]", mState));
3382 }
3384 return NS_OK;
3385 }
3387 nsresult
3388 CacheIndex::OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult)
3389 {
3390 LOG(("CacheIndex::OnDataRead() [handle=%p, result=0x%08x]", aHandle,
3391 aResult));
3393 CacheIndexAutoLock lock(this);
3395 if (!IsIndexUsable()) {
3396 return NS_ERROR_NOT_AVAILABLE;
3397 }
3399 switch (mState) {
3400 case READING:
3401 MOZ_ASSERT(mIndexHandle == aHandle || mJournalHandle == aHandle);
3403 if (NS_FAILED(aResult)) {
3404 FinishRead(false);
3405 } else {
3406 if (!mIndexOnDiskIsValid) {
3407 ParseRecords();
3408 } else {
3409 ParseJournal();
3410 }
3411 }
3412 break;
3413 default:
3414 // Reading was canceled.
3415 LOG(("CacheIndex::OnDataRead() - ignoring notification since the "
3416 "operation was previously canceled [state=%d]", mState));
3417 }
3419 return NS_OK;
3420 }
3422 nsresult
3423 CacheIndex::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
3424 {
3425 MOZ_CRASH("CacheIndex::OnFileDoomed should not be called!");
3426 return NS_ERROR_UNEXPECTED;
3427 }
3429 nsresult
3430 CacheIndex::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
3431 {
3432 MOZ_CRASH("CacheIndex::OnEOFSet should not be called!");
3433 return NS_ERROR_UNEXPECTED;
3434 }
3436 nsresult
3437 CacheIndex::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult)
3438 {
3439 LOG(("CacheIndex::OnFileRenamed() [handle=%p, result=0x%08x]", aHandle,
3440 aResult));
3442 CacheIndexAutoLock lock(this);
3444 if (!IsIndexUsable()) {
3445 return NS_ERROR_NOT_AVAILABLE;
3446 }
3448 if (mState == READY && mShuttingDown) {
3449 return NS_OK;
3450 }
3452 switch (mState) {
3453 case WRITING:
3454 // This is a result of renaming the new index written to tmpfile to index
3455 // file. This is the last step when writing the index and the whole
3456 // writing process is successful iff renaming was successful.
3458 if (mIndexHandle != aHandle) {
3459 LOG(("CacheIndex::OnFileRenamed() - ignoring notification since it "
3460 "belongs to previously canceled operation [state=%d]", mState));
3461 break;
3462 }
3464 FinishWrite(NS_SUCCEEDED(aResult));
3465 break;
3466 case READING:
3467 // This is a result of renaming journal file to tmpfile. It is renamed
3468 // before we start reading index and journal file and it should normally
3469 // succeed. If it fails give up reading of index.
3471 if (mJournalHandle != aHandle) {
3472 LOG(("CacheIndex::OnFileRenamed() - ignoring notification since it "
3473 "belongs to previously canceled operation [state=%d]", mState));
3474 break;
3475 }
3477 if (NS_FAILED(aResult)) {
3478 FinishRead(false);
3479 } else {
3480 StartReadingIndex();
3481 }
3482 break;
3483 default:
3484 // Reading/writing was canceled.
3485 LOG(("CacheIndex::OnFileRenamed() - ignoring notification since the "
3486 "operation was previously canceled [state=%d]", mState));
3487 }
3489 return NS_OK;
3490 }
3492 // Memory reporting
3494 namespace { // anon
3496 size_t
3497 CollectIndexEntryMemory(CacheIndexEntry* aEntry,
3498 mozilla::MallocSizeOf mallocSizeOf,
3499 void *arg)
3500 {
3501 return aEntry->SizeOfExcludingThis(mallocSizeOf);
3502 }
3504 } // anon
3506 size_t
3507 CacheIndex::SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const
3508 {
3509 CacheIndexAutoLock lock(const_cast<CacheIndex*>(this));
3511 size_t n = 0;
3512 nsCOMPtr<nsISizeOf> sizeOf;
3514 // mIndexHandle and mJournalHandle are reported via SizeOfHandlesRunnable
3515 // in CacheFileIOManager::SizeOfExcludingThisInternal as part of special
3516 // handles array.
3518 sizeOf = do_QueryInterface(mCacheDirectory);
3519 if (sizeOf) {
3520 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3521 }
3523 sizeOf = do_QueryInterface(mUpdateTimer);
3524 if (sizeOf) {
3525 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3526 }
3528 n += mallocSizeOf(mRWBuf);
3529 n += mallocSizeOf(mRWHash);
3531 n += mIndex.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf);
3532 n += mPendingUpdates.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf);
3533 n += mTmpJournal.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf);
3535 // mFrecencyArray and mExpirationArray items are reported by
3536 // mIndex/mPendingUpdates
3537 n += mFrecencyArray.SizeOfExcludingThis(mallocSizeOf);
3538 n += mExpirationArray.SizeOfExcludingThis(mallocSizeOf);
3539 n += mDiskConsumptionObservers.SizeOfExcludingThis(mallocSizeOf);
3541 return n;
3542 }
3544 // static
3545 size_t
3546 CacheIndex::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
3547 {
3548 if (!gInstance)
3549 return 0;
3551 return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
3552 }
3554 // static
3555 size_t
3556 CacheIndex::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
3557 {
3558 return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf);
3559 }
3561 } // net
3562 } // mozilla