Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / netwerk / cache2 / CacheIndex.cpp
blob34931087c071a3f197c2901b60b40d47a1f42ebf
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 "CacheFileUtils.h"
11 #include "CacheIndexIterator.h"
12 #include "CacheIndexContextIterator.h"
13 #include "nsThreadUtils.h"
14 #include "nsISizeOf.h"
15 #include "nsPrintfCString.h"
16 #include "mozilla/DebugOnly.h"
17 #include "prinrval.h"
18 #include "nsIFile.h"
19 #include "nsITimer.h"
20 #include "mozilla/AutoRestore.h"
21 #include <algorithm>
22 #include "mozilla/StaticPrefs_network.h"
23 #include "mozilla/Telemetry.h"
24 #include "mozilla/Unused.h"
26 #define kMinUnwrittenChanges 300
27 #define kMinDumpInterval 20000 // in milliseconds
28 #define kMaxBufSize 16384
29 #define kIndexVersion 0x0000000A
30 #define kUpdateIndexStartDelay 50000 // in milliseconds
31 #define kTelemetryReportBytesLimit (2U * 1024U * 1024U * 1024U) // 2GB
33 #define INDEX_NAME "index"
34 #define TEMP_INDEX_NAME "index.tmp"
35 #define JOURNAL_NAME "index.log"
37 namespace mozilla::net {
39 namespace {
41 class FrecencyComparator {
42 public:
43 bool Equals(const RefPtr<CacheIndexRecordWrapper>& a,
44 const RefPtr<CacheIndexRecordWrapper>& b) const {
45 if (!a || !b) {
46 return false;
49 return a->Get()->mFrecency == b->Get()->mFrecency;
51 bool LessThan(const RefPtr<CacheIndexRecordWrapper>& a,
52 const RefPtr<CacheIndexRecordWrapper>& b) const {
53 // Removed (=null) entries must be at the end of the array.
54 if (!a) {
55 return false;
57 if (!b) {
58 return true;
61 // Place entries with frecency 0 at the end of the non-removed entries.
62 if (a->Get()->mFrecency == 0) {
63 return false;
65 if (b->Get()->mFrecency == 0) {
66 return true;
69 return a->Get()->mFrecency < b->Get()->mFrecency;
73 } // namespace
75 // used to dispatch a wrapper deletion the caller's thread
76 // cannot be used on IOThread after shutdown begins
77 class DeleteCacheIndexRecordWrapper : public Runnable {
78 CacheIndexRecordWrapper* mWrapper;
80 public:
81 explicit DeleteCacheIndexRecordWrapper(CacheIndexRecordWrapper* wrapper)
82 : Runnable("net::CacheIndex::DeleteCacheIndexRecordWrapper"),
83 mWrapper(wrapper) {}
84 NS_IMETHOD Run() override {
85 StaticMutexAutoLock lock(CacheIndex::sLock);
87 // if somehow the item is still in the frecency array, remove it
88 RefPtr<CacheIndex> index = CacheIndex::gInstance;
89 if (index) {
90 bool found = index->mFrecencyArray.RecordExistedUnlocked(mWrapper);
91 if (found) {
92 LOG(
93 ("DeleteCacheIndexRecordWrapper::Run() - \
94 record wrapper found in frecency array during deletion"));
95 index->mFrecencyArray.RemoveRecord(mWrapper, lock);
99 delete mWrapper;
100 return NS_OK;
104 void CacheIndexRecordWrapper::DispatchDeleteSelfToCurrentThread() {
105 // Dispatch during shutdown will not trigger DeleteCacheIndexRecordWrapper
106 nsCOMPtr<nsIRunnable> event = new DeleteCacheIndexRecordWrapper(this);
107 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(event));
110 CacheIndexRecordWrapper::~CacheIndexRecordWrapper() {
111 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
112 CacheIndex::sLock.AssertCurrentThreadOwns();
113 RefPtr<CacheIndex> index = CacheIndex::gInstance;
114 if (index) {
115 bool found = index->mFrecencyArray.RecordExistedUnlocked(this);
116 MOZ_DIAGNOSTIC_ASSERT(!found);
118 #endif
122 * This helper class is responsible for keeping CacheIndex::mIndexStats and
123 * CacheIndex::mFrecencyArray up to date.
125 class MOZ_RAII CacheIndexEntryAutoManage {
126 public:
127 CacheIndexEntryAutoManage(const SHA1Sum::Hash* aHash, CacheIndex* aIndex,
128 const StaticMutexAutoLock& aProofOfLock)
129 MOZ_REQUIRES(CacheIndex::sLock)
130 : mIndex(aIndex), mProofOfLock(aProofOfLock) {
131 mHash = aHash;
132 const CacheIndexEntry* entry = FindEntry();
133 mIndex->mIndexStats.BeforeChange(entry);
134 if (entry && entry->IsInitialized() && !entry->IsRemoved()) {
135 mOldRecord = entry->mRec;
136 mOldFrecency = entry->mRec->Get()->mFrecency;
140 ~CacheIndexEntryAutoManage() MOZ_REQUIRES(CacheIndex::sLock) {
141 const CacheIndexEntry* entry = FindEntry();
142 mIndex->mIndexStats.AfterChange(entry);
143 if (!entry || !entry->IsInitialized() || entry->IsRemoved()) {
144 entry = nullptr;
147 if (entry && !mOldRecord) {
148 mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
149 mIndex->AddRecordToIterators(entry->mRec, mProofOfLock);
150 } else if (!entry && mOldRecord) {
151 mIndex->mFrecencyArray.RemoveRecord(mOldRecord, mProofOfLock);
152 mIndex->RemoveRecordFromIterators(mOldRecord, mProofOfLock);
153 } else if (entry && mOldRecord) {
154 if (entry->mRec != mOldRecord) {
155 // record has a different address, we have to replace it
156 mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec, mProofOfLock);
158 if (entry->mRec->Get()->mFrecency == mOldFrecency) {
159 // If frecency hasn't changed simply replace the pointer
160 mIndex->mFrecencyArray.ReplaceRecord(mOldRecord, entry->mRec,
161 mProofOfLock);
162 } else {
163 // Remove old pointer and insert the new one at the end of the array
164 mIndex->mFrecencyArray.RemoveRecord(mOldRecord, mProofOfLock);
165 mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
167 } else if (entry->mRec->Get()->mFrecency != mOldFrecency) {
168 // Move the element at the end of the array
169 mIndex->mFrecencyArray.RemoveRecord(entry->mRec, mProofOfLock);
170 mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
172 } else {
173 // both entries were removed or not initialized, do nothing
177 // We cannot rely on nsTHashtable::GetEntry() in case we are removing entries
178 // while iterating. Destructor is called before the entry is removed. Caller
179 // must call one of following methods to skip lookup in the hashtable.
180 void DoNotSearchInIndex() { mDoNotSearchInIndex = true; }
181 void DoNotSearchInUpdates() { mDoNotSearchInUpdates = true; }
183 private:
184 const CacheIndexEntry* FindEntry() MOZ_REQUIRES(CacheIndex::sLock) {
185 const CacheIndexEntry* entry = nullptr;
187 switch (mIndex->mState) {
188 case CacheIndex::READING:
189 case CacheIndex::WRITING:
190 if (!mDoNotSearchInUpdates) {
191 entry = mIndex->mPendingUpdates.GetEntry(*mHash);
193 [[fallthrough]];
194 case CacheIndex::BUILDING:
195 case CacheIndex::UPDATING:
196 case CacheIndex::READY:
197 if (!entry && !mDoNotSearchInIndex) {
198 entry = mIndex->mIndex.GetEntry(*mHash);
200 break;
201 case CacheIndex::INITIAL:
202 case CacheIndex::SHUTDOWN:
203 default:
204 MOZ_ASSERT(false, "Unexpected state!");
207 return entry;
210 const SHA1Sum::Hash* mHash;
211 RefPtr<CacheIndex> mIndex;
212 RefPtr<CacheIndexRecordWrapper> mOldRecord;
213 uint32_t mOldFrecency{0};
214 bool mDoNotSearchInIndex{false};
215 bool mDoNotSearchInUpdates{false};
216 const StaticMutexAutoLock& mProofOfLock;
219 class FileOpenHelper final : public CacheFileIOListener {
220 public:
221 NS_DECL_THREADSAFE_ISUPPORTS
223 explicit FileOpenHelper(CacheIndex* aIndex)
224 : mIndex(aIndex), mCanceled(false) {}
226 void Cancel() {
227 CacheIndex::sLock.AssertCurrentThreadOwns();
228 mCanceled = true;
231 private:
232 virtual ~FileOpenHelper() = default;
234 NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override;
235 NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
236 nsresult aResult) override {
237 MOZ_CRASH("FileOpenHelper::OnDataWritten should not be called!");
238 return NS_ERROR_UNEXPECTED;
240 NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
241 nsresult aResult) override {
242 MOZ_CRASH("FileOpenHelper::OnDataRead should not be called!");
243 return NS_ERROR_UNEXPECTED;
245 NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override {
246 MOZ_CRASH("FileOpenHelper::OnFileDoomed should not be called!");
247 return NS_ERROR_UNEXPECTED;
249 NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override {
250 MOZ_CRASH("FileOpenHelper::OnEOFSet should not be called!");
251 return NS_ERROR_UNEXPECTED;
253 NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle,
254 nsresult aResult) override {
255 MOZ_CRASH("FileOpenHelper::OnFileRenamed should not be called!");
256 return NS_ERROR_UNEXPECTED;
259 RefPtr<CacheIndex> mIndex;
260 bool mCanceled;
263 NS_IMETHODIMP FileOpenHelper::OnFileOpened(CacheFileHandle* aHandle,
264 nsresult aResult) {
265 StaticMutexAutoLock lock(CacheIndex::sLock);
267 if (mCanceled) {
268 if (aHandle) {
269 CacheFileIOManager::DoomFile(aHandle, nullptr);
272 return NS_OK;
275 mIndex->OnFileOpenedInternal(this, aHandle, aResult, lock);
277 return NS_OK;
280 NS_IMPL_ISUPPORTS(FileOpenHelper, CacheFileIOListener);
282 StaticRefPtr<CacheIndex> CacheIndex::gInstance;
283 StaticMutex CacheIndex::sLock;
285 NS_IMPL_ADDREF(CacheIndex)
286 NS_IMPL_RELEASE(CacheIndex)
288 NS_INTERFACE_MAP_BEGIN(CacheIndex)
289 NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
290 NS_INTERFACE_MAP_ENTRY(nsIRunnable)
291 NS_INTERFACE_MAP_END
293 CacheIndex::CacheIndex() {
294 sLock.AssertCurrentThreadOwns();
295 LOG(("CacheIndex::CacheIndex [this=%p]", this));
296 MOZ_ASSERT(!gInstance, "multiple CacheIndex instances!");
299 CacheIndex::~CacheIndex() {
300 sLock.AssertCurrentThreadOwns();
301 LOG(("CacheIndex::~CacheIndex [this=%p]", this));
303 ReleaseBuffer();
306 // static
307 nsresult CacheIndex::Init(nsIFile* aCacheDirectory) {
308 LOG(("CacheIndex::Init()"));
310 MOZ_ASSERT(NS_IsMainThread());
312 StaticMutexAutoLock lock(sLock);
314 if (gInstance) {
315 return NS_ERROR_ALREADY_INITIALIZED;
318 RefPtr<CacheIndex> idx = new CacheIndex();
320 nsresult rv = idx->InitInternal(aCacheDirectory, lock);
321 NS_ENSURE_SUCCESS(rv, rv);
323 gInstance = std::move(idx);
324 return NS_OK;
327 nsresult CacheIndex::InitInternal(nsIFile* aCacheDirectory,
328 const StaticMutexAutoLock& aProofOfLock) {
329 nsresult rv;
330 sLock.AssertCurrentThreadOwns();
332 rv = aCacheDirectory->Clone(getter_AddRefs(mCacheDirectory));
333 NS_ENSURE_SUCCESS(rv, rv);
335 mStartTime = TimeStamp::NowLoRes();
337 ReadIndexFromDisk(aProofOfLock);
339 return NS_OK;
342 // static
343 nsresult CacheIndex::PreShutdown() {
344 MOZ_ASSERT(NS_IsMainThread());
346 StaticMutexAutoLock lock(sLock);
348 LOG(("CacheIndex::PreShutdown() [gInstance=%p]", gInstance.get()));
350 nsresult rv;
351 RefPtr<CacheIndex> index = gInstance;
353 if (!index) {
354 return NS_ERROR_NOT_INITIALIZED;
357 LOG(
358 ("CacheIndex::PreShutdown() - [state=%d, indexOnDiskIsValid=%d, "
359 "dontMarkIndexClean=%d]",
360 index->mState, index->mIndexOnDiskIsValid, index->mDontMarkIndexClean));
362 LOG(("CacheIndex::PreShutdown() - Closing iterators."));
363 for (uint32_t i = 0; i < index->mIterators.Length();) {
364 rv = index->mIterators[i]->CloseInternal(NS_ERROR_FAILURE);
365 if (NS_FAILED(rv)) {
366 // CacheIndexIterator::CloseInternal() removes itself from mIteratos iff
367 // it returns success.
368 LOG(
369 ("CacheIndex::PreShutdown() - Failed to remove iterator %p. "
370 "[rv=0x%08" PRIx32 "]",
371 index->mIterators[i], static_cast<uint32_t>(rv)));
372 i++;
376 index->mShuttingDown = true;
378 if (index->mState == READY) {
379 return NS_OK; // nothing to do
382 nsCOMPtr<nsIRunnable> event;
383 event = NewRunnableMethod("net::CacheIndex::PreShutdownInternal", index,
384 &CacheIndex::PreShutdownInternal);
386 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
387 MOZ_ASSERT(ioTarget);
389 // PreShutdownInternal() will be executed before any queued event on INDEX
390 // level. That's OK since we don't want to wait for any operation in progess.
391 rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
392 if (NS_FAILED(rv)) {
393 NS_WARNING("CacheIndex::PreShutdown() - Can't dispatch event");
394 LOG(("CacheIndex::PreShutdown() - Can't dispatch event"));
395 return rv;
398 return NS_OK;
401 void CacheIndex::PreShutdownInternal() {
402 StaticMutexAutoLock lock(sLock);
404 LOG(
405 ("CacheIndex::PreShutdownInternal() - [state=%d, indexOnDiskIsValid=%d, "
406 "dontMarkIndexClean=%d]",
407 mState, mIndexOnDiskIsValid, mDontMarkIndexClean));
409 MOZ_ASSERT(mShuttingDown);
411 if (mUpdateTimer) {
412 mUpdateTimer->Cancel();
413 mUpdateTimer = nullptr;
416 switch (mState) {
417 case WRITING:
418 FinishWrite(false, lock);
419 break;
420 case READY:
421 // nothing to do, write the journal in Shutdown()
422 break;
423 case READING:
424 FinishRead(false, lock);
425 break;
426 case BUILDING:
427 case UPDATING:
428 FinishUpdate(false, lock);
429 break;
430 default:
431 MOZ_ASSERT(false, "Implement me!");
434 // We should end up in READY state
435 MOZ_ASSERT(mState == READY);
438 // static
439 nsresult CacheIndex::Shutdown() {
440 MOZ_ASSERT(NS_IsMainThread());
442 StaticMutexAutoLock lock(sLock);
444 LOG(("CacheIndex::Shutdown() [gInstance=%p]", gInstance.get()));
446 RefPtr<CacheIndex> index = gInstance.forget();
448 if (!index) {
449 return NS_ERROR_NOT_INITIALIZED;
452 bool sanitize = CacheObserver::ClearCacheOnShutdown();
454 LOG(
455 ("CacheIndex::Shutdown() - [state=%d, indexOnDiskIsValid=%d, "
456 "dontMarkIndexClean=%d, sanitize=%d]",
457 index->mState, index->mIndexOnDiskIsValid, index->mDontMarkIndexClean,
458 sanitize));
460 MOZ_ASSERT(index->mShuttingDown);
462 EState oldState = index->mState;
463 index->ChangeState(SHUTDOWN, lock);
465 if (oldState != READY) {
466 LOG(
467 ("CacheIndex::Shutdown() - Unexpected state. Did posting of "
468 "PreShutdownInternal() fail?"));
471 switch (oldState) {
472 case WRITING:
473 index->FinishWrite(false, lock);
474 [[fallthrough]];
475 case READY:
476 if (index->mIndexOnDiskIsValid && !index->mDontMarkIndexClean) {
477 if (!sanitize && NS_FAILED(index->WriteLogToDisk())) {
478 index->RemoveJournalAndTempFile();
480 } else {
481 index->RemoveJournalAndTempFile();
483 break;
484 case READING:
485 index->FinishRead(false, lock);
486 break;
487 case BUILDING:
488 case UPDATING:
489 index->FinishUpdate(false, lock);
490 break;
491 default:
492 MOZ_ASSERT(false, "Unexpected state!");
495 if (sanitize) {
496 index->RemoveAllIndexFiles();
499 return NS_OK;
502 // static
503 nsresult CacheIndex::AddEntry(const SHA1Sum::Hash* aHash) {
504 LOG(("CacheIndex::AddEntry() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash)));
506 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
508 StaticMutexAutoLock lock(sLock);
510 RefPtr<CacheIndex> index = gInstance;
512 if (!index) {
513 return NS_ERROR_NOT_INITIALIZED;
516 if (!index->IsIndexUsable()) {
517 return NS_ERROR_NOT_AVAILABLE;
520 // Getters in CacheIndexStats assert when mStateLogged is true since the
521 // information is incomplete between calls to BeforeChange() and AfterChange()
522 // (i.e. while CacheIndexEntryAutoManage exists). We need to check whether
523 // non-fresh entries exists outside the scope of CacheIndexEntryAutoManage.
524 bool updateIfNonFreshEntriesExist = false;
527 CacheIndexEntryAutoManage entryMng(aHash, index, lock);
529 CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
530 bool entryRemoved = entry && entry->IsRemoved();
531 CacheIndexEntryUpdate* updated = nullptr;
533 if (index->mState == READY || index->mState == UPDATING ||
534 index->mState == BUILDING) {
535 MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
537 if (entry && !entryRemoved) {
538 // Found entry in index that shouldn't exist.
540 if (entry->IsFresh()) {
541 // Someone removed the file on disk while FF is running. Update
542 // process can fix only non-fresh entries (i.e. entries that were not
543 // added within this session). Start update only if we have such
544 // entries.
546 // TODO: This should be very rare problem. If it turns out not to be
547 // true, change the update process so that it also iterates all
548 // initialized non-empty entries and checks whether the file exists.
550 LOG(
551 ("CacheIndex::AddEntry() - Cache file was removed outside FF "
552 "process!"));
554 updateIfNonFreshEntriesExist = true;
555 } else if (index->mState == READY) {
556 // Index is outdated, update it.
557 LOG(
558 ("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
559 "update is needed"));
560 index->mIndexNeedsUpdate = true;
561 } else {
562 // We cannot be here when building index since all entries are fresh
563 // during building.
564 MOZ_ASSERT(index->mState == UPDATING);
568 if (!entry) {
569 entry = index->mIndex.PutEntry(*aHash);
571 } else { // WRITING, READING
572 updated = index->mPendingUpdates.GetEntry(*aHash);
573 bool updatedRemoved = updated && updated->IsRemoved();
575 if ((updated && !updatedRemoved) ||
576 (!updated && entry && !entryRemoved && entry->IsFresh())) {
577 // Fresh entry found, so the file was removed outside FF
578 LOG(
579 ("CacheIndex::AddEntry() - Cache file was removed outside FF "
580 "process!"));
582 updateIfNonFreshEntriesExist = true;
583 } else if (!updated && entry && !entryRemoved) {
584 if (index->mState == WRITING) {
585 LOG(
586 ("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
587 "update is needed"));
588 index->mIndexNeedsUpdate = true;
590 // Ignore if state is READING since the index information is partial
593 updated = index->mPendingUpdates.PutEntry(*aHash);
596 if (updated) {
597 updated->InitNew();
598 updated->MarkDirty();
599 updated->MarkFresh();
600 } else {
601 entry->InitNew();
602 entry->MarkDirty();
603 entry->MarkFresh();
607 if (updateIfNonFreshEntriesExist &&
608 index->mIndexStats.Count() != index->mIndexStats.Fresh()) {
609 index->mIndexNeedsUpdate = true;
612 index->StartUpdatingIndexIfNeeded(lock);
613 index->WriteIndexToDiskIfNeeded(lock);
615 return NS_OK;
618 // static
619 nsresult CacheIndex::EnsureEntryExists(const SHA1Sum::Hash* aHash) {
620 LOG(("CacheIndex::EnsureEntryExists() [hash=%08x%08x%08x%08x%08x]",
621 LOGSHA1(aHash)));
623 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
625 StaticMutexAutoLock lock(sLock);
627 RefPtr<CacheIndex> index = gInstance;
629 if (!index) {
630 return NS_ERROR_NOT_INITIALIZED;
633 if (!index->IsIndexUsable()) {
634 return NS_ERROR_NOT_AVAILABLE;
638 CacheIndexEntryAutoManage entryMng(aHash, index, lock);
640 CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
641 bool entryRemoved = entry && entry->IsRemoved();
643 if (index->mState == READY || index->mState == UPDATING ||
644 index->mState == BUILDING) {
645 MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
647 if (!entry || entryRemoved) {
648 if (entryRemoved && entry->IsFresh()) {
649 // This could happen only if somebody copies files to the entries
650 // directory while FF is running.
651 LOG(
652 ("CacheIndex::EnsureEntryExists() - Cache file was added outside "
653 "FF process! Update is needed."));
654 index->mIndexNeedsUpdate = true;
655 } else if (index->mState == READY ||
656 (entryRemoved && !entry->IsFresh())) {
657 // Removed non-fresh entries can be present as a result of
658 // MergeJournal()
659 LOG(
660 ("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
661 " exist, update is needed"));
662 index->mIndexNeedsUpdate = true;
665 if (!entry) {
666 entry = index->mIndex.PutEntry(*aHash);
668 entry->InitNew();
669 entry->MarkDirty();
671 entry->MarkFresh();
672 } else { // WRITING, READING
673 CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash);
674 bool updatedRemoved = updated && updated->IsRemoved();
676 if (updatedRemoved || (!updated && entryRemoved && entry->IsFresh())) {
677 // Fresh information about missing entry found. This could happen only
678 // if somebody copies files to the entries directory while FF is
679 // running.
680 LOG(
681 ("CacheIndex::EnsureEntryExists() - Cache file was added outside "
682 "FF process! Update is needed."));
683 index->mIndexNeedsUpdate = true;
684 } else if (!updated && (!entry || entryRemoved)) {
685 if (index->mState == WRITING) {
686 LOG(
687 ("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
688 " exist, update is needed"));
689 index->mIndexNeedsUpdate = true;
691 // Ignore if state is READING since the index information is partial
694 // We don't need entryRemoved and updatedRemoved info anymore
695 if (entryRemoved) entry = nullptr;
696 if (updatedRemoved) updated = nullptr;
698 if (updated) {
699 updated->MarkFresh();
700 } else {
701 if (!entry) {
702 // Create a new entry
703 updated = index->mPendingUpdates.PutEntry(*aHash);
704 updated->InitNew();
705 updated->MarkFresh();
706 updated->MarkDirty();
707 } else {
708 if (!entry->IsFresh()) {
709 // To mark the entry fresh we must make a copy of index entry
710 // since the index is read-only.
711 updated = index->mPendingUpdates.PutEntry(*aHash);
712 *updated = *entry;
713 updated->MarkFresh();
720 index->StartUpdatingIndexIfNeeded(lock);
721 index->WriteIndexToDiskIfNeeded(lock);
723 return NS_OK;
726 // static
727 nsresult CacheIndex::InitEntry(const SHA1Sum::Hash* aHash,
728 OriginAttrsHash aOriginAttrsHash,
729 bool aAnonymous, bool aPinned) {
730 LOG(
731 ("CacheIndex::InitEntry() [hash=%08x%08x%08x%08x%08x, "
732 "originAttrsHash=%" PRIx64 ", anonymous=%d, pinned=%d]",
733 LOGSHA1(aHash), aOriginAttrsHash, aAnonymous, aPinned));
735 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
737 StaticMutexAutoLock lock(sLock);
739 RefPtr<CacheIndex> index = gInstance;
741 if (!index) {
742 return NS_ERROR_NOT_INITIALIZED;
745 if (!index->IsIndexUsable()) {
746 return NS_ERROR_NOT_AVAILABLE;
750 CacheIndexEntryAutoManage entryMng(aHash, index, lock);
752 CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
753 CacheIndexEntryUpdate* updated = nullptr;
754 bool reinitEntry = false;
756 if (entry && entry->IsRemoved()) {
757 entry = nullptr;
760 if (index->mState == READY || index->mState == UPDATING ||
761 index->mState == BUILDING) {
762 MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
763 MOZ_ASSERT(entry);
764 MOZ_ASSERT(entry->IsFresh());
766 if (!entry) {
767 LOG(("CacheIndex::InitEntry() - Entry was not found in mIndex!"));
768 NS_WARNING(
769 ("CacheIndex::InitEntry() - Entry was not found in mIndex!"));
770 return NS_ERROR_UNEXPECTED;
773 if (IsCollision(entry, aOriginAttrsHash, aAnonymous)) {
774 index->mIndexNeedsUpdate =
775 true; // TODO Does this really help in case of collision?
776 reinitEntry = true;
777 } else {
778 if (entry->IsInitialized()) {
779 return NS_OK;
782 } else {
783 updated = index->mPendingUpdates.GetEntry(*aHash);
784 DebugOnly<bool> removed = updated && updated->IsRemoved();
786 MOZ_ASSERT(updated || !removed);
787 MOZ_ASSERT(updated || entry);
789 if (!updated && !entry) {
790 LOG(
791 ("CacheIndex::InitEntry() - Entry was found neither in mIndex nor "
792 "in mPendingUpdates!"));
793 NS_WARNING(
794 ("CacheIndex::InitEntry() - Entry was found neither in "
795 "mIndex nor in mPendingUpdates!"));
796 return NS_ERROR_UNEXPECTED;
799 if (updated) {
800 MOZ_ASSERT(updated->IsFresh());
802 if (IsCollision(updated, aOriginAttrsHash, aAnonymous)) {
803 index->mIndexNeedsUpdate = true;
804 reinitEntry = true;
805 } else {
806 if (updated->IsInitialized()) {
807 return NS_OK;
810 } else {
811 MOZ_ASSERT(entry->IsFresh());
813 if (IsCollision(entry, aOriginAttrsHash, aAnonymous)) {
814 index->mIndexNeedsUpdate = true;
815 reinitEntry = true;
816 } else {
817 if (entry->IsInitialized()) {
818 return NS_OK;
822 // make a copy of a read-only entry
823 updated = index->mPendingUpdates.PutEntry(*aHash);
824 *updated = *entry;
828 if (reinitEntry) {
829 // There is a collision and we are going to rewrite this entry. Initialize
830 // it as a new entry.
831 if (updated) {
832 updated->InitNew();
833 updated->MarkFresh();
834 } else {
835 entry->InitNew();
836 entry->MarkFresh();
840 if (updated) {
841 updated->Init(aOriginAttrsHash, aAnonymous, aPinned);
842 updated->MarkDirty();
843 } else {
844 entry->Init(aOriginAttrsHash, aAnonymous, aPinned);
845 entry->MarkDirty();
849 index->StartUpdatingIndexIfNeeded(lock);
850 index->WriteIndexToDiskIfNeeded(lock);
852 return NS_OK;
855 // static
856 nsresult CacheIndex::RemoveEntry(const SHA1Sum::Hash* aHash) {
857 LOG(("CacheIndex::RemoveEntry() [hash=%08x%08x%08x%08x%08x]",
858 LOGSHA1(aHash)));
860 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
862 StaticMutexAutoLock lock(sLock);
864 RefPtr<CacheIndex> index = gInstance;
866 if (!index) {
867 return NS_ERROR_NOT_INITIALIZED;
870 if (!index->IsIndexUsable()) {
871 return NS_ERROR_NOT_AVAILABLE;
875 CacheIndexEntryAutoManage entryMng(aHash, index, lock);
877 CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
878 bool entryRemoved = entry && entry->IsRemoved();
880 if (index->mState == READY || index->mState == UPDATING ||
881 index->mState == BUILDING) {
882 MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
884 if (!entry || entryRemoved) {
885 if (entryRemoved && entry->IsFresh()) {
886 // This could happen only if somebody copies files to the entries
887 // directory while FF is running.
888 LOG(
889 ("CacheIndex::RemoveEntry() - Cache file was added outside FF "
890 "process! Update is needed."));
891 index->mIndexNeedsUpdate = true;
892 } else if (index->mState == READY ||
893 (entryRemoved && !entry->IsFresh())) {
894 // Removed non-fresh entries can be present as a result of
895 // MergeJournal()
896 LOG(
897 ("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
898 ", update is needed"));
899 index->mIndexNeedsUpdate = true;
901 } else {
902 if (entry) {
903 if (!entry->IsDirty() && entry->IsFileEmpty()) {
904 index->mIndex.RemoveEntry(entry);
905 entry = nullptr;
906 } else {
907 entry->MarkRemoved();
908 entry->MarkDirty();
909 entry->MarkFresh();
913 } else { // WRITING, READING
914 CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash);
915 bool updatedRemoved = updated && updated->IsRemoved();
917 if (updatedRemoved || (!updated && entryRemoved && entry->IsFresh())) {
918 // Fresh information about missing entry found. This could happen only
919 // if somebody copies files to the entries directory while FF is
920 // running.
921 LOG(
922 ("CacheIndex::RemoveEntry() - Cache file was added outside FF "
923 "process! Update is needed."));
924 index->mIndexNeedsUpdate = true;
925 } else if (!updated && (!entry || entryRemoved)) {
926 if (index->mState == WRITING) {
927 LOG(
928 ("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
929 ", update is needed"));
930 index->mIndexNeedsUpdate = true;
932 // Ignore if state is READING since the index information is partial
935 if (!updated) {
936 updated = index->mPendingUpdates.PutEntry(*aHash);
937 updated->InitNew();
940 updated->MarkRemoved();
941 updated->MarkDirty();
942 updated->MarkFresh();
945 index->StartUpdatingIndexIfNeeded(lock);
946 index->WriteIndexToDiskIfNeeded(lock);
948 return NS_OK;
951 // static
952 nsresult CacheIndex::UpdateEntry(const SHA1Sum::Hash* aHash,
953 const uint32_t* aFrecency,
954 const bool* aHasAltData,
955 const uint16_t* aOnStartTime,
956 const uint16_t* aOnStopTime,
957 const uint8_t* aContentType,
958 const uint32_t* aSize) {
959 LOG(
960 ("CacheIndex::UpdateEntry() [hash=%08x%08x%08x%08x%08x, "
961 "frecency=%s, hasAltData=%s, onStartTime=%s, onStopTime=%s, "
962 "contentType=%s, size=%s]",
963 LOGSHA1(aHash), aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
964 aHasAltData ? (*aHasAltData ? "true" : "false") : "",
965 aOnStartTime ? nsPrintfCString("%u", *aOnStartTime).get() : "",
966 aOnStopTime ? nsPrintfCString("%u", *aOnStopTime).get() : "",
967 aContentType ? nsPrintfCString("%u", *aContentType).get() : "",
968 aSize ? nsPrintfCString("%u", *aSize).get() : ""));
970 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
972 StaticMutexAutoLock lock(sLock);
974 RefPtr<CacheIndex> index = gInstance;
976 if (!index) {
977 return NS_ERROR_NOT_INITIALIZED;
980 if (!index->IsIndexUsable()) {
981 return NS_ERROR_NOT_AVAILABLE;
985 CacheIndexEntryAutoManage entryMng(aHash, index, lock);
987 CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
989 if (entry && entry->IsRemoved()) {
990 entry = nullptr;
993 if (index->mState == READY || index->mState == UPDATING ||
994 index->mState == BUILDING) {
995 MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
996 MOZ_ASSERT(entry);
998 if (!entry) {
999 LOG(("CacheIndex::UpdateEntry() - Entry was not found in mIndex!"));
1000 NS_WARNING(
1001 ("CacheIndex::UpdateEntry() - Entry was not found in mIndex!"));
1002 return NS_ERROR_UNEXPECTED;
1005 if (!HasEntryChanged(entry, aFrecency, aHasAltData, aOnStartTime,
1006 aOnStopTime, aContentType, aSize)) {
1007 return NS_OK;
1010 MOZ_ASSERT(entry->IsFresh());
1011 MOZ_ASSERT(entry->IsInitialized());
1012 entry->MarkDirty();
1014 if (aFrecency) {
1015 entry->SetFrecency(*aFrecency);
1018 if (aHasAltData) {
1019 entry->SetHasAltData(*aHasAltData);
1022 if (aOnStartTime) {
1023 entry->SetOnStartTime(*aOnStartTime);
1026 if (aOnStopTime) {
1027 entry->SetOnStopTime(*aOnStopTime);
1030 if (aContentType) {
1031 entry->SetContentType(*aContentType);
1034 if (aSize) {
1035 entry->SetFileSize(*aSize);
1037 } else {
1038 CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash);
1039 DebugOnly<bool> removed = updated && updated->IsRemoved();
1041 MOZ_ASSERT(updated || !removed);
1042 MOZ_ASSERT(updated || entry);
1044 if (!updated) {
1045 if (!entry) {
1046 LOG(
1047 ("CacheIndex::UpdateEntry() - Entry was found neither in mIndex "
1048 "nor in mPendingUpdates!"));
1049 NS_WARNING(
1050 ("CacheIndex::UpdateEntry() - Entry was found neither in "
1051 "mIndex nor in mPendingUpdates!"));
1052 return NS_ERROR_UNEXPECTED;
1055 // make a copy of a read-only entry
1056 updated = index->mPendingUpdates.PutEntry(*aHash);
1057 *updated = *entry;
1060 MOZ_ASSERT(updated->IsFresh());
1061 MOZ_ASSERT(updated->IsInitialized());
1062 updated->MarkDirty();
1064 if (aFrecency) {
1065 updated->SetFrecency(*aFrecency);
1068 if (aHasAltData) {
1069 updated->SetHasAltData(*aHasAltData);
1072 if (aOnStartTime) {
1073 updated->SetOnStartTime(*aOnStartTime);
1076 if (aOnStopTime) {
1077 updated->SetOnStopTime(*aOnStopTime);
1080 if (aContentType) {
1081 updated->SetContentType(*aContentType);
1084 if (aSize) {
1085 updated->SetFileSize(*aSize);
1090 index->WriteIndexToDiskIfNeeded(lock);
1092 return NS_OK;
1095 // static
1096 nsresult CacheIndex::RemoveAll() {
1097 LOG(("CacheIndex::RemoveAll()"));
1099 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
1101 nsCOMPtr<nsIFile> file;
1104 StaticMutexAutoLock lock(sLock);
1106 RefPtr<CacheIndex> index = gInstance;
1108 if (!index) {
1109 return NS_ERROR_NOT_INITIALIZED;
1112 MOZ_ASSERT(!index->mRemovingAll);
1114 if (!index->IsIndexUsable()) {
1115 return NS_ERROR_NOT_AVAILABLE;
1118 AutoRestore<bool> saveRemovingAll(index->mRemovingAll);
1119 index->mRemovingAll = true;
1121 // Doom index and journal handles but don't null them out since this will be
1122 // done in FinishWrite/FinishRead methods.
1123 if (index->mIndexHandle) {
1124 CacheFileIOManager::DoomFile(index->mIndexHandle, nullptr);
1125 } else {
1126 // We don't have a handle to index file, so get the file here, but delete
1127 // it outside the lock. Ignore the result since this is not fatal.
1128 index->GetFile(nsLiteralCString(INDEX_NAME), getter_AddRefs(file));
1131 if (index->mJournalHandle) {
1132 CacheFileIOManager::DoomFile(index->mJournalHandle, nullptr);
1135 switch (index->mState) {
1136 case WRITING:
1137 index->FinishWrite(false, lock);
1138 break;
1139 case READY:
1140 // nothing to do
1141 break;
1142 case READING:
1143 index->FinishRead(false, lock);
1144 break;
1145 case BUILDING:
1146 case UPDATING:
1147 index->FinishUpdate(false, lock);
1148 break;
1149 default:
1150 MOZ_ASSERT(false, "Unexpected state!");
1153 // We should end up in READY state
1154 MOZ_ASSERT(index->mState == READY);
1156 // There should not be any handle
1157 MOZ_ASSERT(!index->mIndexHandle);
1158 MOZ_ASSERT(!index->mJournalHandle);
1160 index->mIndexOnDiskIsValid = false;
1161 index->mIndexNeedsUpdate = false;
1163 index->mIndexStats.Clear();
1164 index->mFrecencyArray.Clear(lock);
1165 index->mIndex.Clear();
1167 for (uint32_t i = 0; i < index->mIterators.Length();) {
1168 nsresult rv = index->mIterators[i]->CloseInternal(NS_ERROR_NOT_AVAILABLE);
1169 if (NS_FAILED(rv)) {
1170 // CacheIndexIterator::CloseInternal() removes itself from mIterators
1171 // iff it returns success.
1172 LOG(
1173 ("CacheIndex::RemoveAll() - Failed to remove iterator %p. "
1174 "[rv=0x%08" PRIx32 "]",
1175 index->mIterators[i], static_cast<uint32_t>(rv)));
1176 i++;
1181 if (file) {
1182 // Ignore the result. The file might not exist and the failure is not fatal.
1183 file->Remove(false);
1186 return NS_OK;
1189 // static
1190 nsresult CacheIndex::HasEntry(
1191 const nsACString& aKey, EntryStatus* _retval,
1192 const std::function<void(const CacheIndexEntry*)>& aCB) {
1193 LOG(("CacheIndex::HasEntry() [key=%s]", PromiseFlatCString(aKey).get()));
1195 SHA1Sum sum;
1196 SHA1Sum::Hash hash;
1197 sum.update(aKey.BeginReading(), aKey.Length());
1198 sum.finish(hash);
1200 return HasEntry(hash, _retval, aCB);
1203 // static
1204 nsresult CacheIndex::HasEntry(
1205 const SHA1Sum::Hash& hash, EntryStatus* _retval,
1206 const std::function<void(const CacheIndexEntry*)>& aCB) {
1207 StaticMutexAutoLock lock(sLock);
1209 RefPtr<CacheIndex> index = gInstance;
1211 if (!index) {
1212 return NS_ERROR_NOT_INITIALIZED;
1215 if (!index->IsIndexUsable()) {
1216 return NS_ERROR_NOT_AVAILABLE;
1219 const CacheIndexEntry* entry = nullptr;
1221 switch (index->mState) {
1222 case READING:
1223 case WRITING:
1224 entry = index->mPendingUpdates.GetEntry(hash);
1225 [[fallthrough]];
1226 case BUILDING:
1227 case UPDATING:
1228 case READY:
1229 if (!entry) {
1230 entry = index->mIndex.GetEntry(hash);
1232 break;
1233 case INITIAL:
1234 case SHUTDOWN:
1235 MOZ_ASSERT(false, "Unexpected state!");
1238 if (!entry) {
1239 if (index->mState == READY || index->mState == WRITING) {
1240 *_retval = DOES_NOT_EXIST;
1241 } else {
1242 *_retval = DO_NOT_KNOW;
1244 } else {
1245 if (entry->IsRemoved()) {
1246 if (entry->IsFresh()) {
1247 *_retval = DOES_NOT_EXIST;
1248 } else {
1249 *_retval = DO_NOT_KNOW;
1251 } else {
1252 *_retval = EXISTS;
1253 if (aCB) {
1254 aCB(entry);
1259 LOG(("CacheIndex::HasEntry() - result is %u", *_retval));
1260 return NS_OK;
1263 // static
1264 nsresult CacheIndex::GetEntryForEviction(bool aIgnoreEmptyEntries,
1265 SHA1Sum::Hash* aHash, uint32_t* aCnt) {
1266 LOG(("CacheIndex::GetEntryForEviction()"));
1268 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
1270 StaticMutexAutoLock lock(sLock);
1272 RefPtr<CacheIndex> index = gInstance;
1274 if (!index) return NS_ERROR_NOT_INITIALIZED;
1276 if (!index->IsIndexUsable()) {
1277 return NS_ERROR_NOT_AVAILABLE;
1280 if (index->mIndexStats.Size() == 0) {
1281 return NS_ERROR_NOT_AVAILABLE;
1284 int32_t mediaUsage =
1285 round(static_cast<double>(index->mIndexStats.SizeByType(
1286 nsICacheEntry::CONTENT_TYPE_MEDIA)) *
1287 100.0 / static_cast<double>(index->mIndexStats.Size()));
1288 int32_t mediaUsageLimit =
1289 StaticPrefs::browser_cache_disk_content_type_media_limit();
1290 bool evictMedia = false;
1291 if (mediaUsage > mediaUsageLimit) {
1292 LOG(
1293 ("CacheIndex::GetEntryForEviction() - media content type is over the "
1294 "limit [mediaUsage=%d, mediaUsageLimit=%d]",
1295 mediaUsage, mediaUsageLimit));
1296 evictMedia = true;
1299 SHA1Sum::Hash hash;
1300 CacheIndexRecord* foundRecord = nullptr;
1301 uint32_t skipped = 0;
1303 // find first non-forced valid and unpinned entry with the lowest frecency
1304 index->mFrecencyArray.SortIfNeeded(lock);
1306 for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
1307 CacheIndexRecord* rec = iter.Get()->Get();
1309 memcpy(&hash, rec->mHash, sizeof(SHA1Sum::Hash));
1311 ++skipped;
1313 if (evictMedia && CacheIndexEntry::GetContentType(rec) !=
1314 nsICacheEntry::CONTENT_TYPE_MEDIA) {
1315 continue;
1318 if (IsForcedValidEntry(&hash)) {
1319 continue;
1322 if (CacheIndexEntry::IsPinned(rec)) {
1323 continue;
1326 if (aIgnoreEmptyEntries && !CacheIndexEntry::GetFileSize(*rec)) {
1327 continue;
1330 --skipped;
1331 foundRecord = rec;
1332 break;
1335 if (!foundRecord) return NS_ERROR_NOT_AVAILABLE;
1337 *aCnt = skipped;
1339 LOG(
1340 ("CacheIndex::GetEntryForEviction() - returning entry "
1341 "[hash=%08x%08x%08x%08x%08x, cnt=%u, frecency=%u, contentType=%u]",
1342 LOGSHA1(&hash), *aCnt, foundRecord->mFrecency,
1343 CacheIndexEntry::GetContentType(foundRecord)));
1345 memcpy(aHash, &hash, sizeof(SHA1Sum::Hash));
1347 return NS_OK;
1350 // static
1351 bool CacheIndex::IsForcedValidEntry(const SHA1Sum::Hash* aHash) {
1352 RefPtr<CacheFileHandle> handle;
1354 CacheFileIOManager::gInstance->mHandles.GetHandle(aHash,
1355 getter_AddRefs(handle));
1357 if (!handle) return false;
1359 nsCString hashKey = handle->Key();
1360 return CacheStorageService::Self()->IsForcedValidEntry(hashKey);
1363 // static
1364 nsresult CacheIndex::GetCacheSize(uint32_t* _retval) {
1365 LOG(("CacheIndex::GetCacheSize()"));
1367 StaticMutexAutoLock lock(sLock);
1369 RefPtr<CacheIndex> index = gInstance;
1371 if (!index) return NS_ERROR_NOT_INITIALIZED;
1373 if (!index->IsIndexUsable()) {
1374 return NS_ERROR_NOT_AVAILABLE;
1377 *_retval = index->mIndexStats.Size();
1378 LOG(("CacheIndex::GetCacheSize() - returning %u", *_retval));
1379 return NS_OK;
1382 // static
1383 nsresult CacheIndex::GetEntryFileCount(uint32_t* _retval) {
1384 LOG(("CacheIndex::GetEntryFileCount()"));
1386 StaticMutexAutoLock lock(sLock);
1388 RefPtr<CacheIndex> index = gInstance;
1390 if (!index) {
1391 return NS_ERROR_NOT_INITIALIZED;
1394 if (!index->IsIndexUsable()) {
1395 return NS_ERROR_NOT_AVAILABLE;
1398 *_retval = index->mIndexStats.ActiveEntriesCount();
1399 LOG(("CacheIndex::GetEntryFileCount() - returning %u", *_retval));
1400 return NS_OK;
1403 // static
1404 nsresult CacheIndex::GetCacheStats(nsILoadContextInfo* aInfo, uint32_t* aSize,
1405 uint32_t* aCount) {
1406 LOG(("CacheIndex::GetCacheStats() [info=%p]", aInfo));
1408 StaticMutexAutoLock lock(sLock);
1410 RefPtr<CacheIndex> index = gInstance;
1412 if (!index) {
1413 return NS_ERROR_NOT_INITIALIZED;
1416 if (!index->IsIndexUsable()) {
1417 return NS_ERROR_NOT_AVAILABLE;
1420 *aSize = 0;
1421 *aCount = 0;
1423 for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
1424 if (aInfo &&
1425 !CacheIndexEntry::RecordMatchesLoadContextInfo(iter.Get(), aInfo)) {
1426 continue;
1429 *aSize += CacheIndexEntry::GetFileSize(*(iter.Get()->Get()));
1430 ++*aCount;
1433 return NS_OK;
1436 // static
1437 nsresult CacheIndex::AsyncGetDiskConsumption(
1438 nsICacheStorageConsumptionObserver* aObserver) {
1439 LOG(("CacheIndex::AsyncGetDiskConsumption()"));
1441 StaticMutexAutoLock lock(sLock);
1443 RefPtr<CacheIndex> index = gInstance;
1445 if (!index) {
1446 return NS_ERROR_NOT_INITIALIZED;
1449 if (!index->IsIndexUsable()) {
1450 return NS_ERROR_NOT_AVAILABLE;
1453 RefPtr<DiskConsumptionObserver> observer =
1454 DiskConsumptionObserver::Init(aObserver);
1456 NS_ENSURE_ARG(observer);
1458 if ((index->mState == READY || index->mState == WRITING) &&
1459 !index->mAsyncGetDiskConsumptionBlocked) {
1460 LOG(("CacheIndex::AsyncGetDiskConsumption - calling immediately"));
1461 // Safe to call the callback under the lock,
1462 // we always post to the main thread.
1463 observer->OnDiskConsumption(index->mIndexStats.Size() << 10);
1464 return NS_OK;
1467 LOG(("CacheIndex::AsyncGetDiskConsumption - remembering callback"));
1468 // Will be called when the index get to the READY state.
1469 index->mDiskConsumptionObservers.AppendElement(observer);
1471 // Move forward with index re/building if it is pending
1472 RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
1473 if (ioThread) {
1474 ioThread->Dispatch(
1475 NS_NewRunnableFunction("net::CacheIndex::AsyncGetDiskConsumption",
1476 []() -> void {
1477 StaticMutexAutoLock lock(sLock);
1479 RefPtr<CacheIndex> index = gInstance;
1480 if (index && index->mUpdateTimer) {
1481 index->mUpdateTimer->Cancel();
1482 index->DelayedUpdateLocked(lock);
1485 CacheIOThread::INDEX);
1488 return NS_OK;
1491 // static
1492 nsresult CacheIndex::GetIterator(nsILoadContextInfo* aInfo, bool aAddNew,
1493 CacheIndexIterator** _retval) {
1494 LOG(("CacheIndex::GetIterator() [info=%p, addNew=%d]", aInfo, aAddNew));
1496 StaticMutexAutoLock lock(sLock);
1498 RefPtr<CacheIndex> index = gInstance;
1500 if (!index) {
1501 return NS_ERROR_NOT_INITIALIZED;
1504 if (!index->IsIndexUsable()) {
1505 return NS_ERROR_NOT_AVAILABLE;
1508 RefPtr<CacheIndexIterator> idxIter;
1509 if (aInfo) {
1510 idxIter = new CacheIndexContextIterator(index, aAddNew, aInfo);
1511 } else {
1512 idxIter = new CacheIndexIterator(index, aAddNew);
1515 index->mFrecencyArray.SortIfNeeded(lock);
1517 for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
1518 idxIter->AddRecord(iter.Get(), lock);
1521 index->mIterators.AppendElement(idxIter);
1522 idxIter.swap(*_retval);
1523 return NS_OK;
1526 // static
1527 nsresult CacheIndex::IsUpToDate(bool* _retval) {
1528 LOG(("CacheIndex::IsUpToDate()"));
1530 StaticMutexAutoLock lock(sLock);
1532 RefPtr<CacheIndex> index = gInstance;
1534 if (!index) {
1535 return NS_ERROR_NOT_INITIALIZED;
1538 if (!index->IsIndexUsable()) {
1539 return NS_ERROR_NOT_AVAILABLE;
1542 *_retval = (index->mState == READY || index->mState == WRITING) &&
1543 !index->mIndexNeedsUpdate && !index->mShuttingDown;
1545 LOG(("CacheIndex::IsUpToDate() - returning %d", *_retval));
1546 return NS_OK;
1549 bool CacheIndex::IsIndexUsable() {
1550 MOZ_ASSERT(mState != INITIAL);
1552 switch (mState) {
1553 case INITIAL:
1554 case SHUTDOWN:
1555 return false;
1557 case READING:
1558 case WRITING:
1559 case BUILDING:
1560 case UPDATING:
1561 case READY:
1562 break;
1565 return true;
1568 // static
1569 bool CacheIndex::IsCollision(CacheIndexEntry* aEntry,
1570 OriginAttrsHash aOriginAttrsHash,
1571 bool aAnonymous) {
1572 if (!aEntry->IsInitialized()) {
1573 return false;
1576 if (aEntry->Anonymous() != aAnonymous ||
1577 aEntry->OriginAttrsHash() != aOriginAttrsHash) {
1578 LOG(
1579 ("CacheIndex::IsCollision() - Collision detected for entry hash=%08x"
1580 "%08x%08x%08x%08x, expected values: originAttrsHash=%" PRIu64 ", "
1581 "anonymous=%d; actual values: originAttrsHash=%" PRIu64
1582 ", anonymous=%d]",
1583 LOGSHA1(aEntry->Hash()), aOriginAttrsHash, aAnonymous,
1584 aEntry->OriginAttrsHash(), aEntry->Anonymous()));
1585 return true;
1588 return false;
1591 // static
1592 bool CacheIndex::HasEntryChanged(
1593 CacheIndexEntry* aEntry, const uint32_t* aFrecency, const bool* aHasAltData,
1594 const uint16_t* aOnStartTime, const uint16_t* aOnStopTime,
1595 const uint8_t* aContentType, const uint32_t* aSize) {
1596 if (aFrecency && *aFrecency != aEntry->GetFrecency()) {
1597 return true;
1600 if (aHasAltData && *aHasAltData != aEntry->GetHasAltData()) {
1601 return true;
1604 if (aOnStartTime && *aOnStartTime != aEntry->GetOnStartTime()) {
1605 return true;
1608 if (aOnStopTime && *aOnStopTime != aEntry->GetOnStopTime()) {
1609 return true;
1612 if (aContentType && *aContentType != aEntry->GetContentType()) {
1613 return true;
1616 if (aSize &&
1617 (*aSize & CacheIndexEntry::kFileSizeMask) != aEntry->GetFileSize()) {
1618 return true;
1621 return false;
1624 void CacheIndex::ProcessPendingOperations(
1625 const StaticMutexAutoLock& aProofOfLock) {
1626 sLock.AssertCurrentThreadOwns();
1627 LOG(("CacheIndex::ProcessPendingOperations()"));
1629 for (auto iter = mPendingUpdates.Iter(); !iter.Done(); iter.Next()) {
1630 CacheIndexEntryUpdate* update = iter.Get();
1632 LOG(("CacheIndex::ProcessPendingOperations() [hash=%08x%08x%08x%08x%08x]",
1633 LOGSHA1(update->Hash())));
1635 MOZ_ASSERT(update->IsFresh());
1637 CacheIndexEntry* entry = mIndex.GetEntry(*update->Hash());
1639 CacheIndexEntryAutoManage emng(update->Hash(), this, aProofOfLock);
1640 emng.DoNotSearchInUpdates();
1642 if (update->IsRemoved()) {
1643 if (entry) {
1644 if (entry->IsRemoved()) {
1645 MOZ_ASSERT(entry->IsFresh());
1646 MOZ_ASSERT(entry->IsDirty());
1647 } else if (!entry->IsDirty() && entry->IsFileEmpty()) {
1648 // Entries with empty file are not stored in index on disk. Just
1649 // remove the entry, but only in case the entry is not dirty, i.e.
1650 // the entry file was empty when we wrote the index.
1651 mIndex.RemoveEntry(entry);
1652 entry = nullptr;
1653 } else {
1654 entry->MarkRemoved();
1655 entry->MarkDirty();
1656 entry->MarkFresh();
1659 } else if (entry) {
1660 // Some information in mIndex can be newer than in mPendingUpdates (see
1661 // bug 1074832). This will copy just those values that were really
1662 // updated.
1663 update->ApplyUpdate(entry);
1664 } else {
1665 // There is no entry in mIndex, copy all information from
1666 // mPendingUpdates to mIndex.
1667 entry = mIndex.PutEntry(*update->Hash());
1668 *entry = *update;
1671 iter.Remove();
1674 MOZ_ASSERT(mPendingUpdates.Count() == 0);
1676 EnsureCorrectStats();
1679 bool CacheIndex::WriteIndexToDiskIfNeeded(
1680 const StaticMutexAutoLock& aProofOfLock) {
1681 sLock.AssertCurrentThreadOwns();
1682 if (mState != READY || mShuttingDown || mRWPending) {
1683 return false;
1686 if (!mLastDumpTime.IsNull() &&
1687 (TimeStamp::NowLoRes() - mLastDumpTime).ToMilliseconds() <
1688 kMinDumpInterval) {
1689 return false;
1692 if (mIndexStats.Dirty() < kMinUnwrittenChanges) {
1693 return false;
1696 WriteIndexToDisk(aProofOfLock);
1697 return true;
1700 void CacheIndex::WriteIndexToDisk(const StaticMutexAutoLock& aProofOfLock) {
1701 sLock.AssertCurrentThreadOwns();
1702 LOG(("CacheIndex::WriteIndexToDisk()"));
1703 mIndexStats.Log();
1705 nsresult rv;
1707 MOZ_ASSERT(mState == READY);
1708 MOZ_ASSERT(!mRWBuf);
1709 MOZ_ASSERT(!mRWHash);
1710 MOZ_ASSERT(!mRWPending);
1712 ChangeState(WRITING, aProofOfLock);
1714 mProcessEntries = mIndexStats.ActiveEntriesCount();
1716 mIndexFileOpener = new FileOpenHelper(this);
1717 rv = CacheFileIOManager::OpenFile(
1718 nsLiteralCString(TEMP_INDEX_NAME),
1719 CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::CREATE,
1720 mIndexFileOpener);
1721 if (NS_FAILED(rv)) {
1722 LOG(("CacheIndex::WriteIndexToDisk() - Can't open file [rv=0x%08" PRIx32
1723 "]",
1724 static_cast<uint32_t>(rv)));
1725 FinishWrite(false, aProofOfLock);
1726 return;
1729 // Write index header to a buffer, it will be written to disk together with
1730 // records in WriteRecords() once we open the file successfully.
1731 AllocBuffer();
1732 mRWHash = new CacheHash();
1734 mRWBufPos = 0;
1735 // index version
1736 NetworkEndian::writeUint32(mRWBuf + mRWBufPos, kIndexVersion);
1737 mRWBufPos += sizeof(uint32_t);
1738 // timestamp
1739 NetworkEndian::writeUint32(mRWBuf + mRWBufPos,
1740 static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC));
1741 mRWBufPos += sizeof(uint32_t);
1742 // dirty flag
1743 NetworkEndian::writeUint32(mRWBuf + mRWBufPos, 1);
1744 mRWBufPos += sizeof(uint32_t);
1745 // amount of data written to the cache
1746 NetworkEndian::writeUint32(mRWBuf + mRWBufPos,
1747 static_cast<uint32_t>(mTotalBytesWritten >> 10));
1748 mRWBufPos += sizeof(uint32_t);
1750 mSkipEntries = 0;
1753 void CacheIndex::WriteRecords(const StaticMutexAutoLock& aProofOfLock) {
1754 sLock.AssertCurrentThreadOwns();
1755 LOG(("CacheIndex::WriteRecords()"));
1757 nsresult rv;
1759 MOZ_ASSERT(mState == WRITING);
1760 MOZ_ASSERT(!mRWPending);
1762 int64_t fileOffset;
1764 if (mSkipEntries) {
1765 MOZ_ASSERT(mRWBufPos == 0);
1766 fileOffset = sizeof(CacheIndexHeader);
1767 fileOffset += sizeof(CacheIndexRecord) * mSkipEntries;
1768 } else {
1769 MOZ_ASSERT(mRWBufPos == sizeof(CacheIndexHeader));
1770 fileOffset = 0;
1772 uint32_t hashOffset = mRWBufPos;
1774 char* buf = mRWBuf + mRWBufPos;
1775 uint32_t skip = mSkipEntries;
1776 uint32_t processMax = (mRWBufSize - mRWBufPos) / sizeof(CacheIndexRecord);
1777 MOZ_ASSERT(processMax != 0 ||
1778 mProcessEntries ==
1779 0); // TODO make sure we can write an empty index
1780 uint32_t processed = 0;
1781 #ifdef DEBUG
1782 bool hasMore = false;
1783 #endif
1784 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
1785 CacheIndexEntry* entry = iter.Get();
1786 if (entry->IsRemoved() || !entry->IsInitialized() || entry->IsFileEmpty()) {
1787 continue;
1790 if (skip) {
1791 skip--;
1792 continue;
1795 if (processed == processMax) {
1796 #ifdef DEBUG
1797 hasMore = true;
1798 #endif
1799 break;
1802 entry->WriteToBuf(buf);
1803 buf += sizeof(CacheIndexRecord);
1804 processed++;
1807 MOZ_ASSERT(mRWBufPos != static_cast<uint32_t>(buf - mRWBuf) ||
1808 mProcessEntries == 0);
1809 mRWBufPos = buf - mRWBuf;
1810 mSkipEntries += processed;
1811 MOZ_ASSERT(mSkipEntries <= mProcessEntries);
1813 mRWHash->Update(mRWBuf + hashOffset, mRWBufPos - hashOffset);
1815 if (mSkipEntries == mProcessEntries) {
1816 MOZ_ASSERT(!hasMore);
1818 // We've processed all records
1819 if (mRWBufPos + sizeof(CacheHash::Hash32_t) > mRWBufSize) {
1820 // realloc buffer to spare another write cycle
1821 mRWBufSize = mRWBufPos + sizeof(CacheHash::Hash32_t);
1822 mRWBuf = static_cast<char*>(moz_xrealloc(mRWBuf, mRWBufSize));
1825 NetworkEndian::writeUint32(mRWBuf + mRWBufPos, mRWHash->GetHash());
1826 mRWBufPos += sizeof(CacheHash::Hash32_t);
1827 } else {
1828 MOZ_ASSERT(hasMore);
1831 rv = CacheFileIOManager::Write(mIndexHandle, fileOffset, mRWBuf, mRWBufPos,
1832 mSkipEntries == mProcessEntries, false, this);
1833 if (NS_FAILED(rv)) {
1834 LOG(
1835 ("CacheIndex::WriteRecords() - CacheFileIOManager::Write() failed "
1836 "synchronously [rv=0x%08" PRIx32 "]",
1837 static_cast<uint32_t>(rv)));
1838 FinishWrite(false, aProofOfLock);
1839 } else {
1840 mRWPending = true;
1843 mRWBufPos = 0;
1846 void CacheIndex::FinishWrite(bool aSucceeded,
1847 const StaticMutexAutoLock& aProofOfLock) {
1848 sLock.AssertCurrentThreadOwns();
1849 LOG(("CacheIndex::FinishWrite() [succeeded=%d]", aSucceeded));
1851 MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == WRITING);
1853 // If there is write operation pending we must be cancelling writing of the
1854 // index when shutting down or removing the whole index.
1855 MOZ_ASSERT(!mRWPending || (!aSucceeded && (mShuttingDown || mRemovingAll)));
1857 mIndexHandle = nullptr;
1858 mRWHash = nullptr;
1859 ReleaseBuffer();
1861 if (aSucceeded) {
1862 // Opening of the file must not be in progress if writing succeeded.
1863 MOZ_ASSERT(!mIndexFileOpener);
1865 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
1866 CacheIndexEntry* entry = iter.Get();
1868 bool remove = false;
1870 CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
1872 if (entry->IsRemoved()) {
1873 emng.DoNotSearchInIndex();
1874 remove = true;
1875 } else if (entry->IsDirty()) {
1876 entry->ClearDirty();
1879 if (remove) {
1880 iter.Remove();
1884 mIndexOnDiskIsValid = true;
1885 } else {
1886 if (mIndexFileOpener) {
1887 // If opening of the file is still in progress (e.g. WRITE process was
1888 // canceled by RemoveAll()) then we need to cancel the opener to make sure
1889 // that OnFileOpenedInternal() won't be called.
1890 mIndexFileOpener->Cancel();
1891 mIndexFileOpener = nullptr;
1895 ProcessPendingOperations(aProofOfLock);
1896 mIndexStats.Log();
1898 if (mState == WRITING) {
1899 ChangeState(READY, aProofOfLock);
1900 mLastDumpTime = TimeStamp::NowLoRes();
1904 nsresult CacheIndex::GetFile(const nsACString& aName, nsIFile** _retval) {
1905 nsresult rv;
1907 nsCOMPtr<nsIFile> file;
1908 rv = mCacheDirectory->Clone(getter_AddRefs(file));
1909 NS_ENSURE_SUCCESS(rv, rv);
1911 rv = file->AppendNative(aName);
1912 NS_ENSURE_SUCCESS(rv, rv);
1914 file.swap(*_retval);
1915 return NS_OK;
1918 void CacheIndex::RemoveFile(const nsACString& aName) {
1919 MOZ_ASSERT(mState == SHUTDOWN);
1921 nsresult rv;
1923 nsCOMPtr<nsIFile> file;
1924 rv = GetFile(aName, getter_AddRefs(file));
1925 NS_ENSURE_SUCCESS_VOID(rv);
1927 rv = file->Remove(false);
1928 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
1929 LOG(
1930 ("CacheIndex::RemoveFile() - Cannot remove old entry file from disk "
1931 "[rv=0x%08" PRIx32 ", name=%s]",
1932 static_cast<uint32_t>(rv), PromiseFlatCString(aName).get()));
1936 void CacheIndex::RemoveAllIndexFiles() {
1937 LOG(("CacheIndex::RemoveAllIndexFiles()"));
1938 RemoveFile(nsLiteralCString(INDEX_NAME));
1939 RemoveJournalAndTempFile();
1942 void CacheIndex::RemoveJournalAndTempFile() {
1943 LOG(("CacheIndex::RemoveJournalAndTempFile()"));
1944 RemoveFile(nsLiteralCString(TEMP_INDEX_NAME));
1945 RemoveFile(nsLiteralCString(JOURNAL_NAME));
1948 class WriteLogHelper {
1949 public:
1950 explicit WriteLogHelper(PRFileDesc* aFD)
1951 : mFD(aFD), mBufSize(kMaxBufSize), mBufPos(0) {
1952 mHash = new CacheHash();
1953 mBuf = static_cast<char*>(moz_xmalloc(mBufSize));
1956 ~WriteLogHelper() { free(mBuf); }
1958 nsresult AddEntry(CacheIndexEntry* aEntry);
1959 nsresult Finish();
1961 private:
1962 nsresult FlushBuffer();
1964 PRFileDesc* mFD;
1965 char* mBuf;
1966 uint32_t mBufSize;
1967 int32_t mBufPos;
1968 RefPtr<CacheHash> mHash;
1971 nsresult WriteLogHelper::AddEntry(CacheIndexEntry* aEntry) {
1972 nsresult rv;
1974 if (mBufPos + sizeof(CacheIndexRecord) > mBufSize) {
1975 mHash->Update(mBuf, mBufPos);
1977 rv = FlushBuffer();
1978 NS_ENSURE_SUCCESS(rv, rv);
1979 MOZ_ASSERT(mBufPos + sizeof(CacheIndexRecord) <= mBufSize);
1982 aEntry->WriteToBuf(mBuf + mBufPos);
1983 mBufPos += sizeof(CacheIndexRecord);
1985 return NS_OK;
1988 nsresult WriteLogHelper::Finish() {
1989 nsresult rv;
1991 mHash->Update(mBuf, mBufPos);
1992 if (mBufPos + sizeof(CacheHash::Hash32_t) > mBufSize) {
1993 rv = FlushBuffer();
1994 NS_ENSURE_SUCCESS(rv, rv);
1995 MOZ_ASSERT(mBufPos + sizeof(CacheHash::Hash32_t) <= mBufSize);
1998 NetworkEndian::writeUint32(mBuf + mBufPos, mHash->GetHash());
1999 mBufPos += sizeof(CacheHash::Hash32_t);
2001 rv = FlushBuffer();
2002 NS_ENSURE_SUCCESS(rv, rv);
2004 return NS_OK;
2007 nsresult WriteLogHelper::FlushBuffer() {
2008 if (CacheObserver::IsPastShutdownIOLag()) {
2009 LOG(("WriteLogHelper::FlushBuffer() - Interrupting writing journal."));
2010 return NS_ERROR_FAILURE;
2013 int32_t bytesWritten = PR_Write(mFD, mBuf, mBufPos);
2015 if (bytesWritten != mBufPos) {
2016 return NS_ERROR_FAILURE;
2019 mBufPos = 0;
2020 return NS_OK;
2023 nsresult CacheIndex::WriteLogToDisk() {
2024 LOG(("CacheIndex::WriteLogToDisk()"));
2026 nsresult rv;
2028 MOZ_ASSERT(mPendingUpdates.Count() == 0);
2029 MOZ_ASSERT(mState == SHUTDOWN);
2031 if (CacheObserver::IsPastShutdownIOLag()) {
2032 LOG(("CacheIndex::WriteLogToDisk() - Skipping writing journal."));
2033 return NS_ERROR_FAILURE;
2036 RemoveFile(nsLiteralCString(TEMP_INDEX_NAME));
2038 nsCOMPtr<nsIFile> indexFile;
2039 rv = GetFile(nsLiteralCString(INDEX_NAME), getter_AddRefs(indexFile));
2040 NS_ENSURE_SUCCESS(rv, rv);
2042 nsCOMPtr<nsIFile> logFile;
2043 rv = GetFile(nsLiteralCString(JOURNAL_NAME), getter_AddRefs(logFile));
2044 NS_ENSURE_SUCCESS(rv, rv);
2046 mIndexStats.Log();
2048 PRFileDesc* fd = nullptr;
2049 rv = logFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600,
2050 &fd);
2051 NS_ENSURE_SUCCESS(rv, rv);
2053 WriteLogHelper wlh(fd);
2054 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
2055 CacheIndexEntry* entry = iter.Get();
2056 if (entry->IsRemoved() || entry->IsDirty()) {
2057 rv = wlh.AddEntry(entry);
2058 if (NS_WARN_IF(NS_FAILED(rv))) {
2059 return rv;
2064 rv = wlh.Finish();
2065 PR_Close(fd);
2066 NS_ENSURE_SUCCESS(rv, rv);
2068 rv = indexFile->OpenNSPRFileDesc(PR_RDWR, 0600, &fd);
2069 NS_ENSURE_SUCCESS(rv, rv);
2071 // Seek to dirty flag in the index header and clear it.
2072 static_assert(2 * sizeof(uint32_t) == offsetof(CacheIndexHeader, mIsDirty),
2073 "Unexpected offset of CacheIndexHeader::mIsDirty");
2074 int64_t offset = PR_Seek64(fd, 2 * sizeof(uint32_t), PR_SEEK_SET);
2075 if (offset == -1) {
2076 PR_Close(fd);
2077 return NS_ERROR_FAILURE;
2080 uint32_t isDirty = 0;
2081 int32_t bytesWritten = PR_Write(fd, &isDirty, sizeof(isDirty));
2082 PR_Close(fd);
2083 if (bytesWritten != sizeof(isDirty)) {
2084 return NS_ERROR_FAILURE;
2087 return NS_OK;
2090 void CacheIndex::ReadIndexFromDisk(const StaticMutexAutoLock& aProofOfLock) {
2091 sLock.AssertCurrentThreadOwns();
2092 LOG(("CacheIndex::ReadIndexFromDisk()"));
2094 nsresult rv;
2096 MOZ_ASSERT(mState == INITIAL);
2098 ChangeState(READING, aProofOfLock);
2100 mIndexFileOpener = new FileOpenHelper(this);
2101 rv = CacheFileIOManager::OpenFile(
2102 nsLiteralCString(INDEX_NAME),
2103 CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN,
2104 mIndexFileOpener);
2105 if (NS_FAILED(rv)) {
2106 LOG(
2107 ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
2108 "failed [rv=0x%08" PRIx32 ", file=%s]",
2109 static_cast<uint32_t>(rv), INDEX_NAME));
2110 FinishRead(false, aProofOfLock);
2111 return;
2114 mJournalFileOpener = new FileOpenHelper(this);
2115 rv = CacheFileIOManager::OpenFile(
2116 nsLiteralCString(JOURNAL_NAME),
2117 CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN,
2118 mJournalFileOpener);
2119 if (NS_FAILED(rv)) {
2120 LOG(
2121 ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
2122 "failed [rv=0x%08" PRIx32 ", file=%s]",
2123 static_cast<uint32_t>(rv), JOURNAL_NAME));
2124 FinishRead(false, aProofOfLock);
2127 mTmpFileOpener = new FileOpenHelper(this);
2128 rv = CacheFileIOManager::OpenFile(
2129 nsLiteralCString(TEMP_INDEX_NAME),
2130 CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN,
2131 mTmpFileOpener);
2132 if (NS_FAILED(rv)) {
2133 LOG(
2134 ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
2135 "failed [rv=0x%08" PRIx32 ", file=%s]",
2136 static_cast<uint32_t>(rv), TEMP_INDEX_NAME));
2137 FinishRead(false, aProofOfLock);
2141 void CacheIndex::StartReadingIndex(const StaticMutexAutoLock& aProofOfLock) {
2142 sLock.AssertCurrentThreadOwns();
2143 LOG(("CacheIndex::StartReadingIndex()"));
2145 nsresult rv;
2147 MOZ_ASSERT(mIndexHandle);
2148 MOZ_ASSERT(mState == READING);
2149 MOZ_ASSERT(!mIndexOnDiskIsValid);
2150 MOZ_ASSERT(!mDontMarkIndexClean);
2151 MOZ_ASSERT(!mJournalReadSuccessfully);
2152 MOZ_ASSERT(mIndexHandle->FileSize() >= 0);
2153 MOZ_ASSERT(!mRWPending);
2155 int64_t entriesSize = mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
2156 sizeof(CacheHash::Hash32_t);
2158 if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
2159 LOG(("CacheIndex::StartReadingIndex() - Index is corrupted"));
2160 FinishRead(false, aProofOfLock);
2161 return;
2164 AllocBuffer();
2165 mSkipEntries = 0;
2166 mRWHash = new CacheHash();
2168 mRWBufPos =
2169 std::min(mRWBufSize, static_cast<uint32_t>(mIndexHandle->FileSize()));
2171 rv = CacheFileIOManager::Read(mIndexHandle, 0, mRWBuf, mRWBufPos, this);
2172 if (NS_FAILED(rv)) {
2173 LOG(
2174 ("CacheIndex::StartReadingIndex() - CacheFileIOManager::Read() failed "
2175 "synchronously [rv=0x%08" PRIx32 "]",
2176 static_cast<uint32_t>(rv)));
2177 FinishRead(false, aProofOfLock);
2178 } else {
2179 mRWPending = true;
2183 void CacheIndex::ParseRecords(const StaticMutexAutoLock& aProofOfLock) {
2184 sLock.AssertCurrentThreadOwns();
2185 LOG(("CacheIndex::ParseRecords()"));
2187 nsresult rv;
2189 MOZ_ASSERT(!mRWPending);
2191 uint32_t entryCnt = (mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
2192 sizeof(CacheHash::Hash32_t)) /
2193 sizeof(CacheIndexRecord);
2194 uint32_t pos = 0;
2196 if (!mSkipEntries) {
2197 if (NetworkEndian::readUint32(mRWBuf + pos) != kIndexVersion) {
2198 FinishRead(false, aProofOfLock);
2199 return;
2201 pos += sizeof(uint32_t);
2203 mIndexTimeStamp = NetworkEndian::readUint32(mRWBuf + pos);
2204 pos += sizeof(uint32_t);
2206 if (NetworkEndian::readUint32(mRWBuf + pos)) {
2207 if (mJournalHandle) {
2208 CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
2209 mJournalHandle = nullptr;
2211 } else {
2212 uint32_t* isDirty =
2213 reinterpret_cast<uint32_t*>(moz_xmalloc(sizeof(uint32_t)));
2214 NetworkEndian::writeUint32(isDirty, 1);
2216 // Mark index dirty. The buffer will be freed by CacheFileIOManager.
2217 CacheFileIOManager::WriteWithoutCallback(
2218 mIndexHandle, 2 * sizeof(uint32_t), reinterpret_cast<char*>(isDirty),
2219 sizeof(uint32_t), true, false);
2221 pos += sizeof(uint32_t);
2223 uint64_t dataWritten = NetworkEndian::readUint32(mRWBuf + pos);
2224 pos += sizeof(uint32_t);
2225 dataWritten <<= 10;
2226 mTotalBytesWritten += dataWritten;
2229 uint32_t hashOffset = pos;
2231 while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
2232 mSkipEntries != entryCnt) {
2233 CacheIndexRecord* rec = reinterpret_cast<CacheIndexRecord*>(mRWBuf + pos);
2234 CacheIndexEntry tmpEntry(&rec->mHash);
2235 tmpEntry.ReadFromBuf(mRWBuf + pos);
2237 if (tmpEntry.IsDirty() || !tmpEntry.IsInitialized() ||
2238 tmpEntry.IsFileEmpty() || tmpEntry.IsFresh() || tmpEntry.IsRemoved()) {
2239 LOG(
2240 ("CacheIndex::ParseRecords() - Invalid entry found in index, removing"
2241 " whole index [dirty=%d, initialized=%d, fileEmpty=%d, fresh=%d, "
2242 "removed=%d]",
2243 tmpEntry.IsDirty(), tmpEntry.IsInitialized(), tmpEntry.IsFileEmpty(),
2244 tmpEntry.IsFresh(), tmpEntry.IsRemoved()));
2245 FinishRead(false, aProofOfLock);
2246 return;
2249 CacheIndexEntryAutoManage emng(tmpEntry.Hash(), this, aProofOfLock);
2251 CacheIndexEntry* entry = mIndex.PutEntry(*tmpEntry.Hash());
2252 *entry = tmpEntry;
2254 pos += sizeof(CacheIndexRecord);
2255 mSkipEntries++;
2258 mRWHash->Update(mRWBuf + hashOffset, pos - hashOffset);
2260 if (pos != mRWBufPos) {
2261 memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
2264 mRWBufPos -= pos;
2265 pos = 0;
2267 int64_t fileOffset = sizeof(CacheIndexHeader) +
2268 mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
2270 MOZ_ASSERT(fileOffset <= mIndexHandle->FileSize());
2271 if (fileOffset == mIndexHandle->FileSize()) {
2272 uint32_t expectedHash = NetworkEndian::readUint32(mRWBuf);
2273 if (mRWHash->GetHash() != expectedHash) {
2274 LOG(("CacheIndex::ParseRecords() - Hash mismatch, [is %x, should be %x]",
2275 mRWHash->GetHash(), expectedHash));
2276 FinishRead(false, aProofOfLock);
2277 return;
2280 mIndexOnDiskIsValid = true;
2281 mJournalReadSuccessfully = false;
2283 if (mJournalHandle) {
2284 StartReadingJournal(aProofOfLock);
2285 } else {
2286 FinishRead(false, aProofOfLock);
2289 return;
2292 pos = mRWBufPos;
2293 uint32_t toRead =
2294 std::min(mRWBufSize - pos,
2295 static_cast<uint32_t>(mIndexHandle->FileSize() - fileOffset));
2296 mRWBufPos = pos + toRead;
2298 rv = CacheFileIOManager::Read(mIndexHandle, fileOffset, mRWBuf + pos, toRead,
2299 this);
2300 if (NS_FAILED(rv)) {
2301 LOG(
2302 ("CacheIndex::ParseRecords() - CacheFileIOManager::Read() failed "
2303 "synchronously [rv=0x%08" PRIx32 "]",
2304 static_cast<uint32_t>(rv)));
2305 FinishRead(false, aProofOfLock);
2306 return;
2308 mRWPending = true;
2311 void CacheIndex::StartReadingJournal(const StaticMutexAutoLock& aProofOfLock) {
2312 sLock.AssertCurrentThreadOwns();
2313 LOG(("CacheIndex::StartReadingJournal()"));
2315 nsresult rv;
2317 MOZ_ASSERT(mJournalHandle);
2318 MOZ_ASSERT(mIndexOnDiskIsValid);
2319 MOZ_ASSERT(mTmpJournal.Count() == 0);
2320 MOZ_ASSERT(mJournalHandle->FileSize() >= 0);
2321 MOZ_ASSERT(!mRWPending);
2323 int64_t entriesSize =
2324 mJournalHandle->FileSize() - sizeof(CacheHash::Hash32_t);
2326 if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
2327 LOG(("CacheIndex::StartReadingJournal() - Journal is corrupted"));
2328 FinishRead(false, aProofOfLock);
2329 return;
2332 mSkipEntries = 0;
2333 mRWHash = new CacheHash();
2335 mRWBufPos =
2336 std::min(mRWBufSize, static_cast<uint32_t>(mJournalHandle->FileSize()));
2338 rv = CacheFileIOManager::Read(mJournalHandle, 0, mRWBuf, mRWBufPos, this);
2339 if (NS_FAILED(rv)) {
2340 LOG(
2341 ("CacheIndex::StartReadingJournal() - CacheFileIOManager::Read() failed"
2342 " synchronously [rv=0x%08" PRIx32 "]",
2343 static_cast<uint32_t>(rv)));
2344 FinishRead(false, aProofOfLock);
2345 } else {
2346 mRWPending = true;
2350 void CacheIndex::ParseJournal(const StaticMutexAutoLock& aProofOfLock) {
2351 sLock.AssertCurrentThreadOwns();
2352 LOG(("CacheIndex::ParseJournal()"));
2354 nsresult rv;
2356 MOZ_ASSERT(!mRWPending);
2358 uint32_t entryCnt =
2359 (mJournalHandle->FileSize() - sizeof(CacheHash::Hash32_t)) /
2360 sizeof(CacheIndexRecord);
2362 uint32_t pos = 0;
2364 while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
2365 mSkipEntries != entryCnt) {
2366 CacheIndexEntry tmpEntry(reinterpret_cast<SHA1Sum::Hash*>(mRWBuf + pos));
2367 tmpEntry.ReadFromBuf(mRWBuf + pos);
2369 CacheIndexEntry* entry = mTmpJournal.PutEntry(*tmpEntry.Hash());
2370 *entry = tmpEntry;
2372 if (entry->IsDirty() || entry->IsFresh()) {
2373 LOG(
2374 ("CacheIndex::ParseJournal() - Invalid entry found in journal, "
2375 "ignoring whole journal [dirty=%d, fresh=%d]",
2376 entry->IsDirty(), entry->IsFresh()));
2377 FinishRead(false, aProofOfLock);
2378 return;
2381 pos += sizeof(CacheIndexRecord);
2382 mSkipEntries++;
2385 mRWHash->Update(mRWBuf, pos);
2387 if (pos != mRWBufPos) {
2388 memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
2391 mRWBufPos -= pos;
2392 pos = 0;
2394 int64_t fileOffset = mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
2396 MOZ_ASSERT(fileOffset <= mJournalHandle->FileSize());
2397 if (fileOffset == mJournalHandle->FileSize()) {
2398 uint32_t expectedHash = NetworkEndian::readUint32(mRWBuf);
2399 if (mRWHash->GetHash() != expectedHash) {
2400 LOG(("CacheIndex::ParseJournal() - Hash mismatch, [is %x, should be %x]",
2401 mRWHash->GetHash(), expectedHash));
2402 FinishRead(false, aProofOfLock);
2403 return;
2406 mJournalReadSuccessfully = true;
2407 FinishRead(true, aProofOfLock);
2408 return;
2411 pos = mRWBufPos;
2412 uint32_t toRead =
2413 std::min(mRWBufSize - pos,
2414 static_cast<uint32_t>(mJournalHandle->FileSize() - fileOffset));
2415 mRWBufPos = pos + toRead;
2417 rv = CacheFileIOManager::Read(mJournalHandle, fileOffset, mRWBuf + pos,
2418 toRead, this);
2419 if (NS_FAILED(rv)) {
2420 LOG(
2421 ("CacheIndex::ParseJournal() - CacheFileIOManager::Read() failed "
2422 "synchronously [rv=0x%08" PRIx32 "]",
2423 static_cast<uint32_t>(rv)));
2424 FinishRead(false, aProofOfLock);
2425 return;
2427 mRWPending = true;
2430 void CacheIndex::MergeJournal(const StaticMutexAutoLock& aProofOfLock) {
2431 sLock.AssertCurrentThreadOwns();
2432 LOG(("CacheIndex::MergeJournal()"));
2434 for (auto iter = mTmpJournal.Iter(); !iter.Done(); iter.Next()) {
2435 CacheIndexEntry* entry = iter.Get();
2437 LOG(("CacheIndex::MergeJournal() [hash=%08x%08x%08x%08x%08x]",
2438 LOGSHA1(entry->Hash())));
2440 CacheIndexEntry* entry2 = mIndex.GetEntry(*entry->Hash());
2442 CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
2443 if (entry->IsRemoved()) {
2444 if (entry2) {
2445 entry2->MarkRemoved();
2446 entry2->MarkDirty();
2448 } else {
2449 if (!entry2) {
2450 entry2 = mIndex.PutEntry(*entry->Hash());
2453 *entry2 = *entry;
2454 entry2->MarkDirty();
2457 iter.Remove();
2460 MOZ_ASSERT(mTmpJournal.Count() == 0);
2463 void CacheIndex::EnsureNoFreshEntry() {
2464 #ifdef DEBUG_STATS
2465 CacheIndexStats debugStats;
2466 debugStats.DisableLogging();
2467 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
2468 debugStats.BeforeChange(nullptr);
2469 debugStats.AfterChange(iter.Get());
2471 MOZ_ASSERT(debugStats.Fresh() == 0);
2472 #endif
2475 void CacheIndex::EnsureCorrectStats() {
2476 #ifdef DEBUG_STATS
2477 MOZ_ASSERT(mPendingUpdates.Count() == 0);
2478 CacheIndexStats debugStats;
2479 debugStats.DisableLogging();
2480 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
2481 debugStats.BeforeChange(nullptr);
2482 debugStats.AfterChange(iter.Get());
2484 MOZ_ASSERT(debugStats == mIndexStats);
2485 #endif
2488 void CacheIndex::FinishRead(bool aSucceeded,
2489 const StaticMutexAutoLock& aProofOfLock) {
2490 sLock.AssertCurrentThreadOwns();
2491 LOG(("CacheIndex::FinishRead() [succeeded=%d]", aSucceeded));
2493 MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == READING);
2495 MOZ_ASSERT(
2496 // -> rebuild
2497 (!aSucceeded && !mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
2498 // -> update
2499 (!aSucceeded && mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
2500 // -> ready
2501 (aSucceeded && mIndexOnDiskIsValid && mJournalReadSuccessfully));
2503 // If there is read operation pending we must be cancelling reading of the
2504 // index when shutting down or removing the whole index.
2505 MOZ_ASSERT(!mRWPending || (!aSucceeded && (mShuttingDown || mRemovingAll)));
2507 if (mState == SHUTDOWN) {
2508 RemoveFile(nsLiteralCString(TEMP_INDEX_NAME));
2509 RemoveFile(nsLiteralCString(JOURNAL_NAME));
2510 } else {
2511 if (mIndexHandle && !mIndexOnDiskIsValid) {
2512 CacheFileIOManager::DoomFile(mIndexHandle, nullptr);
2515 if (mJournalHandle) {
2516 CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
2520 if (mIndexFileOpener) {
2521 mIndexFileOpener->Cancel();
2522 mIndexFileOpener = nullptr;
2524 if (mJournalFileOpener) {
2525 mJournalFileOpener->Cancel();
2526 mJournalFileOpener = nullptr;
2528 if (mTmpFileOpener) {
2529 mTmpFileOpener->Cancel();
2530 mTmpFileOpener = nullptr;
2533 mIndexHandle = nullptr;
2534 mJournalHandle = nullptr;
2535 mRWHash = nullptr;
2536 ReleaseBuffer();
2538 if (mState == SHUTDOWN) {
2539 return;
2542 if (!mIndexOnDiskIsValid) {
2543 MOZ_ASSERT(mTmpJournal.Count() == 0);
2544 EnsureNoFreshEntry();
2545 ProcessPendingOperations(aProofOfLock);
2546 // Remove all entries that we haven't seen during this session
2547 RemoveNonFreshEntries(aProofOfLock);
2548 StartUpdatingIndex(true, aProofOfLock);
2549 return;
2552 if (!mJournalReadSuccessfully) {
2553 mTmpJournal.Clear();
2554 EnsureNoFreshEntry();
2555 ProcessPendingOperations(aProofOfLock);
2556 StartUpdatingIndex(false, aProofOfLock);
2557 return;
2560 MergeJournal(aProofOfLock);
2561 EnsureNoFreshEntry();
2562 ProcessPendingOperations(aProofOfLock);
2563 mIndexStats.Log();
2565 ChangeState(READY, aProofOfLock);
2566 mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
2569 // static
2570 void CacheIndex::DelayedUpdate(nsITimer* aTimer, void* aClosure) {
2571 LOG(("CacheIndex::DelayedUpdate()"));
2573 StaticMutexAutoLock lock(sLock);
2574 RefPtr<CacheIndex> index = gInstance;
2576 if (!index) {
2577 return;
2580 index->DelayedUpdateLocked(lock);
2583 // static
2584 void CacheIndex::DelayedUpdateLocked(const StaticMutexAutoLock& aProofOfLock) {
2585 sLock.AssertCurrentThreadOwns();
2586 LOG(("CacheIndex::DelayedUpdateLocked()"));
2588 nsresult rv;
2590 mUpdateTimer = nullptr;
2592 if (!IsIndexUsable()) {
2593 return;
2596 if (mState == READY && mShuttingDown) {
2597 return;
2600 // mUpdateEventPending must be false here since StartUpdatingIndex() won't
2601 // schedule timer if it is true.
2602 MOZ_ASSERT(!mUpdateEventPending);
2603 if (mState != BUILDING && mState != UPDATING) {
2604 LOG(("CacheIndex::DelayedUpdateLocked() - Update was canceled"));
2605 return;
2608 // We need to redispatch to run with lower priority
2609 RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
2610 MOZ_ASSERT(ioThread);
2612 mUpdateEventPending = true;
2613 rv = ioThread->Dispatch(this, CacheIOThread::INDEX);
2614 if (NS_FAILED(rv)) {
2615 mUpdateEventPending = false;
2616 NS_WARNING("CacheIndex::DelayedUpdateLocked() - Can't dispatch event");
2617 LOG(("CacheIndex::DelayedUpdate() - Can't dispatch event"));
2618 FinishUpdate(false, aProofOfLock);
2622 nsresult CacheIndex::ScheduleUpdateTimer(uint32_t aDelay) {
2623 LOG(("CacheIndex::ScheduleUpdateTimer() [delay=%u]", aDelay));
2625 MOZ_ASSERT(!mUpdateTimer);
2627 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
2628 MOZ_ASSERT(ioTarget);
2630 return NS_NewTimerWithFuncCallback(
2631 getter_AddRefs(mUpdateTimer), CacheIndex::DelayedUpdate, nullptr, aDelay,
2632 nsITimer::TYPE_ONE_SHOT, "net::CacheIndex::ScheduleUpdateTimer",
2633 ioTarget);
2636 nsresult CacheIndex::SetupDirectoryEnumerator() {
2637 MOZ_ASSERT(!NS_IsMainThread());
2638 MOZ_ASSERT(!mDirEnumerator);
2640 nsresult rv;
2641 nsCOMPtr<nsIFile> file;
2643 rv = mCacheDirectory->Clone(getter_AddRefs(file));
2644 NS_ENSURE_SUCCESS(rv, rv);
2646 rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR));
2647 NS_ENSURE_SUCCESS(rv, rv);
2649 bool exists;
2650 rv = file->Exists(&exists);
2651 NS_ENSURE_SUCCESS(rv, rv);
2653 if (!exists) {
2654 NS_WARNING(
2655 "CacheIndex::SetupDirectoryEnumerator() - Entries directory "
2656 "doesn't exist!");
2657 LOG(
2658 ("CacheIndex::SetupDirectoryEnumerator() - Entries directory doesn't "
2659 "exist!"));
2660 return NS_ERROR_UNEXPECTED;
2663 // Do not do IO under the lock.
2664 nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator;
2666 StaticMutexAutoUnlock unlock(sLock);
2667 rv = file->GetDirectoryEntries(getter_AddRefs(dirEnumerator));
2669 mDirEnumerator = dirEnumerator.forget();
2670 NS_ENSURE_SUCCESS(rv, rv);
2672 return NS_OK;
2675 nsresult CacheIndex::InitEntryFromDiskData(CacheIndexEntry* aEntry,
2676 CacheFileMetadata* aMetaData,
2677 int64_t aFileSize) {
2678 nsresult rv;
2680 aEntry->InitNew();
2681 aEntry->MarkDirty();
2682 aEntry->MarkFresh();
2684 aEntry->Init(GetOriginAttrsHash(aMetaData->OriginAttributes()),
2685 aMetaData->IsAnonymous(), aMetaData->Pinned());
2687 aEntry->SetFrecency(aMetaData->GetFrecency());
2689 const char* altData = aMetaData->GetElement(CacheFileUtils::kAltDataKey);
2690 bool hasAltData = altData != nullptr;
2691 if (hasAltData && NS_FAILED(CacheFileUtils::ParseAlternativeDataInfo(
2692 altData, nullptr, nullptr))) {
2693 return NS_ERROR_FAILURE;
2695 aEntry->SetHasAltData(hasAltData);
2697 static auto toUint16 = [](const char* aUint16String) -> uint16_t {
2698 if (!aUint16String) {
2699 return kIndexTimeNotAvailable;
2701 nsresult rv;
2702 uint64_t n64 = nsDependentCString(aUint16String).ToInteger64(&rv);
2703 MOZ_ASSERT(NS_SUCCEEDED(rv));
2704 return n64 <= kIndexTimeOutOfBound ? n64 : kIndexTimeOutOfBound;
2707 aEntry->SetOnStartTime(
2708 toUint16(aMetaData->GetElement("net-response-time-onstart")));
2709 aEntry->SetOnStopTime(
2710 toUint16(aMetaData->GetElement("net-response-time-onstop")));
2712 const char* contentTypeStr = aMetaData->GetElement("ctid");
2713 uint8_t contentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
2714 if (contentTypeStr) {
2715 int64_t n64 = nsDependentCString(contentTypeStr).ToInteger64(&rv);
2716 if (NS_FAILED(rv) || n64 < nsICacheEntry::CONTENT_TYPE_UNKNOWN ||
2717 n64 >= nsICacheEntry::CONTENT_TYPE_LAST) {
2718 n64 = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
2720 contentType = n64;
2722 aEntry->SetContentType(contentType);
2724 aEntry->SetFileSize(static_cast<uint32_t>(std::min(
2725 static_cast<int64_t>(PR_UINT32_MAX), (aFileSize + 0x3FF) >> 10)));
2726 return NS_OK;
2729 bool CacheIndex::IsUpdatePending() {
2730 sLock.AssertCurrentThreadOwns();
2732 return mUpdateTimer || mUpdateEventPending;
2735 void CacheIndex::BuildIndex(const StaticMutexAutoLock& aProofOfLock) {
2736 sLock.AssertCurrentThreadOwns();
2737 LOG(("CacheIndex::BuildIndex()"));
2739 MOZ_ASSERT(mPendingUpdates.Count() == 0);
2741 nsresult rv;
2743 if (!mDirEnumerator) {
2744 rv = SetupDirectoryEnumerator();
2745 if (mState == SHUTDOWN) {
2746 // The index was shut down while we released the lock. FinishUpdate() was
2747 // already called from Shutdown(), so just simply return here.
2748 return;
2751 if (NS_FAILED(rv)) {
2752 FinishUpdate(false, aProofOfLock);
2753 return;
2757 while (true) {
2758 if (CacheIOThread::YieldAndRerun()) {
2759 LOG((
2760 "CacheIndex::BuildIndex() - Breaking loop for higher level events."));
2761 mUpdateEventPending = true;
2762 return;
2765 bool fileExists = false;
2766 nsCOMPtr<nsIFile> file;
2768 // Do not do IO under the lock.
2769 nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator(mDirEnumerator);
2770 sLock.AssertCurrentThreadOwns();
2771 StaticMutexAutoUnlock unlock(sLock);
2772 rv = dirEnumerator->GetNextFile(getter_AddRefs(file));
2774 if (file) {
2775 file->Exists(&fileExists);
2778 if (mState == SHUTDOWN) {
2779 return;
2781 if (!file) {
2782 FinishUpdate(NS_SUCCEEDED(rv), aProofOfLock);
2783 return;
2786 nsAutoCString leaf;
2787 rv = file->GetNativeLeafName(leaf);
2788 if (NS_FAILED(rv)) {
2789 LOG(
2790 ("CacheIndex::BuildIndex() - GetNativeLeafName() failed! Skipping "
2791 "file."));
2792 mDontMarkIndexClean = true;
2793 continue;
2796 if (!fileExists) {
2797 LOG(
2798 ("CacheIndex::BuildIndex() - File returned by the iterator was "
2799 "removed in the meantime [name=%s]",
2800 leaf.get()));
2801 continue;
2804 SHA1Sum::Hash hash;
2805 rv = CacheFileIOManager::StrToHash(leaf, &hash);
2806 if (NS_FAILED(rv)) {
2807 LOG(
2808 ("CacheIndex::BuildIndex() - Filename is not a hash, removing file. "
2809 "[name=%s]",
2810 leaf.get()));
2811 file->Remove(false);
2812 continue;
2815 CacheIndexEntry* entry = mIndex.GetEntry(hash);
2816 if (entry && entry->IsRemoved()) {
2817 LOG(
2818 ("CacheIndex::BuildIndex() - Found file that should not exist. "
2819 "[name=%s]",
2820 leaf.get()));
2821 entry->Log();
2822 MOZ_ASSERT(entry->IsFresh());
2823 entry = nullptr;
2826 #ifdef DEBUG
2827 RefPtr<CacheFileHandle> handle;
2828 CacheFileIOManager::gInstance->mHandles.GetHandle(&hash,
2829 getter_AddRefs(handle));
2830 #endif
2832 if (entry) {
2833 // the entry is up to date
2834 LOG(
2835 ("CacheIndex::BuildIndex() - Skipping file because the entry is up to"
2836 " date. [name=%s]",
2837 leaf.get()));
2838 entry->Log();
2839 MOZ_ASSERT(entry->IsFresh()); // The entry must be from this session
2840 // there must be an active CacheFile if the entry is not initialized
2841 MOZ_ASSERT(entry->IsInitialized() || handle);
2842 continue;
2845 MOZ_ASSERT(!handle);
2847 RefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
2848 int64_t size = 0;
2851 // Do not do IO under the lock.
2852 StaticMutexAutoUnlock unlock(sLock);
2853 rv = meta->SyncReadMetadata(file);
2855 if (NS_SUCCEEDED(rv)) {
2856 rv = file->GetFileSize(&size);
2857 if (NS_FAILED(rv)) {
2858 LOG(
2859 ("CacheIndex::BuildIndex() - Cannot get filesize of file that was"
2860 " successfully parsed. [name=%s]",
2861 leaf.get()));
2865 if (mState == SHUTDOWN) {
2866 return;
2869 // Nobody could add the entry while the lock was released since we modify
2870 // the index only on IO thread and this loop is executed on IO thread too.
2871 entry = mIndex.GetEntry(hash);
2872 MOZ_ASSERT(!entry || entry->IsRemoved());
2874 if (NS_FAILED(rv)) {
2875 LOG(
2876 ("CacheIndex::BuildIndex() - CacheFileMetadata::SyncReadMetadata() "
2877 "failed, removing file. [name=%s]",
2878 leaf.get()));
2879 file->Remove(false);
2880 } else {
2881 CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
2882 entry = mIndex.PutEntry(hash);
2883 if (NS_FAILED(InitEntryFromDiskData(entry, meta, size))) {
2884 LOG(
2885 ("CacheIndex::BuildIndex() - CacheFile::InitEntryFromDiskData() "
2886 "failed, removing file. [name=%s]",
2887 leaf.get()));
2888 file->Remove(false);
2889 entry->MarkRemoved();
2890 } else {
2891 LOG(("CacheIndex::BuildIndex() - Added entry to index. [name=%s]",
2892 leaf.get()));
2893 entry->Log();
2898 MOZ_ASSERT_UNREACHABLE("We should never get here");
2901 bool CacheIndex::StartUpdatingIndexIfNeeded(
2902 const StaticMutexAutoLock& aProofOfLock, bool aSwitchingToReadyState) {
2903 sLock.AssertCurrentThreadOwns();
2904 // Start updating process when we are in or we are switching to READY state
2905 // and index needs update, but not during shutdown or when removing all
2906 // entries.
2907 if ((mState == READY || aSwitchingToReadyState) && mIndexNeedsUpdate &&
2908 !mShuttingDown && !mRemovingAll) {
2909 LOG(("CacheIndex::StartUpdatingIndexIfNeeded() - starting update process"));
2910 mIndexNeedsUpdate = false;
2911 StartUpdatingIndex(false, aProofOfLock);
2912 return true;
2915 return false;
2918 void CacheIndex::StartUpdatingIndex(bool aRebuild,
2919 const StaticMutexAutoLock& aProofOfLock) {
2920 sLock.AssertCurrentThreadOwns();
2921 LOG(("CacheIndex::StartUpdatingIndex() [rebuild=%d]", aRebuild));
2923 nsresult rv;
2925 mIndexStats.Log();
2927 ChangeState(aRebuild ? BUILDING : UPDATING, aProofOfLock);
2928 mDontMarkIndexClean = false;
2930 if (mShuttingDown || mRemovingAll) {
2931 FinishUpdate(false, aProofOfLock);
2932 return;
2935 if (IsUpdatePending()) {
2936 LOG(("CacheIndex::StartUpdatingIndex() - Update is already pending"));
2937 return;
2940 uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds();
2941 if (elapsed < kUpdateIndexStartDelay) {
2942 LOG(
2943 ("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
2944 "scheduling timer to fire in %u ms.",
2945 elapsed, kUpdateIndexStartDelay - elapsed));
2946 rv = ScheduleUpdateTimer(kUpdateIndexStartDelay - elapsed);
2947 if (NS_SUCCEEDED(rv)) {
2948 return;
2951 LOG(
2952 ("CacheIndex::StartUpdatingIndex() - ScheduleUpdateTimer() failed. "
2953 "Starting update immediately."));
2954 } else {
2955 LOG(
2956 ("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
2957 "starting update now.",
2958 elapsed));
2961 RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
2962 MOZ_ASSERT(ioThread);
2964 // We need to dispatch an event even if we are on IO thread since we need to
2965 // update the index with the correct priority.
2966 mUpdateEventPending = true;
2967 rv = ioThread->Dispatch(this, CacheIOThread::INDEX);
2968 if (NS_FAILED(rv)) {
2969 mUpdateEventPending = false;
2970 NS_WARNING("CacheIndex::StartUpdatingIndex() - Can't dispatch event");
2971 LOG(("CacheIndex::StartUpdatingIndex() - Can't dispatch event"));
2972 FinishUpdate(false, aProofOfLock);
2976 void CacheIndex::UpdateIndex(const StaticMutexAutoLock& aProofOfLock) {
2977 sLock.AssertCurrentThreadOwns();
2978 LOG(("CacheIndex::UpdateIndex()"));
2980 MOZ_ASSERT(mPendingUpdates.Count() == 0);
2981 sLock.AssertCurrentThreadOwns();
2983 nsresult rv;
2985 if (!mDirEnumerator) {
2986 rv = SetupDirectoryEnumerator();
2987 if (mState == SHUTDOWN) {
2988 // The index was shut down while we released the lock. FinishUpdate() was
2989 // already called from Shutdown(), so just simply return here.
2990 return;
2993 if (NS_FAILED(rv)) {
2994 FinishUpdate(false, aProofOfLock);
2995 return;
2999 while (true) {
3000 if (CacheIOThread::YieldAndRerun()) {
3001 LOG(
3002 ("CacheIndex::UpdateIndex() - Breaking loop for higher level "
3003 "events."));
3004 mUpdateEventPending = true;
3005 return;
3008 bool fileExists = false;
3009 nsCOMPtr<nsIFile> file;
3011 // Do not do IO under the lock.
3012 nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator(mDirEnumerator);
3013 StaticMutexAutoUnlock unlock(sLock);
3014 rv = dirEnumerator->GetNextFile(getter_AddRefs(file));
3016 if (file) {
3017 file->Exists(&fileExists);
3020 if (mState == SHUTDOWN) {
3021 return;
3023 if (!file) {
3024 FinishUpdate(NS_SUCCEEDED(rv), aProofOfLock);
3025 return;
3028 nsAutoCString leaf;
3029 rv = file->GetNativeLeafName(leaf);
3030 if (NS_FAILED(rv)) {
3031 LOG(
3032 ("CacheIndex::UpdateIndex() - GetNativeLeafName() failed! Skipping "
3033 "file."));
3034 mDontMarkIndexClean = true;
3035 continue;
3038 if (!fileExists) {
3039 LOG(
3040 ("CacheIndex::UpdateIndex() - File returned by the iterator was "
3041 "removed in the meantime [name=%s]",
3042 leaf.get()));
3043 continue;
3046 SHA1Sum::Hash hash;
3047 rv = CacheFileIOManager::StrToHash(leaf, &hash);
3048 if (NS_FAILED(rv)) {
3049 LOG(
3050 ("CacheIndex::UpdateIndex() - Filename is not a hash, removing file. "
3051 "[name=%s]",
3052 leaf.get()));
3053 file->Remove(false);
3054 continue;
3057 CacheIndexEntry* entry = mIndex.GetEntry(hash);
3058 if (entry && entry->IsRemoved()) {
3059 if (entry->IsFresh()) {
3060 LOG(
3061 ("CacheIndex::UpdateIndex() - Found file that should not exist. "
3062 "[name=%s]",
3063 leaf.get()));
3064 entry->Log();
3066 entry = nullptr;
3069 #ifdef DEBUG
3070 RefPtr<CacheFileHandle> handle;
3071 CacheFileIOManager::gInstance->mHandles.GetHandle(&hash,
3072 getter_AddRefs(handle));
3073 #endif
3075 if (entry && entry->IsFresh()) {
3076 // the entry is up to date
3077 LOG(
3078 ("CacheIndex::UpdateIndex() - Skipping file because the entry is up "
3079 " to date. [name=%s]",
3080 leaf.get()));
3081 entry->Log();
3082 // there must be an active CacheFile if the entry is not initialized
3083 MOZ_ASSERT(entry->IsInitialized() || handle);
3084 continue;
3087 MOZ_ASSERT(!handle);
3089 if (entry) {
3090 PRTime lastModifiedTime;
3092 // Do not do IO under the lock.
3093 StaticMutexAutoUnlock unlock(sLock);
3094 rv = file->GetLastModifiedTime(&lastModifiedTime);
3096 if (mState == SHUTDOWN) {
3097 return;
3099 if (NS_FAILED(rv)) {
3100 LOG(
3101 ("CacheIndex::UpdateIndex() - Cannot get lastModifiedTime. "
3102 "[name=%s]",
3103 leaf.get()));
3104 // Assume the file is newer than index
3105 } else {
3106 if (mIndexTimeStamp > (lastModifiedTime / PR_MSEC_PER_SEC)) {
3107 LOG(
3108 ("CacheIndex::UpdateIndex() - Skipping file because of last "
3109 "modified time. [name=%s, indexTimeStamp=%" PRIu32 ", "
3110 "lastModifiedTime=%" PRId64 "]",
3111 leaf.get(), mIndexTimeStamp,
3112 lastModifiedTime / PR_MSEC_PER_SEC));
3114 CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
3115 entry->MarkFresh();
3116 continue;
3121 RefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
3122 int64_t size = 0;
3125 // Do not do IO under the lock.
3126 StaticMutexAutoUnlock unlock(sLock);
3127 rv = meta->SyncReadMetadata(file);
3129 if (NS_SUCCEEDED(rv)) {
3130 rv = file->GetFileSize(&size);
3131 if (NS_FAILED(rv)) {
3132 LOG(
3133 ("CacheIndex::UpdateIndex() - Cannot get filesize of file that "
3134 "was successfully parsed. [name=%s]",
3135 leaf.get()));
3139 if (mState == SHUTDOWN) {
3140 return;
3143 // Nobody could add the entry while the lock was released since we modify
3144 // the index only on IO thread and this loop is executed on IO thread too.
3145 entry = mIndex.GetEntry(hash);
3146 MOZ_ASSERT(!entry || !entry->IsFresh());
3148 CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
3150 if (NS_FAILED(rv)) {
3151 LOG(
3152 ("CacheIndex::UpdateIndex() - CacheFileMetadata::SyncReadMetadata() "
3153 "failed, removing file. [name=%s]",
3154 leaf.get()));
3155 } else {
3156 entry = mIndex.PutEntry(hash);
3157 rv = InitEntryFromDiskData(entry, meta, size);
3158 if (NS_FAILED(rv)) {
3159 LOG(
3160 ("CacheIndex::UpdateIndex() - CacheIndex::InitEntryFromDiskData "
3161 "failed, removing file. [name=%s]",
3162 leaf.get()));
3166 if (NS_FAILED(rv)) {
3167 file->Remove(false);
3168 if (entry) {
3169 entry->MarkRemoved();
3170 entry->MarkFresh();
3171 entry->MarkDirty();
3173 } else {
3174 LOG(
3175 ("CacheIndex::UpdateIndex() - Added/updated entry to/in index. "
3176 "[name=%s]",
3177 leaf.get()));
3178 entry->Log();
3182 MOZ_ASSERT_UNREACHABLE("We should never get here");
3185 void CacheIndex::FinishUpdate(bool aSucceeded,
3186 const StaticMutexAutoLock& aProofOfLock) {
3187 LOG(("CacheIndex::FinishUpdate() [succeeded=%d]", aSucceeded));
3189 MOZ_ASSERT(mState == UPDATING || mState == BUILDING ||
3190 (!aSucceeded && mState == SHUTDOWN));
3192 if (mDirEnumerator) {
3193 if (NS_IsMainThread()) {
3194 LOG(
3195 ("CacheIndex::FinishUpdate() - posting of PreShutdownInternal failed?"
3196 " Cannot safely release mDirEnumerator, leaking it!"));
3197 NS_WARNING(("CacheIndex::FinishUpdate() - Leaking mDirEnumerator!"));
3198 // This can happen only in case dispatching event to IO thread failed in
3199 // CacheIndex::PreShutdown().
3200 Unused << mDirEnumerator.forget(); // Leak it since dir enumerator is not
3201 // threadsafe
3202 } else {
3203 mDirEnumerator->Close();
3204 mDirEnumerator = nullptr;
3208 if (!aSucceeded) {
3209 mDontMarkIndexClean = true;
3212 if (mState == SHUTDOWN) {
3213 return;
3216 if (mState == UPDATING && aSucceeded) {
3217 // If we've iterated over all entries successfully then all entries that
3218 // really exist on the disk are now marked as fresh. All non-fresh entries
3219 // don't exist anymore and must be removed from the index.
3220 RemoveNonFreshEntries(aProofOfLock);
3223 // Make sure we won't start update. If the build or update failed, there is no
3224 // reason to believe that it will succeed next time.
3225 mIndexNeedsUpdate = false;
3227 ChangeState(READY, aProofOfLock);
3228 mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
3231 void CacheIndex::RemoveNonFreshEntries(
3232 const StaticMutexAutoLock& aProofOfLock) {
3233 sLock.AssertCurrentThreadOwns();
3234 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
3235 CacheIndexEntry* entry = iter.Get();
3236 if (entry->IsFresh()) {
3237 continue;
3240 LOG(
3241 ("CacheIndex::RemoveNonFreshEntries() - Removing entry. "
3242 "[hash=%08x%08x%08x%08x%08x]",
3243 LOGSHA1(entry->Hash())));
3246 CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
3247 emng.DoNotSearchInIndex();
3250 iter.Remove();
3254 // static
3255 char const* CacheIndex::StateString(EState aState) {
3256 switch (aState) {
3257 case INITIAL:
3258 return "INITIAL";
3259 case READING:
3260 return "READING";
3261 case WRITING:
3262 return "WRITING";
3263 case BUILDING:
3264 return "BUILDING";
3265 case UPDATING:
3266 return "UPDATING";
3267 case READY:
3268 return "READY";
3269 case SHUTDOWN:
3270 return "SHUTDOWN";
3273 MOZ_ASSERT(false, "Unexpected state!");
3274 return "?";
3277 void CacheIndex::ChangeState(EState aNewState,
3278 const StaticMutexAutoLock& aProofOfLock) {
3279 sLock.AssertCurrentThreadOwns();
3280 LOG(("CacheIndex::ChangeState() changing state %s -> %s", StateString(mState),
3281 StateString(aNewState)));
3283 // All pending updates should be processed before changing state
3284 MOZ_ASSERT(mPendingUpdates.Count() == 0);
3286 // PreShutdownInternal() should change the state to READY from every state. It
3287 // may go through different states, but once we are in READY state the only
3288 // possible transition is to SHUTDOWN state.
3289 MOZ_ASSERT(!mShuttingDown || mState != READY || aNewState == SHUTDOWN);
3291 // Start updating process when switching to READY state if needed
3292 if (aNewState == READY && StartUpdatingIndexIfNeeded(aProofOfLock, true)) {
3293 return;
3296 // Try to evict entries over limit everytime we're leaving state READING,
3297 // BUILDING or UPDATING, but not during shutdown or when removing all
3298 // entries.
3299 if (!mShuttingDown && !mRemovingAll && aNewState != SHUTDOWN &&
3300 (mState == READING || mState == BUILDING || mState == UPDATING)) {
3301 CacheFileIOManager::EvictIfOverLimit();
3304 mState = aNewState;
3306 if (mState != SHUTDOWN) {
3307 CacheFileIOManager::CacheIndexStateChanged();
3310 NotifyAsyncGetDiskConsumptionCallbacks();
3313 void CacheIndex::NotifyAsyncGetDiskConsumptionCallbacks() {
3314 if ((mState == READY || mState == WRITING) &&
3315 !mAsyncGetDiskConsumptionBlocked && mDiskConsumptionObservers.Length()) {
3316 for (uint32_t i = 0; i < mDiskConsumptionObservers.Length(); ++i) {
3317 DiskConsumptionObserver* o = mDiskConsumptionObservers[i];
3318 // Safe to call under the lock. We always post to the main thread.
3319 o->OnDiskConsumption(mIndexStats.Size() << 10);
3322 mDiskConsumptionObservers.Clear();
3326 void CacheIndex::AllocBuffer() {
3327 switch (mState) {
3328 case WRITING:
3329 mRWBufSize = sizeof(CacheIndexHeader) + sizeof(CacheHash::Hash32_t) +
3330 mProcessEntries * sizeof(CacheIndexRecord);
3331 if (mRWBufSize > kMaxBufSize) {
3332 mRWBufSize = kMaxBufSize;
3334 break;
3335 case READING:
3336 mRWBufSize = kMaxBufSize;
3337 break;
3338 default:
3339 MOZ_ASSERT(false, "Unexpected state!");
3342 mRWBuf = static_cast<char*>(moz_xmalloc(mRWBufSize));
3345 void CacheIndex::ReleaseBuffer() {
3346 sLock.AssertCurrentThreadOwns();
3348 if (!mRWBuf || mRWPending) {
3349 return;
3352 LOG(("CacheIndex::ReleaseBuffer() releasing buffer"));
3354 free(mRWBuf);
3355 mRWBuf = nullptr;
3356 mRWBufSize = 0;
3357 mRWBufPos = 0;
3360 void CacheIndex::FrecencyArray::AppendRecord(
3361 CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) {
3362 sLock.AssertCurrentThreadOwns();
3363 LOG(
3364 ("CacheIndex::FrecencyArray::AppendRecord() [record=%p, hash=%08x%08x%08x"
3365 "%08x%08x]",
3366 aRecord, LOGSHA1(aRecord->Get()->mHash)));
3368 MOZ_DIAGNOSTIC_ASSERT(!mRecs.Contains(aRecord));
3369 mRecs.AppendElement(aRecord);
3371 // If the new frecency is 0, the element should be at the end of the array,
3372 // i.e. this change doesn't affect order of the array
3373 if (aRecord->Get()->mFrecency != 0) {
3374 ++mUnsortedElements;
3378 void CacheIndex::FrecencyArray::RemoveRecord(
3379 CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) {
3380 sLock.AssertCurrentThreadOwns();
3381 LOG(("CacheIndex::FrecencyArray::RemoveRecord() [record=%p]", aRecord));
3383 decltype(mRecs)::index_type idx;
3384 idx = mRecs.IndexOf(aRecord);
3385 MOZ_RELEASE_ASSERT(idx != mRecs.NoIndex);
3386 // sanity check to ensure correct record removal
3387 MOZ_RELEASE_ASSERT(mRecs[idx] == aRecord);
3388 mRecs[idx] = nullptr;
3389 ++mRemovedElements;
3391 // Calling SortIfNeeded ensures that we get rid of removed elements in the
3392 // array once we hit the limit.
3393 SortIfNeeded(aProofOfLock);
3396 void CacheIndex::FrecencyArray::ReplaceRecord(
3397 CacheIndexRecordWrapper* aOldRecord, CacheIndexRecordWrapper* aNewRecord,
3398 const StaticMutexAutoLock& aProofOfLock) {
3399 sLock.AssertCurrentThreadOwns();
3400 LOG(
3401 ("CacheIndex::FrecencyArray::ReplaceRecord() [oldRecord=%p, "
3402 "newRecord=%p]",
3403 aOldRecord, aNewRecord));
3405 decltype(mRecs)::index_type idx;
3406 idx = mRecs.IndexOf(aOldRecord);
3407 MOZ_RELEASE_ASSERT(idx != mRecs.NoIndex);
3408 // sanity check to ensure correct record replaced
3409 MOZ_RELEASE_ASSERT(mRecs[idx] == aOldRecord);
3410 mRecs[idx] = aNewRecord;
3413 void CacheIndex::FrecencyArray::SortIfNeeded(
3414 const StaticMutexAutoLock& aProofOfLock) {
3415 sLock.AssertCurrentThreadOwns();
3416 const uint32_t kMaxUnsortedCount = 512;
3417 const uint32_t kMaxUnsortedPercent = 10;
3418 const uint32_t kMaxRemovedCount = 512;
3420 uint32_t unsortedLimit = std::min<uint32_t>(
3421 kMaxUnsortedCount, Length() * kMaxUnsortedPercent / 100);
3423 if (mUnsortedElements > unsortedLimit ||
3424 mRemovedElements > kMaxRemovedCount) {
3425 LOG(
3426 ("CacheIndex::FrecencyArray::SortIfNeeded() - Sorting array "
3427 "[unsortedElements=%u, unsortedLimit=%u, removedElements=%u, "
3428 "maxRemovedCount=%u]",
3429 mUnsortedElements, unsortedLimit, mRemovedElements, kMaxRemovedCount));
3431 mRecs.Sort(FrecencyComparator());
3432 mUnsortedElements = 0;
3433 if (mRemovedElements) {
3434 #if defined(EARLY_BETA_OR_EARLIER)
3435 // validate only null items are removed
3436 for (uint32_t i = Length(); i < mRecs.Length(); ++i) {
3437 MOZ_DIAGNOSTIC_ASSERT(!mRecs[i]);
3439 #endif
3440 // Removed elements are at the end after sorting.
3441 mRecs.RemoveElementsAt(Length(), mRemovedElements);
3442 mRemovedElements = 0;
3447 bool CacheIndex::FrecencyArray::RecordExistedUnlocked(
3448 CacheIndexRecordWrapper* aRecord) {
3449 return mRecs.Contains(aRecord);
3452 void CacheIndex::AddRecordToIterators(CacheIndexRecordWrapper* aRecord,
3453 const StaticMutexAutoLock& aProofOfLock) {
3454 sLock.AssertCurrentThreadOwns();
3455 for (uint32_t i = 0; i < mIterators.Length(); ++i) {
3456 // Add a new record only when iterator is supposed to be updated.
3457 if (mIterators[i]->ShouldBeNewAdded()) {
3458 mIterators[i]->AddRecord(aRecord, aProofOfLock);
3463 void CacheIndex::RemoveRecordFromIterators(
3464 CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) {
3465 sLock.AssertCurrentThreadOwns();
3466 for (uint32_t i = 0; i < mIterators.Length(); ++i) {
3467 // Remove the record from iterator always, it makes no sence to return
3468 // non-existing entries. Also the pointer to the record is no longer valid
3469 // once the entry is removed from index.
3470 mIterators[i]->RemoveRecord(aRecord, aProofOfLock);
3474 void CacheIndex::ReplaceRecordInIterators(
3475 CacheIndexRecordWrapper* aOldRecord, CacheIndexRecordWrapper* aNewRecord,
3476 const StaticMutexAutoLock& aProofOfLock) {
3477 sLock.AssertCurrentThreadOwns();
3478 for (uint32_t i = 0; i < mIterators.Length(); ++i) {
3479 // We have to replace the record always since the pointer is no longer
3480 // valid after this point. NOTE: Replacing the record doesn't mean that
3481 // a new entry was added, it just means that the data in the entry was
3482 // changed (e.g. a file size) and we had to track this change in
3483 // mPendingUpdates since mIndex was read-only.
3484 mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord, aProofOfLock);
3488 nsresult CacheIndex::Run() {
3489 LOG(("CacheIndex::Run()"));
3491 StaticMutexAutoLock lock(sLock);
3493 if (!IsIndexUsable()) {
3494 return NS_ERROR_NOT_AVAILABLE;
3497 if (mState == READY && mShuttingDown) {
3498 return NS_OK;
3501 mUpdateEventPending = false;
3503 switch (mState) {
3504 case BUILDING:
3505 BuildIndex(lock);
3506 break;
3507 case UPDATING:
3508 UpdateIndex(lock);
3509 break;
3510 default:
3511 LOG(("CacheIndex::Run() - Update/Build was canceled"));
3514 return NS_OK;
3517 void CacheIndex::OnFileOpenedInternal(FileOpenHelper* aOpener,
3518 CacheFileHandle* aHandle,
3519 nsresult aResult,
3520 const StaticMutexAutoLock& aProofOfLock) {
3521 sLock.AssertCurrentThreadOwns();
3522 LOG(
3523 ("CacheIndex::OnFileOpenedInternal() [opener=%p, handle=%p, "
3524 "result=0x%08" PRIx32 "]",
3525 aOpener, aHandle, static_cast<uint32_t>(aResult)));
3526 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
3528 nsresult rv;
3530 MOZ_RELEASE_ASSERT(IsIndexUsable());
3532 if (mState == READY && mShuttingDown) {
3533 return;
3536 switch (mState) {
3537 case WRITING:
3538 MOZ_ASSERT(aOpener == mIndexFileOpener);
3539 mIndexFileOpener = nullptr;
3541 if (NS_FAILED(aResult)) {
3542 LOG(
3543 ("CacheIndex::OnFileOpenedInternal() - Can't open index file for "
3544 "writing [rv=0x%08" PRIx32 "]",
3545 static_cast<uint32_t>(aResult)));
3546 FinishWrite(false, aProofOfLock);
3547 } else {
3548 mIndexHandle = aHandle;
3549 WriteRecords(aProofOfLock);
3551 break;
3552 case READING:
3553 if (aOpener == mIndexFileOpener) {
3554 mIndexFileOpener = nullptr;
3556 if (NS_SUCCEEDED(aResult)) {
3557 if (aHandle->FileSize() == 0) {
3558 FinishRead(false, aProofOfLock);
3559 CacheFileIOManager::DoomFile(aHandle, nullptr);
3560 break;
3562 mIndexHandle = aHandle;
3563 } else {
3564 FinishRead(false, aProofOfLock);
3565 break;
3567 } else if (aOpener == mJournalFileOpener) {
3568 mJournalFileOpener = nullptr;
3569 mJournalHandle = aHandle;
3570 } else if (aOpener == mTmpFileOpener) {
3571 mTmpFileOpener = nullptr;
3572 mTmpHandle = aHandle;
3573 } else {
3574 MOZ_ASSERT(false, "Unexpected state!");
3577 if (mIndexFileOpener || mJournalFileOpener || mTmpFileOpener) {
3578 // Some opener still didn't finish
3579 break;
3582 // We fail and cancel all other openers when we opening index file fails.
3583 MOZ_ASSERT(mIndexHandle);
3585 if (mTmpHandle) {
3586 CacheFileIOManager::DoomFile(mTmpHandle, nullptr);
3587 mTmpHandle = nullptr;
3589 if (mJournalHandle) { // this shouldn't normally happen
3590 LOG(
3591 ("CacheIndex::OnFileOpenedInternal() - Unexpected state, all "
3592 "files [%s, %s, %s] should never exist. Removing whole index.",
3593 INDEX_NAME, JOURNAL_NAME, TEMP_INDEX_NAME));
3594 FinishRead(false, aProofOfLock);
3595 break;
3599 if (mJournalHandle) {
3600 // Rename journal to make sure we update index on next start in case
3601 // firefox crashes
3602 rv = CacheFileIOManager::RenameFile(
3603 mJournalHandle, nsLiteralCString(TEMP_INDEX_NAME), this);
3604 if (NS_FAILED(rv)) {
3605 LOG(
3606 ("CacheIndex::OnFileOpenedInternal() - CacheFileIOManager::"
3607 "RenameFile() failed synchronously [rv=0x%08" PRIx32 "]",
3608 static_cast<uint32_t>(rv)));
3609 FinishRead(false, aProofOfLock);
3610 break;
3612 } else {
3613 StartReadingIndex(aProofOfLock);
3616 break;
3617 default:
3618 MOZ_ASSERT(false, "Unexpected state!");
3622 nsresult CacheIndex::OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) {
3623 MOZ_CRASH("CacheIndex::OnFileOpened should not be called!");
3624 return NS_ERROR_UNEXPECTED;
3627 nsresult CacheIndex::OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
3628 nsresult aResult) {
3629 LOG(("CacheIndex::OnDataWritten() [handle=%p, result=0x%08" PRIx32 "]",
3630 aHandle, static_cast<uint32_t>(aResult)));
3632 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
3634 nsresult rv;
3636 StaticMutexAutoLock lock(sLock);
3638 MOZ_RELEASE_ASSERT(IsIndexUsable());
3639 MOZ_RELEASE_ASSERT(mRWPending);
3640 mRWPending = false;
3642 if (mState == READY && mShuttingDown) {
3643 return NS_OK;
3646 switch (mState) {
3647 case WRITING:
3648 MOZ_ASSERT(mIndexHandle == aHandle);
3650 if (NS_FAILED(aResult)) {
3651 FinishWrite(false, lock);
3652 } else {
3653 if (mSkipEntries == mProcessEntries) {
3654 rv = CacheFileIOManager::RenameFile(
3655 mIndexHandle, nsLiteralCString(INDEX_NAME), this);
3656 if (NS_FAILED(rv)) {
3657 LOG(
3658 ("CacheIndex::OnDataWritten() - CacheFileIOManager::"
3659 "RenameFile() failed synchronously [rv=0x%08" PRIx32 "]",
3660 static_cast<uint32_t>(rv)));
3661 FinishWrite(false, lock);
3663 } else {
3664 WriteRecords(lock);
3667 break;
3668 default:
3669 // Writing was canceled.
3670 LOG(
3671 ("CacheIndex::OnDataWritten() - ignoring notification since the "
3672 "operation was previously canceled [state=%d]",
3673 mState));
3674 ReleaseBuffer();
3677 return NS_OK;
3680 nsresult CacheIndex::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
3681 nsresult aResult) {
3682 LOG(("CacheIndex::OnDataRead() [handle=%p, result=0x%08" PRIx32 "]", aHandle,
3683 static_cast<uint32_t>(aResult)));
3685 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
3687 StaticMutexAutoLock lock(sLock);
3689 MOZ_RELEASE_ASSERT(IsIndexUsable());
3690 MOZ_RELEASE_ASSERT(mRWPending);
3691 mRWPending = false;
3693 switch (mState) {
3694 case READING:
3695 MOZ_ASSERT(mIndexHandle == aHandle || mJournalHandle == aHandle);
3697 if (NS_FAILED(aResult)) {
3698 FinishRead(false, lock);
3699 } else {
3700 if (!mIndexOnDiskIsValid) {
3701 ParseRecords(lock);
3702 } else {
3703 ParseJournal(lock);
3706 break;
3707 default:
3708 // Reading was canceled.
3709 LOG(
3710 ("CacheIndex::OnDataRead() - ignoring notification since the "
3711 "operation was previously canceled [state=%d]",
3712 mState));
3713 ReleaseBuffer();
3716 return NS_OK;
3719 nsresult CacheIndex::OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) {
3720 MOZ_CRASH("CacheIndex::OnFileDoomed should not be called!");
3721 return NS_ERROR_UNEXPECTED;
3724 nsresult CacheIndex::OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) {
3725 MOZ_CRASH("CacheIndex::OnEOFSet should not be called!");
3726 return NS_ERROR_UNEXPECTED;
3729 nsresult CacheIndex::OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) {
3730 LOG(("CacheIndex::OnFileRenamed() [handle=%p, result=0x%08" PRIx32 "]",
3731 aHandle, static_cast<uint32_t>(aResult)));
3733 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
3735 StaticMutexAutoLock lock(sLock);
3737 MOZ_RELEASE_ASSERT(IsIndexUsable());
3739 if (mState == READY && mShuttingDown) {
3740 return NS_OK;
3743 switch (mState) {
3744 case WRITING:
3745 // This is a result of renaming the new index written to tmpfile to index
3746 // file. This is the last step when writing the index and the whole
3747 // writing process is successful iff renaming was successful.
3749 if (mIndexHandle != aHandle) {
3750 LOG(
3751 ("CacheIndex::OnFileRenamed() - ignoring notification since it "
3752 "belongs to previously canceled operation [state=%d]",
3753 mState));
3754 break;
3757 FinishWrite(NS_SUCCEEDED(aResult), lock);
3758 break;
3759 case READING:
3760 // This is a result of renaming journal file to tmpfile. It is renamed
3761 // before we start reading index and journal file and it should normally
3762 // succeed. If it fails give up reading of index.
3764 if (mJournalHandle != aHandle) {
3765 LOG(
3766 ("CacheIndex::OnFileRenamed() - ignoring notification since it "
3767 "belongs to previously canceled operation [state=%d]",
3768 mState));
3769 break;
3772 if (NS_FAILED(aResult)) {
3773 FinishRead(false, lock);
3774 } else {
3775 StartReadingIndex(lock);
3777 break;
3778 default:
3779 // Reading/writing was canceled.
3780 LOG(
3781 ("CacheIndex::OnFileRenamed() - ignoring notification since the "
3782 "operation was previously canceled [state=%d]",
3783 mState));
3786 return NS_OK;
3789 // Memory reporting
3791 size_t CacheIndex::SizeOfExcludingThisInternal(
3792 mozilla::MallocSizeOf mallocSizeOf) const {
3793 sLock.AssertCurrentThreadOwns();
3795 size_t n = 0;
3796 nsCOMPtr<nsISizeOf> sizeOf;
3798 // mIndexHandle and mJournalHandle are reported via SizeOfHandlesRunnable
3799 // in CacheFileIOManager::SizeOfExcludingThisInternal as part of special
3800 // handles array.
3802 sizeOf = do_QueryInterface(mCacheDirectory);
3803 if (sizeOf) {
3804 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3807 sizeOf = do_QueryInterface(mUpdateTimer);
3808 if (sizeOf) {
3809 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3812 n += mallocSizeOf(mRWBuf);
3813 n += mallocSizeOf(mRWHash);
3815 n += mIndex.SizeOfExcludingThis(mallocSizeOf);
3816 n += mPendingUpdates.SizeOfExcludingThis(mallocSizeOf);
3817 n += mTmpJournal.SizeOfExcludingThis(mallocSizeOf);
3819 // mFrecencyArray items are reported by mIndex/mPendingUpdates
3820 n += mFrecencyArray.mRecs.ShallowSizeOfExcludingThis(mallocSizeOf);
3821 n += mDiskConsumptionObservers.ShallowSizeOfExcludingThis(mallocSizeOf);
3823 return n;
3826 // static
3827 size_t CacheIndex::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
3828 StaticMutexAutoLock lock(sLock);
3830 if (!gInstance) return 0;
3832 return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
3835 // static
3836 size_t CacheIndex::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
3837 StaticMutexAutoLock lock(sLock);
3839 return mallocSizeOf(gInstance) +
3840 (gInstance ? gInstance->SizeOfExcludingThisInternal(mallocSizeOf) : 0);
3843 // static
3844 void CacheIndex::UpdateTotalBytesWritten(uint32_t aBytesWritten) {
3845 StaticMutexAutoLock lock(sLock);
3847 RefPtr<CacheIndex> index = gInstance;
3848 if (!index) {
3849 return;
3852 index->mTotalBytesWritten += aBytesWritten;
3854 // Do telemetry report if enough data has been written and the index is
3855 // in READY state. The data is available also in WRITING state, but we would
3856 // need to deal with pending updates.
3857 if (index->mTotalBytesWritten >= kTelemetryReportBytesLimit &&
3858 index->mState == READY && !index->mIndexNeedsUpdate &&
3859 !index->mShuttingDown) {
3860 index->DoTelemetryReport();
3861 index->mTotalBytesWritten = 0;
3862 return;
3866 void CacheIndex::DoTelemetryReport() {
3867 static const nsLiteralCString
3868 contentTypeNames[nsICacheEntry::CONTENT_TYPE_LAST] = {
3869 "UNKNOWN"_ns, "OTHER"_ns, "JAVASCRIPT"_ns, "IMAGE"_ns,
3870 "MEDIA"_ns, "STYLESHEET"_ns, "WASM"_ns};
3872 for (uint32_t i = 0; i < nsICacheEntry::CONTENT_TYPE_LAST; ++i) {
3873 if (mIndexStats.Size() > 0) {
3874 Telemetry::Accumulate(
3875 Telemetry::NETWORK_CACHE_SIZE_SHARE, contentTypeNames[i],
3876 round(static_cast<double>(mIndexStats.SizeByType(i)) * 100.0 /
3877 static_cast<double>(mIndexStats.Size())));
3880 if (mIndexStats.Count() > 0) {
3881 Telemetry::Accumulate(
3882 Telemetry::NETWORK_CACHE_ENTRY_COUNT_SHARE, contentTypeNames[i],
3883 round(static_cast<double>(mIndexStats.CountByType(i)) * 100.0 /
3884 static_cast<double>(mIndexStats.Count())));
3888 nsCString probeKey;
3889 if (CacheObserver::SmartCacheSizeEnabled()) {
3890 probeKey = "SMARTSIZE"_ns;
3891 } else {
3892 probeKey = "USERDEFINEDSIZE"_ns;
3894 Telemetry::Accumulate(Telemetry::NETWORK_CACHE_ENTRY_COUNT, probeKey,
3895 mIndexStats.Count());
3896 Telemetry::Accumulate(Telemetry::NETWORK_CACHE_SIZE, probeKey,
3897 mIndexStats.Size() >> 10);
3900 // static
3901 void CacheIndex::OnAsyncEviction(bool aEvicting) {
3902 StaticMutexAutoLock lock(sLock);
3904 RefPtr<CacheIndex> index = gInstance;
3905 if (!index) {
3906 return;
3909 index->mAsyncGetDiskConsumptionBlocked = aEvicting;
3910 if (!aEvicting) {
3911 index->NotifyAsyncGetDiskConsumptionCallbacks();
3915 } // namespace mozilla::net