CacheIndex.cpp (113739B)
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/. */ 4 5 #include "CacheIndex.h" 6 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 "nsPrintfCString.h" 15 #include "mozilla/DebugOnly.h" 16 #include "prinrval.h" 17 #include "nsIFile.h" 18 #include "nsITimer.h" 19 #include "nsNetUtil.h" 20 #include "mozilla/AutoRestore.h" 21 #include <algorithm> 22 #include "mozilla/StaticPrefs_network.h" 23 #include "mozilla/glean/NetwerkCache2Metrics.h" 24 25 #define kMinUnwrittenChanges 300 26 #define kMinDumpInterval 20000 // in milliseconds 27 #define kMaxBufSize 16384 28 #define kIndexVersion 0x0000000A 29 #define kUpdateIndexStartDelay 50000 // in milliseconds 30 #define kTelemetryReportBytesLimit (2U * 1024U * 1024U * 1024U) // 2GB 31 32 #define INDEX_NAME "index" 33 #define TEMP_INDEX_NAME "index.tmp" 34 #define JOURNAL_NAME "index.log" 35 36 namespace mozilla::net { 37 38 namespace { 39 40 class FrecencyComparator { 41 public: 42 bool Equals(const RefPtr<CacheIndexRecordWrapper>& a, 43 const RefPtr<CacheIndexRecordWrapper>& b) const { 44 if (!a || !b) { 45 return false; 46 } 47 48 return a->Get()->mFrecency == b->Get()->mFrecency; 49 } 50 bool LessThan(const RefPtr<CacheIndexRecordWrapper>& a, 51 const RefPtr<CacheIndexRecordWrapper>& b) const { 52 // Removed (=null) entries must be at the end of the array. 53 if (!a) { 54 return false; 55 } 56 if (!b) { 57 return true; 58 } 59 60 // Place entries with frecency 0 at the end of the non-removed entries. 61 if (a->Get()->mFrecency == 0) { 62 return false; 63 } 64 if (b->Get()->mFrecency == 0) { 65 return true; 66 } 67 68 return a->Get()->mFrecency < b->Get()->mFrecency; 69 } 70 }; 71 72 } // namespace 73 74 // used to dispatch a wrapper deletion the caller's thread 75 // cannot be used on IOThread after shutdown begins 76 class DeleteCacheIndexRecordWrapper : public Runnable { 77 CacheIndexRecordWrapper* mWrapper; 78 79 public: 80 explicit DeleteCacheIndexRecordWrapper(CacheIndexRecordWrapper* wrapper) 81 : Runnable("net::CacheIndex::DeleteCacheIndexRecordWrapper"), 82 mWrapper(wrapper) {} 83 NS_IMETHOD Run() override { 84 StaticMutexAutoLock lock(CacheIndex::sLock); 85 86 // if somehow the item is still in the frecency storage, remove it 87 RefPtr<CacheIndex> index = CacheIndex::gInstance; 88 if (index) { 89 bool found = index->mFrecencyStorage.RecordExistedUnlocked(mWrapper); 90 if (found) { 91 LOG( 92 ("DeleteCacheIndexRecordWrapper::Run() - \ 93 record wrapper found in frecency storage during deletion")); 94 index->mFrecencyStorage.RemoveRecord(mWrapper, lock); 95 } 96 } 97 98 delete mWrapper; 99 return NS_OK; 100 } 101 }; 102 103 void CacheIndexRecordWrapper::DispatchDeleteSelfToCurrentThread() { 104 // Dispatch during shutdown will not trigger DeleteCacheIndexRecordWrapper 105 nsCOMPtr<nsIRunnable> event = new DeleteCacheIndexRecordWrapper(this); 106 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(event)); 107 } 108 109 CacheIndexRecordWrapper::~CacheIndexRecordWrapper() { 110 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 111 CacheIndex::sLock.AssertCurrentThreadOwns(); 112 RefPtr<CacheIndex> index = CacheIndex::gInstance; 113 if (index) { 114 bool found = index->mFrecencyStorage.RecordExistedUnlocked(this); 115 MOZ_DIAGNOSTIC_ASSERT(!found); 116 } 117 #endif 118 } 119 120 /** 121 * This helper class is responsible for keeping CacheIndex::mIndexStats and 122 * CacheIndex::mFrecencyStorage up to date. 123 */ 124 class MOZ_RAII CacheIndexEntryAutoManage { 125 public: 126 CacheIndexEntryAutoManage(const SHA1Sum::Hash* aHash, CacheIndex* aIndex, 127 const StaticMutexAutoLock& aProofOfLock) 128 MOZ_REQUIRES(CacheIndex::sLock) 129 : mIndex(aIndex), mProofOfLock(aProofOfLock) { 130 mHash = aHash; 131 const CacheIndexEntry* entry = FindEntry(); 132 mIndex->mIndexStats.BeforeChange(entry); 133 if (entry && entry->IsInitialized() && !entry->IsRemoved()) { 134 mOldRecord = entry->mRec; 135 } 136 } 137 138 ~CacheIndexEntryAutoManage() MOZ_REQUIRES(CacheIndex::sLock) { 139 const CacheIndexEntry* entry = FindEntry(); 140 mIndex->mIndexStats.AfterChange(entry); 141 if (!entry || !entry->IsInitialized() || entry->IsRemoved()) { 142 entry = nullptr; 143 } 144 145 if (entry && !mOldRecord) { 146 mIndex->mFrecencyStorage.AppendRecord(entry->mRec, mProofOfLock); 147 mIndex->AddRecordToIterators(entry->mRec, mProofOfLock); 148 } else if (!entry && mOldRecord) { 149 mIndex->mFrecencyStorage.RemoveRecord(mOldRecord, mProofOfLock); 150 mIndex->RemoveRecordFromIterators(mOldRecord, mProofOfLock); 151 } else if (entry && mOldRecord) { 152 if (entry->mRec != mOldRecord) { 153 // record has a different address, we have to replace it 154 mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec, mProofOfLock); 155 156 mIndex->mFrecencyStorage.ReplaceRecord(mOldRecord, entry->mRec, 157 mProofOfLock); 158 } 159 } else { 160 // both entries were removed or not initialized, do nothing 161 } 162 } 163 164 // We cannot rely on nsTHashtable::GetEntry() in case we are removing entries 165 // while iterating. Destructor is called before the entry is removed. Caller 166 // must call one of following methods to skip lookup in the hashtable. 167 void DoNotSearchInIndex() { mDoNotSearchInIndex = true; } 168 void DoNotSearchInUpdates() { mDoNotSearchInUpdates = true; } 169 170 private: 171 const CacheIndexEntry* FindEntry() MOZ_REQUIRES(CacheIndex::sLock) { 172 const CacheIndexEntry* entry = nullptr; 173 174 switch (mIndex->mState) { 175 case CacheIndex::READING: 176 case CacheIndex::WRITING: 177 if (!mDoNotSearchInUpdates) { 178 entry = mIndex->mPendingUpdates.GetEntry(*mHash); 179 } 180 [[fallthrough]]; 181 case CacheIndex::BUILDING: 182 case CacheIndex::UPDATING: 183 case CacheIndex::READY: 184 if (!entry && !mDoNotSearchInIndex) { 185 entry = mIndex->mIndex.GetEntry(*mHash); 186 } 187 break; 188 case CacheIndex::INITIAL: 189 case CacheIndex::SHUTDOWN: 190 default: 191 MOZ_ASSERT(false, "Unexpected state!"); 192 } 193 194 return entry; 195 } 196 197 const SHA1Sum::Hash* mHash; 198 RefPtr<CacheIndex> mIndex; 199 RefPtr<CacheIndexRecordWrapper> mOldRecord; 200 bool mDoNotSearchInIndex{false}; 201 bool mDoNotSearchInUpdates{false}; 202 const StaticMutexAutoLock& mProofOfLock; 203 }; 204 205 class FileOpenHelper final : public CacheFileIOListener { 206 public: 207 NS_DECL_THREADSAFE_ISUPPORTS 208 209 explicit FileOpenHelper(CacheIndex* aIndex) 210 : mIndex(aIndex), mCanceled(false) {} 211 212 void Cancel() { 213 CacheIndex::sLock.AssertCurrentThreadOwns(); 214 mCanceled = true; 215 } 216 217 private: 218 virtual ~FileOpenHelper() = default; 219 220 NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override; 221 NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf, 222 nsresult aResult) override { 223 MOZ_CRASH("FileOpenHelper::OnDataWritten should not be called!"); 224 return NS_ERROR_UNEXPECTED; 225 } 226 NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf, 227 nsresult aResult) override { 228 MOZ_CRASH("FileOpenHelper::OnDataRead should not be called!"); 229 return NS_ERROR_UNEXPECTED; 230 } 231 NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override { 232 MOZ_CRASH("FileOpenHelper::OnFileDoomed should not be called!"); 233 return NS_ERROR_UNEXPECTED; 234 } 235 NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override { 236 MOZ_CRASH("FileOpenHelper::OnEOFSet should not be called!"); 237 return NS_ERROR_UNEXPECTED; 238 } 239 NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle, 240 nsresult aResult) override { 241 MOZ_CRASH("FileOpenHelper::OnFileRenamed should not be called!"); 242 return NS_ERROR_UNEXPECTED; 243 } 244 245 RefPtr<CacheIndex> mIndex; 246 bool mCanceled; 247 }; 248 249 NS_IMETHODIMP FileOpenHelper::OnFileOpened(CacheFileHandle* aHandle, 250 nsresult aResult) { 251 StaticMutexAutoLock lock(CacheIndex::sLock); 252 253 if (mCanceled) { 254 if (aHandle) { 255 CacheFileIOManager::DoomFile(aHandle, nullptr); 256 } 257 258 return NS_OK; 259 } 260 261 mIndex->OnFileOpenedInternal(this, aHandle, aResult, lock); 262 263 return NS_OK; 264 } 265 266 NS_IMPL_ISUPPORTS(FileOpenHelper, CacheFileIOListener); 267 268 StaticRefPtr<CacheIndex> CacheIndex::gInstance; 269 StaticMutex CacheIndex::sLock; 270 271 NS_IMPL_ADDREF(CacheIndex) 272 NS_IMPL_RELEASE(CacheIndex) 273 274 NS_INTERFACE_MAP_BEGIN(CacheIndex) 275 NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener) 276 NS_INTERFACE_MAP_ENTRY(nsIRunnable) 277 NS_INTERFACE_MAP_END 278 279 CacheIndex::CacheIndex() { 280 sLock.AssertCurrentThreadOwns(); 281 LOG(("CacheIndex::CacheIndex [this=%p]", this)); 282 MOZ_ASSERT(!gInstance, "multiple CacheIndex instances!"); 283 } 284 285 CacheIndex::~CacheIndex() { 286 sLock.AssertCurrentThreadOwns(); 287 LOG(("CacheIndex::~CacheIndex [this=%p]", this)); 288 289 ReleaseBuffer(); 290 } 291 292 // static 293 nsresult CacheIndex::Init(nsIFile* aCacheDirectory) { 294 LOG(("CacheIndex::Init()")); 295 296 MOZ_ASSERT(NS_IsMainThread()); 297 298 StaticMutexAutoLock lock(sLock); 299 300 if (gInstance) { 301 return NS_ERROR_ALREADY_INITIALIZED; 302 } 303 304 RefPtr<CacheIndex> idx = new CacheIndex(); 305 306 nsresult rv = idx->InitInternal(aCacheDirectory, lock); 307 NS_ENSURE_SUCCESS(rv, rv); 308 309 gInstance = std::move(idx); 310 return NS_OK; 311 } 312 313 nsresult CacheIndex::InitInternal(nsIFile* aCacheDirectory, 314 const StaticMutexAutoLock& aProofOfLock) { 315 nsresult rv; 316 sLock.AssertCurrentThreadOwns(); 317 318 rv = aCacheDirectory->Clone(getter_AddRefs(mCacheDirectory)); 319 NS_ENSURE_SUCCESS(rv, rv); 320 321 mStartTime = TimeStamp::NowLoRes(); 322 323 ReadIndexFromDisk(aProofOfLock); 324 325 return NS_OK; 326 } 327 328 // static 329 nsresult CacheIndex::PreShutdown() { 330 MOZ_ASSERT(NS_IsMainThread()); 331 332 StaticMutexAutoLock lock(sLock); 333 334 LOG(("CacheIndex::PreShutdown() [gInstance=%p]", gInstance.get())); 335 336 nsresult rv; 337 RefPtr<CacheIndex> index = gInstance; 338 339 if (!index) { 340 return NS_ERROR_NOT_INITIALIZED; 341 } 342 343 LOG( 344 ("CacheIndex::PreShutdown() - [state=%d, indexOnDiskIsValid=%d, " 345 "dontMarkIndexClean=%d]", 346 index->mState, index->mIndexOnDiskIsValid, index->mDontMarkIndexClean)); 347 348 LOG(("CacheIndex::PreShutdown() - Closing iterators.")); 349 for (uint32_t i = 0; i < index->mIterators.Length();) { 350 rv = index->mIterators[i]->CloseInternal(NS_ERROR_FAILURE); 351 if (NS_FAILED(rv)) { 352 // CacheIndexIterator::CloseInternal() removes itself from mIteratos iff 353 // it returns success. 354 LOG( 355 ("CacheIndex::PreShutdown() - Failed to remove iterator %p. " 356 "[rv=0x%08" PRIx32 "]", 357 index->mIterators[i], static_cast<uint32_t>(rv))); 358 i++; 359 } 360 } 361 362 index->mShuttingDown = true; 363 364 if (index->mState == READY) { 365 return NS_OK; // nothing to do 366 } 367 368 nsCOMPtr<nsIRunnable> event; 369 event = NewRunnableMethod("net::CacheIndex::PreShutdownInternal", index, 370 &CacheIndex::PreShutdownInternal); 371 372 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget(); 373 MOZ_ASSERT(ioTarget); 374 375 // PreShutdownInternal() will be executed before any queued event on INDEX 376 // level. That's OK since we don't want to wait for any operation in progess. 377 rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); 378 if (NS_FAILED(rv)) { 379 NS_WARNING("CacheIndex::PreShutdown() - Can't dispatch event"); 380 LOG(("CacheIndex::PreShutdown() - Can't dispatch event")); 381 return rv; 382 } 383 384 return NS_OK; 385 } 386 387 void CacheIndex::PreShutdownInternal() { 388 StaticMutexAutoLock lock(sLock); 389 390 LOG( 391 ("CacheIndex::PreShutdownInternal() - [state=%d, indexOnDiskIsValid=%d, " 392 "dontMarkIndexClean=%d]", 393 mState, mIndexOnDiskIsValid, mDontMarkIndexClean)); 394 395 MOZ_ASSERT(mShuttingDown); 396 397 if (mUpdateTimer) { 398 mUpdateTimer->Cancel(); 399 mUpdateTimer = nullptr; 400 } 401 402 switch (mState) { 403 case WRITING: 404 FinishWrite(false, lock); 405 break; 406 case READY: 407 // nothing to do, write the journal in Shutdown() 408 break; 409 case READING: 410 FinishRead(false, lock); 411 break; 412 case BUILDING: 413 case UPDATING: 414 FinishUpdate(false, lock); 415 break; 416 default: 417 MOZ_ASSERT(false, "Implement me!"); 418 } 419 420 // We should end up in READY state 421 MOZ_ASSERT(mState == READY); 422 } 423 424 // static 425 nsresult CacheIndex::Shutdown() { 426 MOZ_ASSERT(NS_IsMainThread()); 427 428 StaticMutexAutoLock lock(sLock); 429 430 LOG(("CacheIndex::Shutdown() [gInstance=%p]", gInstance.get())); 431 432 RefPtr<CacheIndex> index = gInstance.forget(); 433 434 if (!index) { 435 return NS_ERROR_NOT_INITIALIZED; 436 } 437 438 bool sanitize = CacheObserver::ClearCacheOnShutdown(); 439 440 LOG( 441 ("CacheIndex::Shutdown() - [state=%d, indexOnDiskIsValid=%d, " 442 "dontMarkIndexClean=%d, sanitize=%d]", 443 index->mState, index->mIndexOnDiskIsValid, index->mDontMarkIndexClean, 444 sanitize)); 445 446 MOZ_ASSERT(index->mShuttingDown); 447 448 EState oldState = index->mState; 449 index->ChangeState(SHUTDOWN, lock); 450 451 if (oldState != READY) { 452 LOG( 453 ("CacheIndex::Shutdown() - Unexpected state. Did posting of " 454 "PreShutdownInternal() fail?")); 455 } 456 457 switch (oldState) { 458 case WRITING: 459 index->FinishWrite(false, lock); 460 [[fallthrough]]; 461 case READY: 462 if (index->mIndexOnDiskIsValid && !index->mDontMarkIndexClean) { 463 if (!sanitize && NS_FAILED(index->WriteLogToDisk())) { 464 index->RemoveJournalAndTempFile(); 465 } 466 } else { 467 index->RemoveJournalAndTempFile(); 468 } 469 break; 470 case READING: 471 index->FinishRead(false, lock); 472 break; 473 case BUILDING: 474 case UPDATING: 475 index->FinishUpdate(false, lock); 476 break; 477 default: 478 MOZ_ASSERT(false, "Unexpected state!"); 479 } 480 481 if (sanitize) { 482 index->RemoveAllIndexFiles(); 483 } 484 485 return NS_OK; 486 } 487 488 // static 489 nsresult CacheIndex::AddEntry(const SHA1Sum::Hash* aHash) { 490 LOG(("CacheIndex::AddEntry() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash))); 491 492 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 493 494 StaticMutexAutoLock lock(sLock); 495 496 RefPtr<CacheIndex> index = gInstance; 497 498 if (!index) { 499 return NS_ERROR_NOT_INITIALIZED; 500 } 501 502 if (!index->IsIndexUsable()) { 503 return NS_ERROR_NOT_AVAILABLE; 504 } 505 506 // Getters in CacheIndexStats assert when mStateLogged is true since the 507 // information is incomplete between calls to BeforeChange() and AfterChange() 508 // (i.e. while CacheIndexEntryAutoManage exists). We need to check whether 509 // non-fresh entries exists outside the scope of CacheIndexEntryAutoManage. 510 bool updateIfNonFreshEntriesExist = false; 511 512 { 513 CacheIndexEntryAutoManage entryMng(aHash, index, lock); 514 515 CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash); 516 bool entryRemoved = entry && entry->IsRemoved(); 517 CacheIndexEntryUpdate* updated = nullptr; 518 519 if (index->mState == READY || index->mState == UPDATING || 520 index->mState == BUILDING) { 521 MOZ_ASSERT(index->mPendingUpdates.Count() == 0); 522 523 if (entry && !entryRemoved) { 524 // Found entry in index that shouldn't exist. 525 526 if (entry->IsFresh()) { 527 // Someone removed the file on disk while FF is running. Update 528 // process can fix only non-fresh entries (i.e. entries that were not 529 // added within this session). Start update only if we have such 530 // entries. 531 // 532 // TODO: This should be very rare problem. If it turns out not to be 533 // true, change the update process so that it also iterates all 534 // initialized non-empty entries and checks whether the file exists. 535 536 LOG( 537 ("CacheIndex::AddEntry() - Cache file was removed outside FF " 538 "process!")); 539 540 updateIfNonFreshEntriesExist = true; 541 } else if (index->mState == READY) { 542 // Index is outdated, update it. 543 LOG( 544 ("CacheIndex::AddEntry() - Found entry that shouldn't exist, " 545 "update is needed")); 546 index->mIndexNeedsUpdate = true; 547 } else { 548 // We cannot be here when building index since all entries are fresh 549 // during building. 550 MOZ_ASSERT(index->mState == UPDATING); 551 } 552 } 553 554 if (!entry) { 555 entry = index->mIndex.PutEntry(*aHash); 556 } 557 } else { // WRITING, READING 558 updated = index->mPendingUpdates.GetEntry(*aHash); 559 bool updatedRemoved = updated && updated->IsRemoved(); 560 561 if ((updated && !updatedRemoved) || 562 (!updated && entry && !entryRemoved && entry->IsFresh())) { 563 // Fresh entry found, so the file was removed outside FF 564 LOG( 565 ("CacheIndex::AddEntry() - Cache file was removed outside FF " 566 "process!")); 567 568 updateIfNonFreshEntriesExist = true; 569 } else if (!updated && entry && !entryRemoved) { 570 if (index->mState == WRITING) { 571 LOG( 572 ("CacheIndex::AddEntry() - Found entry that shouldn't exist, " 573 "update is needed")); 574 index->mIndexNeedsUpdate = true; 575 } 576 // Ignore if state is READING since the index information is partial 577 } 578 579 updated = index->mPendingUpdates.PutEntry(*aHash); 580 } 581 582 if (updated) { 583 updated->InitNew(); 584 updated->MarkDirty(); 585 updated->MarkFresh(); 586 } else { 587 entry->InitNew(); 588 entry->MarkDirty(); 589 entry->MarkFresh(); 590 } 591 } 592 593 if (updateIfNonFreshEntriesExist && 594 index->mIndexStats.Count() != index->mIndexStats.Fresh()) { 595 index->mIndexNeedsUpdate = true; 596 } 597 598 index->StartUpdatingIndexIfNeeded(lock); 599 index->WriteIndexToDiskIfNeeded(lock); 600 601 return NS_OK; 602 } 603 604 // static 605 nsresult CacheIndex::EnsureEntryExists(const SHA1Sum::Hash* aHash) { 606 LOG(("CacheIndex::EnsureEntryExists() [hash=%08x%08x%08x%08x%08x]", 607 LOGSHA1(aHash))); 608 609 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 610 611 StaticMutexAutoLock lock(sLock); 612 613 RefPtr<CacheIndex> index = gInstance; 614 615 if (!index) { 616 return NS_ERROR_NOT_INITIALIZED; 617 } 618 619 if (!index->IsIndexUsable()) { 620 return NS_ERROR_NOT_AVAILABLE; 621 } 622 623 { 624 CacheIndexEntryAutoManage entryMng(aHash, index, lock); 625 626 CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash); 627 bool entryRemoved = entry && entry->IsRemoved(); 628 629 if (index->mState == READY || index->mState == UPDATING || 630 index->mState == BUILDING) { 631 MOZ_ASSERT(index->mPendingUpdates.Count() == 0); 632 633 if (!entry || entryRemoved) { 634 if (entryRemoved && entry->IsFresh()) { 635 // This could happen only if somebody copies files to the entries 636 // directory while FF is running. 637 LOG( 638 ("CacheIndex::EnsureEntryExists() - Cache file was added outside " 639 "FF process! Update is needed.")); 640 index->mIndexNeedsUpdate = true; 641 } else if (index->mState == READY || 642 (entryRemoved && !entry->IsFresh())) { 643 // Removed non-fresh entries can be present as a result of 644 // MergeJournal() 645 LOG( 646 ("CacheIndex::EnsureEntryExists() - Didn't find entry that should" 647 " exist, update is needed")); 648 index->mIndexNeedsUpdate = true; 649 } 650 651 if (!entry) { 652 entry = index->mIndex.PutEntry(*aHash); 653 } 654 entry->InitNew(); 655 entry->MarkDirty(); 656 } 657 entry->MarkFresh(); 658 } else { // WRITING, READING 659 CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash); 660 bool updatedRemoved = updated && updated->IsRemoved(); 661 662 if (updatedRemoved || (!updated && entryRemoved && entry->IsFresh())) { 663 // Fresh information about missing entry found. This could happen only 664 // if somebody copies files to the entries directory while FF is 665 // running. 666 LOG( 667 ("CacheIndex::EnsureEntryExists() - Cache file was added outside " 668 "FF process! Update is needed.")); 669 index->mIndexNeedsUpdate = true; 670 } else if (!updated && (!entry || entryRemoved)) { 671 if (index->mState == WRITING) { 672 LOG( 673 ("CacheIndex::EnsureEntryExists() - Didn't find entry that should" 674 " exist, update is needed")); 675 index->mIndexNeedsUpdate = true; 676 } 677 // Ignore if state is READING since the index information is partial 678 } 679 680 // We don't need entryRemoved and updatedRemoved info anymore 681 if (entryRemoved) entry = nullptr; 682 if (updatedRemoved) updated = nullptr; 683 684 if (updated) { 685 updated->MarkFresh(); 686 } else { 687 if (!entry) { 688 // Create a new entry 689 updated = index->mPendingUpdates.PutEntry(*aHash); 690 updated->InitNew(); 691 updated->MarkFresh(); 692 updated->MarkDirty(); 693 } else { 694 if (!entry->IsFresh()) { 695 // To mark the entry fresh we must make a copy of index entry 696 // since the index is read-only. 697 updated = index->mPendingUpdates.PutEntry(*aHash); 698 *updated = *entry; 699 updated->MarkFresh(); 700 } 701 } 702 } 703 } 704 } 705 706 index->StartUpdatingIndexIfNeeded(lock); 707 index->WriteIndexToDiskIfNeeded(lock); 708 709 return NS_OK; 710 } 711 712 // static 713 nsresult CacheIndex::InitEntry(const SHA1Sum::Hash* aHash, 714 OriginAttrsHash aOriginAttrsHash, 715 bool aAnonymous, bool aPinned) { 716 LOG( 717 ("CacheIndex::InitEntry() [hash=%08x%08x%08x%08x%08x, " 718 "originAttrsHash=%" PRIx64 ", anonymous=%d, pinned=%d]", 719 LOGSHA1(aHash), aOriginAttrsHash, aAnonymous, aPinned)); 720 721 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 722 723 StaticMutexAutoLock lock(sLock); 724 725 RefPtr<CacheIndex> index = gInstance; 726 727 if (!index) { 728 return NS_ERROR_NOT_INITIALIZED; 729 } 730 731 if (!index->IsIndexUsable()) { 732 return NS_ERROR_NOT_AVAILABLE; 733 } 734 735 { 736 CacheIndexEntryAutoManage entryMng(aHash, index, lock); 737 738 CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash); 739 CacheIndexEntryUpdate* updated = nullptr; 740 bool reinitEntry = false; 741 742 if (entry && entry->IsRemoved()) { 743 entry = nullptr; 744 } 745 746 if (index->mState == READY || index->mState == UPDATING || 747 index->mState == BUILDING) { 748 MOZ_ASSERT(index->mPendingUpdates.Count() == 0); 749 MOZ_ASSERT(entry); 750 MOZ_ASSERT(entry->IsFresh()); 751 752 if (!entry) { 753 LOG(("CacheIndex::InitEntry() - Entry was not found in mIndex!")); 754 NS_WARNING( 755 ("CacheIndex::InitEntry() - Entry was not found in mIndex!")); 756 return NS_ERROR_UNEXPECTED; 757 } 758 759 if (IsCollision(entry, aOriginAttrsHash, aAnonymous)) { 760 index->mIndexNeedsUpdate = 761 true; // TODO Does this really help in case of collision? 762 reinitEntry = true; 763 } else { 764 if (entry->IsInitialized()) { 765 return NS_OK; 766 } 767 } 768 } else { 769 updated = index->mPendingUpdates.GetEntry(*aHash); 770 DebugOnly<bool> removed = updated && updated->IsRemoved(); 771 772 MOZ_ASSERT(updated || !removed); 773 MOZ_ASSERT(updated || entry); 774 775 if (!updated && !entry) { 776 LOG( 777 ("CacheIndex::InitEntry() - Entry was found neither in mIndex nor " 778 "in mPendingUpdates!")); 779 NS_WARNING( 780 ("CacheIndex::InitEntry() - Entry was found neither in " 781 "mIndex nor in mPendingUpdates!")); 782 return NS_ERROR_UNEXPECTED; 783 } 784 785 if (updated) { 786 MOZ_ASSERT(updated->IsFresh()); 787 788 if (IsCollision(updated, aOriginAttrsHash, aAnonymous)) { 789 index->mIndexNeedsUpdate = true; 790 reinitEntry = true; 791 } else { 792 if (updated->IsInitialized()) { 793 return NS_OK; 794 } 795 } 796 } else { 797 MOZ_ASSERT(entry->IsFresh()); 798 799 if (IsCollision(entry, aOriginAttrsHash, aAnonymous)) { 800 index->mIndexNeedsUpdate = true; 801 reinitEntry = true; 802 } else { 803 if (entry->IsInitialized()) { 804 return NS_OK; 805 } 806 } 807 808 // make a copy of a read-only entry 809 updated = index->mPendingUpdates.PutEntry(*aHash); 810 *updated = *entry; 811 } 812 } 813 814 if (reinitEntry) { 815 // There is a collision and we are going to rewrite this entry. Initialize 816 // it as a new entry. 817 if (updated) { 818 updated->InitNew(); 819 updated->MarkFresh(); 820 } else { 821 entry->InitNew(); 822 entry->MarkFresh(); 823 } 824 } 825 826 if (updated) { 827 updated->Init(aOriginAttrsHash, aAnonymous, aPinned); 828 updated->MarkDirty(); 829 } else { 830 entry->Init(aOriginAttrsHash, aAnonymous, aPinned); 831 entry->MarkDirty(); 832 } 833 } 834 835 index->StartUpdatingIndexIfNeeded(lock); 836 index->WriteIndexToDiskIfNeeded(lock); 837 838 return NS_OK; 839 } 840 841 // static 842 nsresult CacheIndex::RemoveEntry(const SHA1Sum::Hash* aHash, 843 const nsACString& aKey, 844 bool aClearDictionary) { 845 LOG( 846 ("CacheIndex::RemoveEntry() [hash=%08x%08x%08x%08x%08x] key=%s " 847 "clear_dictionary=%d", 848 LOGSHA1(aHash), PromiseFlatCString(aKey).get(), aClearDictionary)); 849 850 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 851 852 // Remove any dictionary associated with this entry even if we later 853 // error out - async since removal happens on MainThread. 854 855 // TODO XXX There may be a hole here where a dictionary entry can get 856 // referenced for a request before RemoveDictionaryFor can run, but after 857 // the entry is removed here. 858 859 // Note: we don't want to (re)clear dictionaries when the 860 // CacheFileContextEvictor purges entries; they've already been cleared 861 // via CacheIndex::EvictByContext synchronously 862 if (aClearDictionary) { 863 DictionaryCache::RemoveDictionaryFor(aKey); 864 } 865 866 StaticMutexAutoLock lock(sLock); 867 868 RefPtr<CacheIndex> index = gInstance; 869 870 if (!index) { 871 return NS_ERROR_NOT_INITIALIZED; 872 } 873 874 if (!index->IsIndexUsable()) { 875 return NS_ERROR_NOT_AVAILABLE; 876 } 877 878 { 879 CacheIndexEntryAutoManage entryMng(aHash, index, lock); 880 881 CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash); 882 bool entryRemoved = entry && entry->IsRemoved(); 883 884 if (index->mState == READY || index->mState == UPDATING || 885 index->mState == BUILDING) { 886 MOZ_ASSERT(index->mPendingUpdates.Count() == 0); 887 888 if (!entry || entryRemoved) { 889 if (entryRemoved && entry->IsFresh()) { 890 // This could happen only if somebody copies files to the entries 891 // directory while FF is running. 892 LOG( 893 ("CacheIndex::RemoveEntry() - Cache file was added outside FF " 894 "process! Update is needed.")); 895 index->mIndexNeedsUpdate = true; 896 } else if (index->mState == READY || 897 (entryRemoved && !entry->IsFresh())) { 898 // Removed non-fresh entries can be present as a result of 899 // MergeJournal() 900 LOG( 901 ("CacheIndex::RemoveEntry() - Didn't find entry that should exist" 902 ", update is needed")); 903 index->mIndexNeedsUpdate = true; 904 } 905 } else { 906 if (entry) { 907 if (!entry->IsDirty() && entry->IsFileEmpty()) { 908 index->mIndex.RemoveEntry(entry); 909 entry = nullptr; 910 } else { 911 entry->MarkRemoved(); 912 entry->MarkDirty(); 913 entry->MarkFresh(); 914 } 915 } 916 } 917 } else { // WRITING, READING 918 CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash); 919 bool updatedRemoved = updated && updated->IsRemoved(); 920 921 if (updatedRemoved || (!updated && entryRemoved && entry->IsFresh())) { 922 // Fresh information about missing entry found. This could happen only 923 // if somebody copies files to the entries directory while FF is 924 // running. 925 LOG( 926 ("CacheIndex::RemoveEntry() - Cache file was added outside FF " 927 "process! Update is needed.")); 928 index->mIndexNeedsUpdate = true; 929 } else if (!updated && (!entry || entryRemoved)) { 930 if (index->mState == WRITING) { 931 LOG( 932 ("CacheIndex::RemoveEntry() - Didn't find entry that should exist" 933 ", update is needed")); 934 index->mIndexNeedsUpdate = true; 935 } 936 // Ignore if state is READING since the index information is partial 937 } 938 939 if (!updated) { 940 updated = index->mPendingUpdates.PutEntry(*aHash); 941 updated->InitNew(); 942 } 943 944 updated->MarkRemoved(); 945 updated->MarkDirty(); 946 updated->MarkFresh(); 947 } 948 } 949 index->StartUpdatingIndexIfNeeded(lock); 950 index->WriteIndexToDiskIfNeeded(lock); 951 952 return NS_OK; 953 } 954 955 // static 956 nsresult CacheIndex::UpdateEntry(const SHA1Sum::Hash* aHash, 957 const uint32_t* aFrecency, 958 const bool* aHasAltData, 959 const uint16_t* aOnStartTime, 960 const uint16_t* aOnStopTime, 961 const uint8_t* aContentType, 962 const uint32_t* aSize) { 963 LOG( 964 ("CacheIndex::UpdateEntry() [hash=%08x%08x%08x%08x%08x, " 965 "frecency=%s, hasAltData=%s, onStartTime=%s, onStopTime=%s, " 966 "contentType=%s, size=%s]", 967 LOGSHA1(aHash), aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "", 968 aHasAltData ? (*aHasAltData ? "true" : "false") : "", 969 aOnStartTime ? nsPrintfCString("%u", *aOnStartTime).get() : "", 970 aOnStopTime ? nsPrintfCString("%u", *aOnStopTime).get() : "", 971 aContentType ? nsPrintfCString("%u", *aContentType).get() : "", 972 aSize ? nsPrintfCString("%u", *aSize).get() : "")); 973 974 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 975 976 StaticMutexAutoLock lock(sLock); 977 978 RefPtr<CacheIndex> index = gInstance; 979 980 if (!index) { 981 return NS_ERROR_NOT_INITIALIZED; 982 } 983 984 if (!index->IsIndexUsable()) { 985 return NS_ERROR_NOT_AVAILABLE; 986 } 987 988 { 989 CacheIndexEntryAutoManage entryMng(aHash, index, lock); 990 991 CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash); 992 993 if (entry && entry->IsRemoved()) { 994 entry = nullptr; 995 } 996 997 if (index->mState == READY || index->mState == UPDATING || 998 index->mState == BUILDING) { 999 MOZ_ASSERT(index->mPendingUpdates.Count() == 0); 1000 MOZ_ASSERT(entry); 1001 1002 if (!entry) { 1003 LOG(("CacheIndex::UpdateEntry() - Entry was not found in mIndex!")); 1004 NS_WARNING( 1005 ("CacheIndex::UpdateEntry() - Entry was not found in mIndex!")); 1006 return NS_ERROR_UNEXPECTED; 1007 } 1008 1009 if (!HasEntryChanged(entry, aFrecency, aHasAltData, aOnStartTime, 1010 aOnStopTime, aContentType, aSize)) { 1011 return NS_OK; 1012 } 1013 1014 MOZ_ASSERT(entry->IsFresh()); 1015 MOZ_ASSERT(entry->IsInitialized()); 1016 entry->MarkDirty(); 1017 1018 if (aFrecency) { 1019 entry->SetFrecency(*aFrecency); 1020 } 1021 1022 if (aHasAltData) { 1023 entry->SetHasAltData(*aHasAltData); 1024 } 1025 1026 if (aOnStartTime) { 1027 entry->SetOnStartTime(*aOnStartTime); 1028 } 1029 1030 if (aOnStopTime) { 1031 entry->SetOnStopTime(*aOnStopTime); 1032 } 1033 1034 if (aContentType) { 1035 entry->SetContentType(*aContentType); 1036 } 1037 1038 if (aSize) { 1039 entry->SetFileSize(*aSize); 1040 } 1041 } else { 1042 CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash); 1043 DebugOnly<bool> removed = updated && updated->IsRemoved(); 1044 1045 MOZ_ASSERT(updated || !removed); 1046 MOZ_ASSERT(updated || entry); 1047 1048 if (!updated) { 1049 if (!entry) { 1050 LOG( 1051 ("CacheIndex::UpdateEntry() - Entry was found neither in mIndex " 1052 "nor in mPendingUpdates!")); 1053 NS_WARNING( 1054 ("CacheIndex::UpdateEntry() - Entry was found neither in " 1055 "mIndex nor in mPendingUpdates!")); 1056 return NS_ERROR_UNEXPECTED; 1057 } 1058 1059 // make a copy of a read-only entry 1060 updated = index->mPendingUpdates.PutEntry(*aHash); 1061 *updated = *entry; 1062 } 1063 1064 MOZ_ASSERT(updated->IsFresh()); 1065 MOZ_ASSERT(updated->IsInitialized()); 1066 updated->MarkDirty(); 1067 1068 if (aFrecency) { 1069 updated->SetFrecency(*aFrecency); 1070 } 1071 1072 if (aHasAltData) { 1073 updated->SetHasAltData(*aHasAltData); 1074 } 1075 1076 if (aOnStartTime) { 1077 updated->SetOnStartTime(*aOnStartTime); 1078 } 1079 1080 if (aOnStopTime) { 1081 updated->SetOnStopTime(*aOnStopTime); 1082 } 1083 1084 if (aContentType) { 1085 updated->SetContentType(*aContentType); 1086 } 1087 1088 if (aSize) { 1089 updated->SetFileSize(*aSize); 1090 } 1091 } 1092 } 1093 1094 index->WriteIndexToDiskIfNeeded(lock); 1095 1096 return NS_OK; 1097 } 1098 1099 // Clear the entries from the Index immediately, to comply with 1100 // https://www.w3.org/TR/clear-site-data/#fetch-integration 1101 // Note that we will effectively hide the entries until the actual evict 1102 // happens. 1103 1104 // aOrigin == "" means clear all unless aBaseDomain is set to something 1105 // static 1106 void CacheIndex::EvictByContext(const nsAString& aOrigin, 1107 const nsAString& aBaseDomain) { 1108 StaticMutexAutoLock lock(sLock); 1109 1110 RefPtr<CacheIndex> index = gInstance; 1111 1112 // Store in hashset that this origin has been evicted; we'll remove it 1113 // when CacheFileIOManager::EvictByContextInternal() finishes. 1114 // Not valid to set both aOrigin and aBaseDomain 1115 if (!aOrigin.IsEmpty() && aBaseDomain.IsEmpty()) { 1116 // likely CacheStorageService::ClearByPrincipal 1117 nsCOMPtr<nsIURI> uri; 1118 if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), aOrigin))) { 1119 // Remove the dictionary entries for this origin immediately 1120 DictionaryCache::RemoveDictionariesForOrigin(uri); 1121 } 1122 } 1123 } 1124 1125 // static 1126 nsresult CacheIndex::RemoveAll() { 1127 LOG(("CacheIndex::RemoveAll()")); 1128 1129 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 1130 1131 nsCOMPtr<nsIFile> file; 1132 1133 { 1134 StaticMutexAutoLock lock(sLock); 1135 1136 RefPtr<CacheIndex> index = gInstance; 1137 1138 if (!index) { 1139 return NS_ERROR_NOT_INITIALIZED; 1140 } 1141 1142 MOZ_ASSERT(!index->mRemovingAll); 1143 1144 if (!index->IsIndexUsable()) { 1145 return NS_ERROR_NOT_AVAILABLE; 1146 } 1147 1148 AutoRestore<bool> saveRemovingAll(index->mRemovingAll); 1149 index->mRemovingAll = true; 1150 1151 // Doom index and journal handles but don't null them out since this will be 1152 // done in FinishWrite/FinishRead methods. 1153 if (index->mIndexHandle) { 1154 CacheFileIOManager::DoomFile(index->mIndexHandle, nullptr); 1155 } else { 1156 // We don't have a handle to index file, so get the file here, but delete 1157 // it outside the lock. Ignore the result since this is not fatal. 1158 index->GetFile(nsLiteralCString(INDEX_NAME), getter_AddRefs(file)); 1159 } 1160 1161 if (index->mJournalHandle) { 1162 CacheFileIOManager::DoomFile(index->mJournalHandle, nullptr); 1163 } 1164 1165 switch (index->mState) { 1166 case WRITING: 1167 index->FinishWrite(false, lock); 1168 break; 1169 case READY: 1170 // nothing to do 1171 break; 1172 case READING: 1173 index->FinishRead(false, lock); 1174 break; 1175 case BUILDING: 1176 case UPDATING: 1177 index->FinishUpdate(false, lock); 1178 break; 1179 default: 1180 MOZ_ASSERT(false, "Unexpected state!"); 1181 } 1182 1183 // We should end up in READY state 1184 MOZ_ASSERT(index->mState == READY); 1185 1186 // There should not be any handle 1187 MOZ_ASSERT(!index->mIndexHandle); 1188 MOZ_ASSERT(!index->mJournalHandle); 1189 1190 index->mIndexOnDiskIsValid = false; 1191 index->mIndexNeedsUpdate = false; 1192 1193 index->mIndexStats.Clear(); 1194 index->mFrecencyStorage.Clear(lock); 1195 index->mIndex.Clear(); 1196 1197 for (uint32_t i = 0; i < index->mIterators.Length();) { 1198 nsresult rv = index->mIterators[i]->CloseInternal(NS_ERROR_NOT_AVAILABLE); 1199 if (NS_FAILED(rv)) { 1200 // CacheIndexIterator::CloseInternal() removes itself from mIterators 1201 // iff it returns success. 1202 LOG( 1203 ("CacheIndex::RemoveAll() - Failed to remove iterator %p. " 1204 "[rv=0x%08" PRIx32 "]", 1205 index->mIterators[i], static_cast<uint32_t>(rv))); 1206 i++; 1207 } 1208 } 1209 } 1210 1211 if (file) { 1212 // Ignore the result. The file might not exist and the failure is not fatal. 1213 file->Remove(false); 1214 } 1215 1216 return NS_OK; 1217 } 1218 1219 // static 1220 nsresult CacheIndex::HasEntry( 1221 const nsACString& aKey, EntryStatus* _retval, 1222 const std::function<void(const CacheIndexEntry*)>& aCB) { 1223 LOG(("CacheIndex::HasEntry() [key=%s]", PromiseFlatCString(aKey).get())); 1224 1225 SHA1Sum sum; 1226 SHA1Sum::Hash hash; 1227 sum.update(aKey.BeginReading(), aKey.Length()); 1228 sum.finish(hash); 1229 1230 return HasEntry(hash, _retval, aCB); 1231 } 1232 1233 // static 1234 nsresult CacheIndex::HasEntry( 1235 const SHA1Sum::Hash& hash, EntryStatus* _retval, 1236 const std::function<void(const CacheIndexEntry*)>& aCB) { 1237 StaticMutexAutoLock lock(sLock); 1238 1239 RefPtr<CacheIndex> index = gInstance; 1240 1241 if (!index) { 1242 return NS_ERROR_NOT_INITIALIZED; 1243 } 1244 1245 if (!index->IsIndexUsable()) { 1246 return NS_ERROR_NOT_AVAILABLE; 1247 } 1248 1249 const CacheIndexEntry* entry = nullptr; 1250 1251 switch (index->mState) { 1252 case READING: 1253 case WRITING: 1254 entry = index->mPendingUpdates.GetEntry(hash); 1255 [[fallthrough]]; 1256 case BUILDING: 1257 case UPDATING: 1258 case READY: 1259 if (!entry) { 1260 entry = index->mIndex.GetEntry(hash); 1261 } 1262 break; 1263 case INITIAL: 1264 case SHUTDOWN: 1265 MOZ_ASSERT(false, "Unexpected state!"); 1266 } 1267 1268 if (!entry) { 1269 if (index->mState == READY || index->mState == WRITING) { 1270 *_retval = DOES_NOT_EXIST; 1271 } else { 1272 *_retval = DO_NOT_KNOW; 1273 } 1274 } else { 1275 if (entry->IsRemoved()) { 1276 if (entry->IsFresh()) { 1277 *_retval = DOES_NOT_EXIST; 1278 } else { 1279 *_retval = DO_NOT_KNOW; 1280 } 1281 } else { 1282 *_retval = EXISTS; 1283 if (aCB) { 1284 aCB(entry); 1285 } 1286 } 1287 } 1288 1289 LOG(("CacheIndex::HasEntry() - result is %u", *_retval)); 1290 return NS_OK; 1291 } 1292 1293 // static 1294 // GetEntryForEviction is used by OverLimitEvictionInternal where we create and 1295 // keep our EvictionSortedSnapshot while looping. 1296 nsresult CacheIndex::GetEntryForEviction(EvictionSortedSnapshot& aSnapshot, 1297 bool aIgnoreEmptyEntries, 1298 SHA1Sum::Hash* aHash, uint32_t* aCnt) { 1299 LOG(("CacheIndex::GetEntryForEviction()")); 1300 1301 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 1302 1303 StaticMutexAutoLock lock(sLock); 1304 1305 RefPtr<CacheIndex> index = gInstance; 1306 1307 if (!index) return NS_ERROR_NOT_INITIALIZED; 1308 1309 if (!index->IsIndexUsable()) { 1310 return NS_ERROR_NOT_AVAILABLE; 1311 } 1312 1313 if (index->mIndexStats.Size() == 0) { 1314 return NS_ERROR_NOT_AVAILABLE; 1315 } 1316 1317 int32_t mediaUsage = 1318 round(static_cast<double>(index->mIndexStats.SizeByType( 1319 nsICacheEntry::CONTENT_TYPE_MEDIA)) * 1320 100.0 / static_cast<double>(index->mIndexStats.Size())); 1321 int32_t mediaUsageLimit = 1322 StaticPrefs::browser_cache_disk_content_type_media_limit(); 1323 bool evictMedia = false; 1324 if (mediaUsage > mediaUsageLimit) { 1325 LOG( 1326 ("CacheIndex::GetEntryForEviction() - media content type is over the " 1327 "limit [mediaUsage=%d, mediaUsageLimit=%d]", 1328 mediaUsage, mediaUsageLimit)); 1329 evictMedia = true; 1330 } 1331 1332 SHA1Sum::Hash hash; 1333 CacheIndexRecord* foundRecord = nullptr; 1334 uint32_t skipped = 0; 1335 size_t recordPosition = 0; 1336 1337 // find first non-forced valid and unpinned entry with the lowest frecency 1338 for (size_t i = 0; i < aSnapshot.Length(); ++i) { 1339 if (!aSnapshot[i]) { 1340 continue; // Skip the null records 1341 } 1342 CacheIndexRecord* rec = aSnapshot[i]->Get(); 1343 if (!rec) { 1344 continue; // Skip the null records 1345 } 1346 1347 memcpy(&hash, rec->mHash, sizeof(SHA1Sum::Hash)); 1348 1349 ++skipped; 1350 1351 uint32_t type = CacheIndexEntry::GetContentType(rec); 1352 1353 if (evictMedia && type != nsICacheEntry::CONTENT_TYPE_MEDIA) { 1354 continue; 1355 } 1356 1357 if (type == nsICacheEntry::CONTENT_TYPE_DICTIONARY) { 1358 // Let them be removed by becoming empty and removing themselves 1359 continue; 1360 } 1361 1362 if (IsForcedValidEntry(&hash)) { 1363 continue; 1364 } 1365 1366 if (CacheIndexEntry::IsPinned(rec)) { 1367 continue; 1368 } 1369 1370 if (aIgnoreEmptyEntries && !CacheIndexEntry::GetFileSize(*rec)) { 1371 continue; 1372 } 1373 1374 --skipped; 1375 foundRecord = rec; 1376 recordPosition = i; 1377 break; 1378 } 1379 1380 if (!foundRecord) return NS_ERROR_NOT_AVAILABLE; 1381 1382 *aCnt = skipped; 1383 1384 LOG( 1385 ("CacheIndex::GetEntryForEviction() - returning entry " 1386 "[hash=%08x%08x%08x%08x%08x, cnt=%u, frecency=%u, contentType=%u]", 1387 LOGSHA1(&hash), *aCnt, foundRecord->mFrecency, 1388 CacheIndexEntry::GetContentType(foundRecord))); 1389 1390 memcpy(aHash, &hash, sizeof(SHA1Sum::Hash)); 1391 aSnapshot[recordPosition] = nullptr; // Remove the record from the snapshot 1392 1393 return NS_OK; 1394 } 1395 1396 // static 1397 bool CacheIndex::IsForcedValidEntry(const SHA1Sum::Hash* aHash) { 1398 RefPtr<CacheFileHandle> handle; 1399 1400 CacheFileIOManager::gInstance->mHandles.GetHandle(aHash, 1401 getter_AddRefs(handle)); 1402 1403 if (!handle) return false; 1404 1405 nsCString hashKey = handle->Key(); 1406 return CacheStorageService::Self()->IsForcedValidEntry(hashKey); 1407 } 1408 1409 // static 1410 nsresult CacheIndex::GetCacheSize(uint32_t* _retval) { 1411 LOG(("CacheIndex::GetCacheSize()")); 1412 1413 StaticMutexAutoLock lock(sLock); 1414 1415 RefPtr<CacheIndex> index = gInstance; 1416 1417 if (!index) return NS_ERROR_NOT_INITIALIZED; 1418 1419 if (!index->IsIndexUsable()) { 1420 return NS_ERROR_NOT_AVAILABLE; 1421 } 1422 1423 *_retval = index->mIndexStats.Size(); 1424 LOG(("CacheIndex::GetCacheSize() - returning %u", *_retval)); 1425 return NS_OK; 1426 } 1427 1428 // static 1429 nsresult CacheIndex::GetEntryFileCount(uint32_t* _retval) { 1430 LOG(("CacheIndex::GetEntryFileCount()")); 1431 1432 StaticMutexAutoLock lock(sLock); 1433 1434 RefPtr<CacheIndex> index = gInstance; 1435 1436 if (!index) { 1437 return NS_ERROR_NOT_INITIALIZED; 1438 } 1439 1440 if (!index->IsIndexUsable()) { 1441 return NS_ERROR_NOT_AVAILABLE; 1442 } 1443 1444 *_retval = index->mIndexStats.ActiveEntriesCount(); 1445 LOG(("CacheIndex::GetEntryFileCount() - returning %u", *_retval)); 1446 return NS_OK; 1447 } 1448 1449 // static 1450 nsresult CacheIndex::GetCacheStats(nsILoadContextInfo* aInfo, uint32_t* aSize, 1451 uint32_t* aCount) { 1452 LOG(("CacheIndex::GetCacheStats() [info=%p]", aInfo)); 1453 1454 StaticMutexAutoLock lock(sLock); 1455 1456 RefPtr<CacheIndex> index = gInstance; 1457 1458 if (!index) { 1459 return NS_ERROR_NOT_INITIALIZED; 1460 } 1461 1462 if (!index->IsIndexUsable()) { 1463 return NS_ERROR_NOT_AVAILABLE; 1464 } 1465 1466 *aSize = 0; 1467 *aCount = 0; 1468 1469 for (const auto& item : index->mFrecencyStorage.mRecs) { 1470 if (aInfo && 1471 !CacheIndexEntry::RecordMatchesLoadContextInfo(item.GetKey(), aInfo)) { 1472 continue; 1473 } 1474 1475 *aSize += CacheIndexEntry::GetFileSize(*(item.GetKey()->Get())); 1476 ++*aCount; 1477 } 1478 1479 return NS_OK; 1480 } 1481 1482 // static 1483 nsresult CacheIndex::AsyncGetDiskConsumption( 1484 nsICacheStorageConsumptionObserver* aObserver) { 1485 LOG(("CacheIndex::AsyncGetDiskConsumption()")); 1486 1487 StaticMutexAutoLock lock(sLock); 1488 1489 RefPtr<CacheIndex> index = gInstance; 1490 1491 if (!index) { 1492 return NS_ERROR_NOT_INITIALIZED; 1493 } 1494 1495 if (!index->IsIndexUsable()) { 1496 return NS_ERROR_NOT_AVAILABLE; 1497 } 1498 1499 RefPtr<DiskConsumptionObserver> observer = 1500 DiskConsumptionObserver::Init(aObserver); 1501 1502 NS_ENSURE_ARG(observer); 1503 1504 if ((index->mState == READY || index->mState == WRITING) && 1505 !index->mAsyncGetDiskConsumptionBlocked) { 1506 LOG(("CacheIndex::AsyncGetDiskConsumption - calling immediately")); 1507 // Safe to call the callback under the lock, 1508 // we always post to the main thread. 1509 observer->OnDiskConsumption(index->mIndexStats.Size() << 10); 1510 return NS_OK; 1511 } 1512 1513 LOG(("CacheIndex::AsyncGetDiskConsumption - remembering callback")); 1514 // Will be called when the index get to the READY state. 1515 index->mDiskConsumptionObservers.AppendElement(observer); 1516 1517 // Move forward with index re/building if it is pending 1518 RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread(); 1519 if (ioThread) { 1520 ioThread->Dispatch( 1521 NS_NewRunnableFunction("net::CacheIndex::AsyncGetDiskConsumption", 1522 []() -> void { 1523 StaticMutexAutoLock lock(sLock); 1524 1525 RefPtr<CacheIndex> index = gInstance; 1526 if (index && index->mUpdateTimer) { 1527 index->mUpdateTimer->Cancel(); 1528 index->DelayedUpdateLocked(lock); 1529 } 1530 }), 1531 CacheIOThread::INDEX); 1532 } 1533 1534 return NS_OK; 1535 } 1536 1537 // static 1538 nsresult CacheIndex::GetIterator(nsILoadContextInfo* aInfo, bool aAddNew, 1539 CacheIndexIterator** _retval) { 1540 LOG(("CacheIndex::GetIterator() [info=%p, addNew=%d]", aInfo, aAddNew)); 1541 1542 StaticMutexAutoLock lock(sLock); 1543 1544 RefPtr<CacheIndex> index = gInstance; 1545 1546 if (!index) { 1547 return NS_ERROR_NOT_INITIALIZED; 1548 } 1549 1550 if (!index->IsIndexUsable()) { 1551 return NS_ERROR_NOT_AVAILABLE; 1552 } 1553 1554 RefPtr<CacheIndexIterator> idxIter; 1555 if (aInfo) { 1556 idxIter = new CacheIndexContextIterator(index, aAddNew, aInfo); 1557 } else { 1558 idxIter = new CacheIndexIterator(index, aAddNew); 1559 } 1560 for (const auto& item : index->mFrecencyStorage.mRecs) { 1561 idxIter->AddRecord(item.GetKey(), lock); 1562 } 1563 1564 index->mIterators.AppendElement(idxIter); 1565 idxIter.swap(*_retval); 1566 return NS_OK; 1567 } 1568 1569 // static 1570 nsresult CacheIndex::IsUpToDate(bool* _retval) { 1571 LOG(("CacheIndex::IsUpToDate()")); 1572 1573 StaticMutexAutoLock lock(sLock); 1574 1575 RefPtr<CacheIndex> index = gInstance; 1576 1577 if (!index) { 1578 return NS_ERROR_NOT_INITIALIZED; 1579 } 1580 1581 if (!index->IsIndexUsable()) { 1582 return NS_ERROR_NOT_AVAILABLE; 1583 } 1584 1585 *_retval = (index->mState == READY || index->mState == WRITING) && 1586 !index->mIndexNeedsUpdate && !index->mShuttingDown; 1587 1588 LOG(("CacheIndex::IsUpToDate() - returning %d", *_retval)); 1589 return NS_OK; 1590 } 1591 1592 bool CacheIndex::IsIndexUsable() { 1593 MOZ_ASSERT(mState != INITIAL); 1594 1595 switch (mState) { 1596 case INITIAL: 1597 case SHUTDOWN: 1598 return false; 1599 1600 case READING: 1601 case WRITING: 1602 case BUILDING: 1603 case UPDATING: 1604 case READY: 1605 break; 1606 } 1607 1608 return true; 1609 } 1610 1611 // static 1612 bool CacheIndex::IsCollision(CacheIndexEntry* aEntry, 1613 OriginAttrsHash aOriginAttrsHash, 1614 bool aAnonymous) { 1615 if (!aEntry->IsInitialized()) { 1616 return false; 1617 } 1618 1619 if (aEntry->Anonymous() != aAnonymous || 1620 aEntry->OriginAttrsHash() != aOriginAttrsHash) { 1621 LOG( 1622 ("CacheIndex::IsCollision() - Collision detected for entry hash=%08x" 1623 "%08x%08x%08x%08x, expected values: originAttrsHash=%" PRIu64 ", " 1624 "anonymous=%d; actual values: originAttrsHash=%" PRIu64 1625 ", anonymous=%d]", 1626 LOGSHA1(aEntry->Hash()), aOriginAttrsHash, aAnonymous, 1627 aEntry->OriginAttrsHash(), aEntry->Anonymous())); 1628 return true; 1629 } 1630 1631 return false; 1632 } 1633 1634 // static 1635 bool CacheIndex::HasEntryChanged( 1636 CacheIndexEntry* aEntry, const uint32_t* aFrecency, const bool* aHasAltData, 1637 const uint16_t* aOnStartTime, const uint16_t* aOnStopTime, 1638 const uint8_t* aContentType, const uint32_t* aSize) { 1639 if (aFrecency && *aFrecency != aEntry->GetFrecency()) { 1640 return true; 1641 } 1642 1643 if (aHasAltData && *aHasAltData != aEntry->GetHasAltData()) { 1644 return true; 1645 } 1646 1647 if (aOnStartTime && *aOnStartTime != aEntry->GetOnStartTime()) { 1648 return true; 1649 } 1650 1651 if (aOnStopTime && *aOnStopTime != aEntry->GetOnStopTime()) { 1652 return true; 1653 } 1654 1655 if (aContentType && *aContentType != aEntry->GetContentType()) { 1656 return true; 1657 } 1658 1659 if (aSize && 1660 (*aSize & CacheIndexEntry::kFileSizeMask) != aEntry->GetFileSize()) { 1661 return true; 1662 } 1663 1664 return false; 1665 } 1666 1667 void CacheIndex::ProcessPendingOperations( 1668 const StaticMutexAutoLock& aProofOfLock) { 1669 sLock.AssertCurrentThreadOwns(); 1670 LOG(("CacheIndex::ProcessPendingOperations()")); 1671 1672 for (auto iter = mPendingUpdates.Iter(); !iter.Done(); iter.Next()) { 1673 CacheIndexEntryUpdate* update = iter.Get(); 1674 1675 LOG(("CacheIndex::ProcessPendingOperations() [hash=%08x%08x%08x%08x%08x]", 1676 LOGSHA1(update->Hash()))); 1677 1678 MOZ_ASSERT(update->IsFresh()); 1679 1680 CacheIndexEntry* entry = mIndex.GetEntry(*update->Hash()); 1681 { 1682 CacheIndexEntryAutoManage emng(update->Hash(), this, aProofOfLock); 1683 emng.DoNotSearchInUpdates(); 1684 1685 if (update->IsRemoved()) { 1686 if (entry) { 1687 if (entry->IsRemoved()) { 1688 MOZ_ASSERT(entry->IsFresh()); 1689 MOZ_ASSERT(entry->IsDirty()); 1690 } else if (!entry->IsDirty() && entry->IsFileEmpty()) { 1691 // Entries with empty file are not stored in index on disk. Just 1692 // remove the entry, but only in case the entry is not dirty, i.e. 1693 // the entry file was empty when we wrote the index. 1694 mIndex.RemoveEntry(entry); 1695 entry = nullptr; 1696 } else { 1697 entry->MarkRemoved(); 1698 entry->MarkDirty(); 1699 entry->MarkFresh(); 1700 } 1701 } 1702 } else if (entry) { 1703 // Some information in mIndex can be newer than in mPendingUpdates (see 1704 // bug 1074832). This will copy just those values that were really 1705 // updated. 1706 update->ApplyUpdate(entry); 1707 } else { 1708 // There is no entry in mIndex, copy all information from 1709 // mPendingUpdates to mIndex. 1710 entry = mIndex.PutEntry(*update->Hash()); 1711 *entry = *update; 1712 } 1713 } 1714 iter.Remove(); 1715 } 1716 1717 MOZ_ASSERT(mPendingUpdates.Count() == 0); 1718 1719 EnsureCorrectStats(); 1720 } 1721 1722 bool CacheIndex::WriteIndexToDiskIfNeeded( 1723 const StaticMutexAutoLock& aProofOfLock) { 1724 sLock.AssertCurrentThreadOwns(); 1725 if (mState != READY || mShuttingDown || mRWPending) { 1726 return false; 1727 } 1728 1729 if (!mLastDumpTime.IsNull() && 1730 (TimeStamp::NowLoRes() - mLastDumpTime).ToMilliseconds() < 1731 kMinDumpInterval) { 1732 return false; 1733 } 1734 1735 if (mIndexStats.Dirty() < kMinUnwrittenChanges) { 1736 return false; 1737 } 1738 1739 WriteIndexToDisk(aProofOfLock); 1740 return true; 1741 } 1742 1743 void CacheIndex::WriteIndexToDisk(const StaticMutexAutoLock& aProofOfLock) { 1744 sLock.AssertCurrentThreadOwns(); 1745 LOG(("CacheIndex::WriteIndexToDisk()")); 1746 mIndexStats.Log(); 1747 1748 nsresult rv; 1749 1750 MOZ_ASSERT(mState == READY); 1751 MOZ_ASSERT(!mRWBuf); 1752 MOZ_ASSERT(!mRWHash); 1753 MOZ_ASSERT(!mRWPending); 1754 1755 ChangeState(WRITING, aProofOfLock); 1756 1757 mProcessEntries = mIndexStats.ActiveEntriesCount(); 1758 1759 mIndexFileOpener = new FileOpenHelper(this); 1760 rv = CacheFileIOManager::OpenFile( 1761 nsLiteralCString(TEMP_INDEX_NAME), 1762 CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::CREATE, 1763 mIndexFileOpener); 1764 if (NS_FAILED(rv)) { 1765 LOG(("CacheIndex::WriteIndexToDisk() - Can't open file [rv=0x%08" PRIx32 1766 "]", 1767 static_cast<uint32_t>(rv))); 1768 FinishWrite(false, aProofOfLock); 1769 return; 1770 } 1771 1772 // Write index header to a buffer, it will be written to disk together with 1773 // records in WriteRecords() once we open the file successfully. 1774 AllocBuffer(); 1775 mRWHash = new CacheHash(); 1776 1777 mRWBufPos = 0; 1778 // index version 1779 NetworkEndian::writeUint32(mRWBuf + mRWBufPos, kIndexVersion); 1780 mRWBufPos += sizeof(uint32_t); 1781 // timestamp 1782 NetworkEndian::writeUint32(mRWBuf + mRWBufPos, 1783 static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC)); 1784 mRWBufPos += sizeof(uint32_t); 1785 // dirty flag 1786 NetworkEndian::writeUint32(mRWBuf + mRWBufPos, 1); 1787 mRWBufPos += sizeof(uint32_t); 1788 // amount of data written to the cache 1789 NetworkEndian::writeUint32(mRWBuf + mRWBufPos, 1790 static_cast<uint32_t>(mTotalBytesWritten >> 10)); 1791 mRWBufPos += sizeof(uint32_t); 1792 1793 mSkipEntries = 0; 1794 } 1795 1796 void CacheIndex::WriteRecords(const StaticMutexAutoLock& aProofOfLock) { 1797 sLock.AssertCurrentThreadOwns(); 1798 LOG(("CacheIndex::WriteRecords()")); 1799 1800 nsresult rv; 1801 1802 MOZ_ASSERT(mState == WRITING); 1803 MOZ_ASSERT(!mRWPending); 1804 1805 int64_t fileOffset; 1806 1807 if (mSkipEntries) { 1808 MOZ_ASSERT(mRWBufPos == 0); 1809 fileOffset = sizeof(CacheIndexHeader); 1810 fileOffset += sizeof(CacheIndexRecord) * mSkipEntries; 1811 } else { 1812 MOZ_ASSERT(mRWBufPos == sizeof(CacheIndexHeader)); 1813 fileOffset = 0; 1814 } 1815 uint32_t hashOffset = mRWBufPos; 1816 1817 char* buf = mRWBuf + mRWBufPos; 1818 uint32_t skip = mSkipEntries; 1819 uint32_t processMax = (mRWBufSize - mRWBufPos) / sizeof(CacheIndexRecord); 1820 MOZ_ASSERT(processMax != 0 || 1821 mProcessEntries == 1822 0); // TODO make sure we can write an empty index 1823 uint32_t processed = 0; 1824 #ifdef DEBUG 1825 bool hasMore = false; 1826 #endif 1827 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) { 1828 CacheIndexEntry* entry = iter.Get(); 1829 if (entry->IsRemoved() || !entry->IsInitialized() || entry->IsFileEmpty()) { 1830 continue; 1831 } 1832 1833 if (skip) { 1834 skip--; 1835 continue; 1836 } 1837 1838 if (processed == processMax) { 1839 #ifdef DEBUG 1840 hasMore = true; 1841 #endif 1842 break; 1843 } 1844 1845 entry->WriteToBuf(buf); 1846 buf += sizeof(CacheIndexRecord); 1847 processed++; 1848 } 1849 1850 MOZ_ASSERT(mRWBufPos != static_cast<uint32_t>(buf - mRWBuf) || 1851 mProcessEntries == 0); 1852 mRWBufPos = buf - mRWBuf; 1853 mSkipEntries += processed; 1854 MOZ_ASSERT(mSkipEntries <= mProcessEntries); 1855 1856 mRWHash->Update(mRWBuf + hashOffset, mRWBufPos - hashOffset); 1857 1858 if (mSkipEntries == mProcessEntries) { 1859 MOZ_ASSERT(!hasMore); 1860 1861 // We've processed all records 1862 if (mRWBufPos + sizeof(CacheHash::Hash32_t) > mRWBufSize) { 1863 // realloc buffer to spare another write cycle 1864 mRWBufSize = mRWBufPos + sizeof(CacheHash::Hash32_t); 1865 mRWBuf = static_cast<char*>(moz_xrealloc(mRWBuf, mRWBufSize)); 1866 } 1867 1868 NetworkEndian::writeUint32(mRWBuf + mRWBufPos, mRWHash->GetHash()); 1869 mRWBufPos += sizeof(CacheHash::Hash32_t); 1870 } else { 1871 MOZ_ASSERT(hasMore); 1872 } 1873 1874 rv = CacheFileIOManager::Write(mIndexHandle, fileOffset, mRWBuf, mRWBufPos, 1875 mSkipEntries == mProcessEntries, false, this); 1876 if (NS_FAILED(rv)) { 1877 LOG( 1878 ("CacheIndex::WriteRecords() - CacheFileIOManager::Write() failed " 1879 "synchronously [rv=0x%08" PRIx32 "]", 1880 static_cast<uint32_t>(rv))); 1881 FinishWrite(false, aProofOfLock); 1882 } else { 1883 mRWPending = true; 1884 } 1885 1886 mRWBufPos = 0; 1887 } 1888 1889 void CacheIndex::FinishWrite(bool aSucceeded, 1890 const StaticMutexAutoLock& aProofOfLock) { 1891 sLock.AssertCurrentThreadOwns(); 1892 LOG(("CacheIndex::FinishWrite() [succeeded=%d]", aSucceeded)); 1893 1894 MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == WRITING); 1895 1896 // If there is write operation pending we must be cancelling writing of the 1897 // index when shutting down or removing the whole index. 1898 MOZ_ASSERT(!mRWPending || (!aSucceeded && (mShuttingDown || mRemovingAll))); 1899 1900 mIndexHandle = nullptr; 1901 mRWHash = nullptr; 1902 ReleaseBuffer(); 1903 1904 if (aSucceeded) { 1905 // Opening of the file must not be in progress if writing succeeded. 1906 MOZ_ASSERT(!mIndexFileOpener); 1907 1908 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) { 1909 CacheIndexEntry* entry = iter.Get(); 1910 1911 bool remove = false; 1912 { 1913 CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock); 1914 1915 if (entry->IsRemoved()) { 1916 emng.DoNotSearchInIndex(); 1917 remove = true; 1918 } else if (entry->IsDirty()) { 1919 entry->ClearDirty(); 1920 } 1921 } 1922 if (remove) { 1923 iter.Remove(); 1924 } 1925 } 1926 1927 mIndexOnDiskIsValid = true; 1928 } else { 1929 if (mIndexFileOpener) { 1930 // If opening of the file is still in progress (e.g. WRITE process was 1931 // canceled by RemoveAll()) then we need to cancel the opener to make sure 1932 // that OnFileOpenedInternal() won't be called. 1933 mIndexFileOpener->Cancel(); 1934 mIndexFileOpener = nullptr; 1935 } 1936 } 1937 1938 ProcessPendingOperations(aProofOfLock); 1939 mIndexStats.Log(); 1940 1941 if (mState == WRITING) { 1942 ChangeState(READY, aProofOfLock); 1943 mLastDumpTime = TimeStamp::NowLoRes(); 1944 } 1945 } 1946 1947 nsresult CacheIndex::GetFile(const nsACString& aName, nsIFile** _retval) { 1948 nsresult rv; 1949 1950 nsCOMPtr<nsIFile> file; 1951 rv = mCacheDirectory->Clone(getter_AddRefs(file)); 1952 NS_ENSURE_SUCCESS(rv, rv); 1953 1954 rv = file->AppendNative(aName); 1955 NS_ENSURE_SUCCESS(rv, rv); 1956 1957 file.swap(*_retval); 1958 return NS_OK; 1959 } 1960 1961 void CacheIndex::RemoveFile(const nsACString& aName) { 1962 MOZ_ASSERT(mState == SHUTDOWN); 1963 1964 nsresult rv; 1965 1966 nsCOMPtr<nsIFile> file; 1967 rv = GetFile(aName, getter_AddRefs(file)); 1968 NS_ENSURE_SUCCESS_VOID(rv); 1969 1970 rv = file->Remove(false); 1971 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { 1972 LOG( 1973 ("CacheIndex::RemoveFile() - Cannot remove old entry file from disk " 1974 "[rv=0x%08" PRIx32 ", name=%s]", 1975 static_cast<uint32_t>(rv), PromiseFlatCString(aName).get())); 1976 } 1977 } 1978 1979 void CacheIndex::RemoveAllIndexFiles() { 1980 LOG(("CacheIndex::RemoveAllIndexFiles()")); 1981 RemoveFile(nsLiteralCString(INDEX_NAME)); 1982 RemoveJournalAndTempFile(); 1983 } 1984 1985 void CacheIndex::RemoveJournalAndTempFile() { 1986 LOG(("CacheIndex::RemoveJournalAndTempFile()")); 1987 RemoveFile(nsLiteralCString(TEMP_INDEX_NAME)); 1988 RemoveFile(nsLiteralCString(JOURNAL_NAME)); 1989 } 1990 1991 class WriteLogHelper { 1992 public: 1993 explicit WriteLogHelper(PRFileDesc* aFD) 1994 : mFD(aFD), mBufSize(kMaxBufSize), mBufPos(0) { 1995 mHash = new CacheHash(); 1996 mBuf = static_cast<char*>(moz_xmalloc(mBufSize)); 1997 } 1998 1999 ~WriteLogHelper() { free(mBuf); } 2000 2001 nsresult AddEntry(CacheIndexEntry* aEntry); 2002 nsresult Finish(); 2003 2004 private: 2005 nsresult FlushBuffer(); 2006 2007 PRFileDesc* mFD; 2008 char* mBuf; 2009 uint32_t mBufSize; 2010 int32_t mBufPos; 2011 RefPtr<CacheHash> mHash; 2012 }; 2013 2014 nsresult WriteLogHelper::AddEntry(CacheIndexEntry* aEntry) { 2015 nsresult rv; 2016 2017 if (mBufPos + sizeof(CacheIndexRecord) > mBufSize) { 2018 mHash->Update(mBuf, mBufPos); 2019 2020 rv = FlushBuffer(); 2021 NS_ENSURE_SUCCESS(rv, rv); 2022 MOZ_ASSERT(mBufPos + sizeof(CacheIndexRecord) <= mBufSize); 2023 } 2024 2025 aEntry->WriteToBuf(mBuf + mBufPos); 2026 mBufPos += sizeof(CacheIndexRecord); 2027 2028 return NS_OK; 2029 } 2030 2031 nsresult WriteLogHelper::Finish() { 2032 nsresult rv; 2033 2034 mHash->Update(mBuf, mBufPos); 2035 if (mBufPos + sizeof(CacheHash::Hash32_t) > mBufSize) { 2036 rv = FlushBuffer(); 2037 NS_ENSURE_SUCCESS(rv, rv); 2038 MOZ_ASSERT(mBufPos + sizeof(CacheHash::Hash32_t) <= mBufSize); 2039 } 2040 2041 NetworkEndian::writeUint32(mBuf + mBufPos, mHash->GetHash()); 2042 mBufPos += sizeof(CacheHash::Hash32_t); 2043 2044 rv = FlushBuffer(); 2045 NS_ENSURE_SUCCESS(rv, rv); 2046 2047 return NS_OK; 2048 } 2049 2050 nsresult WriteLogHelper::FlushBuffer() { 2051 if (CacheObserver::IsPastShutdownIOLag()) { 2052 LOG(("WriteLogHelper::FlushBuffer() - Interrupting writing journal.")); 2053 return NS_ERROR_FAILURE; 2054 } 2055 2056 int32_t bytesWritten = PR_Write(mFD, mBuf, mBufPos); 2057 2058 if (bytesWritten != mBufPos) { 2059 return NS_ERROR_FAILURE; 2060 } 2061 2062 mBufPos = 0; 2063 return NS_OK; 2064 } 2065 2066 nsresult CacheIndex::WriteLogToDisk() { 2067 LOG(("CacheIndex::WriteLogToDisk()")); 2068 2069 nsresult rv; 2070 2071 MOZ_ASSERT(mPendingUpdates.Count() == 0); 2072 MOZ_ASSERT(mState == SHUTDOWN); 2073 2074 if (CacheObserver::IsPastShutdownIOLag()) { 2075 LOG(("CacheIndex::WriteLogToDisk() - Skipping writing journal.")); 2076 return NS_ERROR_FAILURE; 2077 } 2078 2079 RemoveFile(nsLiteralCString(TEMP_INDEX_NAME)); 2080 2081 nsCOMPtr<nsIFile> indexFile; 2082 rv = GetFile(nsLiteralCString(INDEX_NAME), getter_AddRefs(indexFile)); 2083 NS_ENSURE_SUCCESS(rv, rv); 2084 2085 nsCOMPtr<nsIFile> logFile; 2086 rv = GetFile(nsLiteralCString(JOURNAL_NAME), getter_AddRefs(logFile)); 2087 NS_ENSURE_SUCCESS(rv, rv); 2088 2089 mIndexStats.Log(); 2090 2091 PRFileDesc* fd = nullptr; 2092 rv = logFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, 2093 &fd); 2094 NS_ENSURE_SUCCESS(rv, rv); 2095 2096 WriteLogHelper wlh(fd); 2097 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) { 2098 CacheIndexEntry* entry = iter.Get(); 2099 if (entry->IsRemoved() || entry->IsDirty()) { 2100 rv = wlh.AddEntry(entry); 2101 if (NS_WARN_IF(NS_FAILED(rv))) { 2102 return rv; 2103 } 2104 } 2105 } 2106 2107 rv = wlh.Finish(); 2108 PR_Close(fd); 2109 NS_ENSURE_SUCCESS(rv, rv); 2110 2111 rv = indexFile->OpenNSPRFileDesc(PR_RDWR, 0600, &fd); 2112 NS_ENSURE_SUCCESS(rv, rv); 2113 2114 // Seek to dirty flag in the index header and clear it. 2115 static_assert(2 * sizeof(uint32_t) == offsetof(CacheIndexHeader, mIsDirty), 2116 "Unexpected offset of CacheIndexHeader::mIsDirty"); 2117 int64_t offset = PR_Seek64(fd, 2 * sizeof(uint32_t), PR_SEEK_SET); 2118 if (offset == -1) { 2119 PR_Close(fd); 2120 return NS_ERROR_FAILURE; 2121 } 2122 2123 uint32_t isDirty = 0; 2124 int32_t bytesWritten = PR_Write(fd, &isDirty, sizeof(isDirty)); 2125 PR_Close(fd); 2126 if (bytesWritten != sizeof(isDirty)) { 2127 return NS_ERROR_FAILURE; 2128 } 2129 2130 return NS_OK; 2131 } 2132 2133 void CacheIndex::ReadIndexFromDisk(const StaticMutexAutoLock& aProofOfLock) { 2134 sLock.AssertCurrentThreadOwns(); 2135 LOG(("CacheIndex::ReadIndexFromDisk()")); 2136 2137 nsresult rv; 2138 2139 MOZ_ASSERT(mState == INITIAL); 2140 2141 ChangeState(READING, aProofOfLock); 2142 2143 mIndexFileOpener = new FileOpenHelper(this); 2144 rv = CacheFileIOManager::OpenFile( 2145 nsLiteralCString(INDEX_NAME), 2146 CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN, 2147 mIndexFileOpener); 2148 if (NS_FAILED(rv)) { 2149 LOG( 2150 ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() " 2151 "failed [rv=0x%08" PRIx32 ", file=%s]", 2152 static_cast<uint32_t>(rv), INDEX_NAME)); 2153 FinishRead(false, aProofOfLock); 2154 return; 2155 } 2156 2157 mJournalFileOpener = new FileOpenHelper(this); 2158 rv = CacheFileIOManager::OpenFile( 2159 nsLiteralCString(JOURNAL_NAME), 2160 CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN, 2161 mJournalFileOpener); 2162 if (NS_FAILED(rv)) { 2163 LOG( 2164 ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() " 2165 "failed [rv=0x%08" PRIx32 ", file=%s]", 2166 static_cast<uint32_t>(rv), JOURNAL_NAME)); 2167 FinishRead(false, aProofOfLock); 2168 } 2169 2170 mTmpFileOpener = new FileOpenHelper(this); 2171 rv = CacheFileIOManager::OpenFile( 2172 nsLiteralCString(TEMP_INDEX_NAME), 2173 CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN, 2174 mTmpFileOpener); 2175 if (NS_FAILED(rv)) { 2176 LOG( 2177 ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() " 2178 "failed [rv=0x%08" PRIx32 ", file=%s]", 2179 static_cast<uint32_t>(rv), TEMP_INDEX_NAME)); 2180 FinishRead(false, aProofOfLock); 2181 } 2182 } 2183 2184 void CacheIndex::StartReadingIndex(const StaticMutexAutoLock& aProofOfLock) { 2185 sLock.AssertCurrentThreadOwns(); 2186 LOG(("CacheIndex::StartReadingIndex()")); 2187 2188 nsresult rv; 2189 2190 MOZ_ASSERT(mIndexHandle); 2191 MOZ_ASSERT(mState == READING); 2192 MOZ_ASSERT(!mIndexOnDiskIsValid); 2193 MOZ_ASSERT(!mDontMarkIndexClean); 2194 MOZ_ASSERT(!mJournalReadSuccessfully); 2195 MOZ_ASSERT(mIndexHandle->FileSize() >= 0); 2196 MOZ_ASSERT(!mRWPending); 2197 2198 int64_t entriesSize = mIndexHandle->FileSize() - sizeof(CacheIndexHeader) - 2199 sizeof(CacheHash::Hash32_t); 2200 2201 if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) { 2202 LOG(("CacheIndex::StartReadingIndex() - Index is corrupted")); 2203 FinishRead(false, aProofOfLock); 2204 return; 2205 } 2206 2207 AllocBuffer(); 2208 mSkipEntries = 0; 2209 mRWHash = new CacheHash(); 2210 2211 mRWBufPos = 2212 std::min(mRWBufSize, static_cast<uint32_t>(mIndexHandle->FileSize())); 2213 2214 rv = CacheFileIOManager::Read(mIndexHandle, 0, mRWBuf, mRWBufPos, this); 2215 if (NS_FAILED(rv)) { 2216 LOG( 2217 ("CacheIndex::StartReadingIndex() - CacheFileIOManager::Read() failed " 2218 "synchronously [rv=0x%08" PRIx32 "]", 2219 static_cast<uint32_t>(rv))); 2220 FinishRead(false, aProofOfLock); 2221 } else { 2222 mRWPending = true; 2223 } 2224 } 2225 2226 void CacheIndex::ParseRecords(const StaticMutexAutoLock& aProofOfLock) { 2227 sLock.AssertCurrentThreadOwns(); 2228 LOG(("CacheIndex::ParseRecords()")); 2229 2230 nsresult rv; 2231 2232 MOZ_ASSERT(!mRWPending); 2233 2234 uint32_t entryCnt = (mIndexHandle->FileSize() - sizeof(CacheIndexHeader) - 2235 sizeof(CacheHash::Hash32_t)) / 2236 sizeof(CacheIndexRecord); 2237 uint32_t pos = 0; 2238 2239 if (!mSkipEntries) { 2240 if (NetworkEndian::readUint32(mRWBuf + pos) != kIndexVersion) { 2241 FinishRead(false, aProofOfLock); 2242 return; 2243 } 2244 pos += sizeof(uint32_t); 2245 2246 mIndexTimeStamp = NetworkEndian::readUint32(mRWBuf + pos); 2247 pos += sizeof(uint32_t); 2248 2249 if (NetworkEndian::readUint32(mRWBuf + pos)) { 2250 if (mJournalHandle) { 2251 CacheFileIOManager::DoomFile(mJournalHandle, nullptr); 2252 mJournalHandle = nullptr; 2253 } 2254 } else { 2255 uint32_t* isDirty = 2256 reinterpret_cast<uint32_t*>(moz_xmalloc(sizeof(uint32_t))); 2257 NetworkEndian::writeUint32(isDirty, 1); 2258 2259 // Mark index dirty. The buffer will be freed by CacheFileIOManager. 2260 CacheFileIOManager::WriteWithoutCallback( 2261 mIndexHandle, 2 * sizeof(uint32_t), reinterpret_cast<char*>(isDirty), 2262 sizeof(uint32_t), true, false); 2263 } 2264 pos += sizeof(uint32_t); 2265 2266 uint64_t dataWritten = NetworkEndian::readUint32(mRWBuf + pos); 2267 pos += sizeof(uint32_t); 2268 dataWritten <<= 10; 2269 mTotalBytesWritten += dataWritten; 2270 } 2271 2272 uint32_t hashOffset = pos; 2273 2274 while (pos + sizeof(CacheIndexRecord) <= mRWBufPos && 2275 mSkipEntries != entryCnt) { 2276 CacheIndexRecord* rec = reinterpret_cast<CacheIndexRecord*>(mRWBuf + pos); 2277 CacheIndexEntry tmpEntry(&rec->mHash); 2278 tmpEntry.ReadFromBuf(mRWBuf + pos); 2279 2280 if (tmpEntry.IsDirty() || !tmpEntry.IsInitialized() || 2281 tmpEntry.IsFileEmpty() || tmpEntry.IsFresh() || tmpEntry.IsRemoved()) { 2282 LOG( 2283 ("CacheIndex::ParseRecords() - Invalid entry found in index, removing" 2284 " whole index [dirty=%d, initialized=%d, fileEmpty=%d, fresh=%d, " 2285 "removed=%d]", 2286 tmpEntry.IsDirty(), tmpEntry.IsInitialized(), tmpEntry.IsFileEmpty(), 2287 tmpEntry.IsFresh(), tmpEntry.IsRemoved())); 2288 FinishRead(false, aProofOfLock); 2289 return; 2290 } 2291 2292 CacheIndexEntryAutoManage emng(tmpEntry.Hash(), this, aProofOfLock); 2293 2294 CacheIndexEntry* entry = mIndex.PutEntry(*tmpEntry.Hash()); 2295 *entry = tmpEntry; 2296 2297 pos += sizeof(CacheIndexRecord); 2298 mSkipEntries++; 2299 } 2300 2301 mRWHash->Update(mRWBuf + hashOffset, pos - hashOffset); 2302 2303 if (pos != mRWBufPos) { 2304 memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos); 2305 } 2306 2307 mRWBufPos -= pos; 2308 pos = 0; 2309 2310 int64_t fileOffset = sizeof(CacheIndexHeader) + 2311 mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos; 2312 2313 MOZ_ASSERT(fileOffset <= mIndexHandle->FileSize()); 2314 if (fileOffset == mIndexHandle->FileSize()) { 2315 uint32_t expectedHash = NetworkEndian::readUint32(mRWBuf); 2316 if (mRWHash->GetHash() != expectedHash) { 2317 LOG(("CacheIndex::ParseRecords() - Hash mismatch, [is %x, should be %x]", 2318 mRWHash->GetHash(), expectedHash)); 2319 FinishRead(false, aProofOfLock); 2320 return; 2321 } 2322 2323 mIndexOnDiskIsValid = true; 2324 mJournalReadSuccessfully = false; 2325 2326 if (mJournalHandle) { 2327 StartReadingJournal(aProofOfLock); 2328 } else { 2329 FinishRead(false, aProofOfLock); 2330 } 2331 2332 return; 2333 } 2334 2335 pos = mRWBufPos; 2336 uint32_t toRead = 2337 std::min(mRWBufSize - pos, 2338 static_cast<uint32_t>(mIndexHandle->FileSize() - fileOffset)); 2339 mRWBufPos = pos + toRead; 2340 2341 rv = CacheFileIOManager::Read(mIndexHandle, fileOffset, mRWBuf + pos, toRead, 2342 this); 2343 if (NS_FAILED(rv)) { 2344 LOG( 2345 ("CacheIndex::ParseRecords() - CacheFileIOManager::Read() failed " 2346 "synchronously [rv=0x%08" PRIx32 "]", 2347 static_cast<uint32_t>(rv))); 2348 FinishRead(false, aProofOfLock); 2349 return; 2350 } 2351 mRWPending = true; 2352 } 2353 2354 void CacheIndex::StartReadingJournal(const StaticMutexAutoLock& aProofOfLock) { 2355 sLock.AssertCurrentThreadOwns(); 2356 LOG(("CacheIndex::StartReadingJournal()")); 2357 2358 nsresult rv; 2359 2360 MOZ_ASSERT(mJournalHandle); 2361 MOZ_ASSERT(mIndexOnDiskIsValid); 2362 MOZ_ASSERT(mTmpJournal.Count() == 0); 2363 MOZ_ASSERT(mJournalHandle->FileSize() >= 0); 2364 MOZ_ASSERT(!mRWPending); 2365 2366 int64_t entriesSize = 2367 mJournalHandle->FileSize() - sizeof(CacheHash::Hash32_t); 2368 2369 if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) { 2370 LOG(("CacheIndex::StartReadingJournal() - Journal is corrupted")); 2371 FinishRead(false, aProofOfLock); 2372 return; 2373 } 2374 2375 mSkipEntries = 0; 2376 mRWHash = new CacheHash(); 2377 2378 mRWBufPos = 2379 std::min(mRWBufSize, static_cast<uint32_t>(mJournalHandle->FileSize())); 2380 2381 rv = CacheFileIOManager::Read(mJournalHandle, 0, mRWBuf, mRWBufPos, this); 2382 if (NS_FAILED(rv)) { 2383 LOG( 2384 ("CacheIndex::StartReadingJournal() - CacheFileIOManager::Read() failed" 2385 " synchronously [rv=0x%08" PRIx32 "]", 2386 static_cast<uint32_t>(rv))); 2387 FinishRead(false, aProofOfLock); 2388 } else { 2389 mRWPending = true; 2390 } 2391 } 2392 2393 void CacheIndex::ParseJournal(const StaticMutexAutoLock& aProofOfLock) { 2394 sLock.AssertCurrentThreadOwns(); 2395 LOG(("CacheIndex::ParseJournal()")); 2396 2397 nsresult rv; 2398 2399 MOZ_ASSERT(!mRWPending); 2400 2401 uint32_t entryCnt = 2402 (mJournalHandle->FileSize() - sizeof(CacheHash::Hash32_t)) / 2403 sizeof(CacheIndexRecord); 2404 2405 uint32_t pos = 0; 2406 2407 while (pos + sizeof(CacheIndexRecord) <= mRWBufPos && 2408 mSkipEntries != entryCnt) { 2409 CacheIndexEntry tmpEntry(reinterpret_cast<SHA1Sum::Hash*>(mRWBuf + pos)); 2410 tmpEntry.ReadFromBuf(mRWBuf + pos); 2411 2412 CacheIndexEntry* entry = mTmpJournal.PutEntry(*tmpEntry.Hash()); 2413 *entry = tmpEntry; 2414 2415 if (entry->IsDirty() || entry->IsFresh()) { 2416 LOG( 2417 ("CacheIndex::ParseJournal() - Invalid entry found in journal, " 2418 "ignoring whole journal [dirty=%d, fresh=%d]", 2419 entry->IsDirty(), entry->IsFresh())); 2420 FinishRead(false, aProofOfLock); 2421 return; 2422 } 2423 2424 pos += sizeof(CacheIndexRecord); 2425 mSkipEntries++; 2426 } 2427 2428 mRWHash->Update(mRWBuf, pos); 2429 2430 if (pos != mRWBufPos) { 2431 memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos); 2432 } 2433 2434 mRWBufPos -= pos; 2435 pos = 0; 2436 2437 int64_t fileOffset = mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos; 2438 2439 MOZ_ASSERT(fileOffset <= mJournalHandle->FileSize()); 2440 if (fileOffset == mJournalHandle->FileSize()) { 2441 uint32_t expectedHash = NetworkEndian::readUint32(mRWBuf); 2442 if (mRWHash->GetHash() != expectedHash) { 2443 LOG(("CacheIndex::ParseJournal() - Hash mismatch, [is %x, should be %x]", 2444 mRWHash->GetHash(), expectedHash)); 2445 FinishRead(false, aProofOfLock); 2446 return; 2447 } 2448 2449 mJournalReadSuccessfully = true; 2450 FinishRead(true, aProofOfLock); 2451 return; 2452 } 2453 2454 pos = mRWBufPos; 2455 uint32_t toRead = 2456 std::min(mRWBufSize - pos, 2457 static_cast<uint32_t>(mJournalHandle->FileSize() - fileOffset)); 2458 mRWBufPos = pos + toRead; 2459 2460 rv = CacheFileIOManager::Read(mJournalHandle, fileOffset, mRWBuf + pos, 2461 toRead, this); 2462 if (NS_FAILED(rv)) { 2463 LOG( 2464 ("CacheIndex::ParseJournal() - CacheFileIOManager::Read() failed " 2465 "synchronously [rv=0x%08" PRIx32 "]", 2466 static_cast<uint32_t>(rv))); 2467 FinishRead(false, aProofOfLock); 2468 return; 2469 } 2470 mRWPending = true; 2471 } 2472 2473 void CacheIndex::MergeJournal(const StaticMutexAutoLock& aProofOfLock) { 2474 sLock.AssertCurrentThreadOwns(); 2475 LOG(("CacheIndex::MergeJournal()")); 2476 2477 for (auto iter = mTmpJournal.Iter(); !iter.Done(); iter.Next()) { 2478 CacheIndexEntry* entry = iter.Get(); 2479 2480 LOG(("CacheIndex::MergeJournal() [hash=%08x%08x%08x%08x%08x]", 2481 LOGSHA1(entry->Hash()))); 2482 2483 CacheIndexEntry* entry2 = mIndex.GetEntry(*entry->Hash()); 2484 { 2485 CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock); 2486 if (entry->IsRemoved()) { 2487 if (entry2) { 2488 entry2->MarkRemoved(); 2489 entry2->MarkDirty(); 2490 } 2491 } else { 2492 if (!entry2) { 2493 entry2 = mIndex.PutEntry(*entry->Hash()); 2494 } 2495 2496 *entry2 = *entry; 2497 entry2->MarkDirty(); 2498 } 2499 } 2500 iter.Remove(); 2501 } 2502 2503 MOZ_ASSERT(mTmpJournal.Count() == 0); 2504 } 2505 2506 void CacheIndex::EnsureNoFreshEntry() { 2507 #ifdef DEBUG_STATS 2508 CacheIndexStats debugStats; 2509 debugStats.DisableLogging(); 2510 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) { 2511 debugStats.BeforeChange(nullptr); 2512 debugStats.AfterChange(iter.Get()); 2513 } 2514 MOZ_ASSERT(debugStats.Fresh() == 0); 2515 #endif 2516 } 2517 2518 void CacheIndex::EnsureCorrectStats() { 2519 #ifdef DEBUG_STATS 2520 MOZ_ASSERT(mPendingUpdates.Count() == 0); 2521 CacheIndexStats debugStats; 2522 debugStats.DisableLogging(); 2523 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) { 2524 debugStats.BeforeChange(nullptr); 2525 debugStats.AfterChange(iter.Get()); 2526 } 2527 MOZ_ASSERT(debugStats == mIndexStats); 2528 #endif 2529 } 2530 2531 void CacheIndex::FinishRead(bool aSucceeded, 2532 const StaticMutexAutoLock& aProofOfLock) { 2533 sLock.AssertCurrentThreadOwns(); 2534 LOG(("CacheIndex::FinishRead() [succeeded=%d]", aSucceeded)); 2535 2536 MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == READING); 2537 2538 MOZ_ASSERT( 2539 // -> rebuild 2540 (!aSucceeded && !mIndexOnDiskIsValid && !mJournalReadSuccessfully) || 2541 // -> update 2542 (!aSucceeded && mIndexOnDiskIsValid && !mJournalReadSuccessfully) || 2543 // -> ready 2544 (aSucceeded && mIndexOnDiskIsValid && mJournalReadSuccessfully)); 2545 2546 // If there is read operation pending we must be cancelling reading of the 2547 // index when shutting down or removing the whole index. 2548 MOZ_ASSERT(!mRWPending || (!aSucceeded && (mShuttingDown || mRemovingAll))); 2549 2550 if (mState == SHUTDOWN) { 2551 RemoveFile(nsLiteralCString(TEMP_INDEX_NAME)); 2552 RemoveFile(nsLiteralCString(JOURNAL_NAME)); 2553 } else { 2554 if (mIndexHandle && !mIndexOnDiskIsValid) { 2555 CacheFileIOManager::DoomFile(mIndexHandle, nullptr); 2556 } 2557 2558 if (mJournalHandle) { 2559 CacheFileIOManager::DoomFile(mJournalHandle, nullptr); 2560 } 2561 } 2562 2563 if (mIndexFileOpener) { 2564 mIndexFileOpener->Cancel(); 2565 mIndexFileOpener = nullptr; 2566 } 2567 if (mJournalFileOpener) { 2568 mJournalFileOpener->Cancel(); 2569 mJournalFileOpener = nullptr; 2570 } 2571 if (mTmpFileOpener) { 2572 mTmpFileOpener->Cancel(); 2573 mTmpFileOpener = nullptr; 2574 } 2575 2576 mIndexHandle = nullptr; 2577 mJournalHandle = nullptr; 2578 mRWHash = nullptr; 2579 ReleaseBuffer(); 2580 2581 if (mState == SHUTDOWN) { 2582 return; 2583 } 2584 2585 if (!mIndexOnDiskIsValid) { 2586 MOZ_ASSERT(mTmpJournal.Count() == 0); 2587 EnsureNoFreshEntry(); 2588 ProcessPendingOperations(aProofOfLock); 2589 // Remove all entries that we haven't seen during this session 2590 RemoveNonFreshEntries(aProofOfLock); 2591 StartUpdatingIndex(true, aProofOfLock); 2592 return; 2593 } 2594 2595 if (!mJournalReadSuccessfully) { 2596 mTmpJournal.Clear(); 2597 EnsureNoFreshEntry(); 2598 ProcessPendingOperations(aProofOfLock); 2599 StartUpdatingIndex(false, aProofOfLock); 2600 return; 2601 } 2602 2603 MergeJournal(aProofOfLock); 2604 EnsureNoFreshEntry(); 2605 ProcessPendingOperations(aProofOfLock); 2606 mIndexStats.Log(); 2607 2608 ChangeState(READY, aProofOfLock); 2609 mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately 2610 } 2611 2612 // static 2613 void CacheIndex::DelayedUpdate(nsITimer* aTimer, void* aClosure) { 2614 LOG(("CacheIndex::DelayedUpdate()")); 2615 2616 StaticMutexAutoLock lock(sLock); 2617 RefPtr<CacheIndex> index = gInstance; 2618 2619 if (!index) { 2620 return; 2621 } 2622 2623 index->DelayedUpdateLocked(lock); 2624 } 2625 2626 // static 2627 void CacheIndex::DelayedUpdateLocked(const StaticMutexAutoLock& aProofOfLock) { 2628 sLock.AssertCurrentThreadOwns(); 2629 LOG(("CacheIndex::DelayedUpdateLocked()")); 2630 2631 nsresult rv; 2632 2633 mUpdateTimer = nullptr; 2634 2635 if (!IsIndexUsable()) { 2636 return; 2637 } 2638 2639 if (mState == READY && mShuttingDown) { 2640 return; 2641 } 2642 2643 // mUpdateEventPending must be false here since StartUpdatingIndex() won't 2644 // schedule timer if it is true. 2645 MOZ_ASSERT(!mUpdateEventPending); 2646 if (mState != BUILDING && mState != UPDATING) { 2647 LOG(("CacheIndex::DelayedUpdateLocked() - Update was canceled")); 2648 return; 2649 } 2650 2651 // We need to redispatch to run with lower priority 2652 RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread(); 2653 MOZ_ASSERT(ioThread); 2654 2655 mUpdateEventPending = true; 2656 rv = ioThread->Dispatch(this, CacheIOThread::INDEX); 2657 if (NS_FAILED(rv)) { 2658 mUpdateEventPending = false; 2659 NS_WARNING("CacheIndex::DelayedUpdateLocked() - Can't dispatch event"); 2660 LOG(("CacheIndex::DelayedUpdate() - Can't dispatch event")); 2661 FinishUpdate(false, aProofOfLock); 2662 } 2663 } 2664 2665 nsresult CacheIndex::ScheduleUpdateTimer(uint32_t aDelay) { 2666 LOG(("CacheIndex::ScheduleUpdateTimer() [delay=%u]", aDelay)); 2667 2668 MOZ_ASSERT(!mUpdateTimer); 2669 2670 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget(); 2671 MOZ_ASSERT(ioTarget); 2672 2673 return NS_NewTimerWithFuncCallback( 2674 getter_AddRefs(mUpdateTimer), CacheIndex::DelayedUpdate, nullptr, aDelay, 2675 nsITimer::TYPE_ONE_SHOT, "net::CacheIndex::ScheduleUpdateTimer"_ns, 2676 ioTarget); 2677 } 2678 2679 nsresult CacheIndex::SetupDirectoryEnumerator() { 2680 MOZ_ASSERT(!NS_IsMainThread()); 2681 MOZ_ASSERT(!mDirEnumerator); 2682 2683 nsresult rv; 2684 nsCOMPtr<nsIFile> file; 2685 2686 rv = mCacheDirectory->Clone(getter_AddRefs(file)); 2687 NS_ENSURE_SUCCESS(rv, rv); 2688 2689 rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR)); 2690 NS_ENSURE_SUCCESS(rv, rv); 2691 2692 bool exists; 2693 rv = file->Exists(&exists); 2694 NS_ENSURE_SUCCESS(rv, rv); 2695 2696 if (!exists) { 2697 NS_WARNING( 2698 "CacheIndex::SetupDirectoryEnumerator() - Entries directory " 2699 "doesn't exist!"); 2700 LOG( 2701 ("CacheIndex::SetupDirectoryEnumerator() - Entries directory doesn't " 2702 "exist!")); 2703 return NS_ERROR_UNEXPECTED; 2704 } 2705 2706 // Do not do IO under the lock. 2707 nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator; 2708 { 2709 StaticMutexAutoUnlock unlock(sLock); 2710 rv = file->GetDirectoryEntries(getter_AddRefs(dirEnumerator)); 2711 } 2712 mDirEnumerator = dirEnumerator.forget(); 2713 NS_ENSURE_SUCCESS(rv, rv); 2714 2715 return NS_OK; 2716 } 2717 2718 nsresult CacheIndex::InitEntryFromDiskData(CacheIndexEntry* aEntry, 2719 CacheFileMetadata* aMetaData, 2720 int64_t aFileSize) { 2721 nsresult rv; 2722 2723 aEntry->InitNew(); 2724 aEntry->MarkDirty(); 2725 aEntry->MarkFresh(); 2726 2727 aEntry->Init(GetOriginAttrsHash(aMetaData->OriginAttributes()), 2728 aMetaData->IsAnonymous(), aMetaData->Pinned()); 2729 2730 aEntry->SetFrecency(aMetaData->GetFrecency()); 2731 2732 const char* altData = aMetaData->GetElement(CacheFileUtils::kAltDataKey); 2733 bool hasAltData = altData != nullptr; 2734 if (hasAltData && NS_FAILED(CacheFileUtils::ParseAlternativeDataInfo( 2735 altData, nullptr, nullptr))) { 2736 return NS_ERROR_FAILURE; 2737 } 2738 aEntry->SetHasAltData(hasAltData); 2739 2740 static auto toUint16 = [](const char* aUint16String) -> uint16_t { 2741 if (!aUint16String) { 2742 return kIndexTimeNotAvailable; 2743 } 2744 nsresult rv; 2745 uint64_t n64 = nsDependentCString(aUint16String).ToInteger64(&rv); 2746 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2747 return n64 <= kIndexTimeOutOfBound ? n64 : kIndexTimeOutOfBound; 2748 }; 2749 2750 aEntry->SetOnStartTime( 2751 toUint16(aMetaData->GetElement("net-response-time-onstart"))); 2752 aEntry->SetOnStopTime( 2753 toUint16(aMetaData->GetElement("net-response-time-onstop"))); 2754 2755 const char* contentTypeStr = aMetaData->GetElement("ctid"); 2756 uint8_t contentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN; 2757 if (contentTypeStr) { 2758 int64_t n64 = nsDependentCString(contentTypeStr).ToInteger64(&rv); 2759 if (NS_FAILED(rv) || n64 < nsICacheEntry::CONTENT_TYPE_UNKNOWN || 2760 n64 >= nsICacheEntry::CONTENT_TYPE_LAST) { 2761 n64 = nsICacheEntry::CONTENT_TYPE_UNKNOWN; 2762 } 2763 contentType = n64; 2764 } 2765 aEntry->SetContentType(contentType); 2766 2767 aEntry->SetFileSize(static_cast<uint32_t>(std::min( 2768 static_cast<int64_t>(PR_UINT32_MAX), (aFileSize + 0x3FF) >> 10))); 2769 return NS_OK; 2770 } 2771 2772 bool CacheIndex::IsUpdatePending() { 2773 sLock.AssertCurrentThreadOwns(); 2774 2775 return mUpdateTimer || mUpdateEventPending; 2776 } 2777 2778 void CacheIndex::BuildIndex(const StaticMutexAutoLock& aProofOfLock) { 2779 sLock.AssertCurrentThreadOwns(); 2780 LOG(("CacheIndex::BuildIndex()")); 2781 2782 MOZ_ASSERT(mPendingUpdates.Count() == 0); 2783 2784 nsresult rv; 2785 2786 if (!mDirEnumerator) { 2787 rv = SetupDirectoryEnumerator(); 2788 if (mState == SHUTDOWN) { 2789 // The index was shut down while we released the lock. FinishUpdate() was 2790 // already called from Shutdown(), so just simply return here. 2791 return; 2792 } 2793 2794 if (NS_FAILED(rv)) { 2795 FinishUpdate(false, aProofOfLock); 2796 return; 2797 } 2798 } 2799 2800 while (true) { 2801 if (CacheIOThread::YieldAndRerun()) { 2802 LOG(( 2803 "CacheIndex::BuildIndex() - Breaking loop for higher level events.")); 2804 mUpdateEventPending = true; 2805 return; 2806 } 2807 2808 bool fileExists = false; 2809 nsCOMPtr<nsIFile> file; 2810 { 2811 // Do not do IO under the lock. 2812 nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator(mDirEnumerator); 2813 sLock.AssertCurrentThreadOwns(); 2814 StaticMutexAutoUnlock unlock(sLock); 2815 rv = dirEnumerator->GetNextFile(getter_AddRefs(file)); 2816 2817 if (file) { 2818 file->Exists(&fileExists); 2819 } 2820 } 2821 if (mState == SHUTDOWN) { 2822 return; 2823 } 2824 if (!file) { 2825 FinishUpdate(NS_SUCCEEDED(rv), aProofOfLock); 2826 return; 2827 } 2828 2829 nsAutoCString leaf; 2830 rv = file->GetNativeLeafName(leaf); 2831 if (NS_FAILED(rv)) { 2832 LOG( 2833 ("CacheIndex::BuildIndex() - GetNativeLeafName() failed! Skipping " 2834 "file.")); 2835 mDontMarkIndexClean = true; 2836 continue; 2837 } 2838 2839 if (!fileExists) { 2840 LOG( 2841 ("CacheIndex::BuildIndex() - File returned by the iterator was " 2842 "removed in the meantime [name=%s]", 2843 leaf.get())); 2844 continue; 2845 } 2846 2847 SHA1Sum::Hash hash; 2848 rv = CacheFileIOManager::StrToHash(leaf, &hash); 2849 if (NS_FAILED(rv)) { 2850 LOG( 2851 ("CacheIndex::BuildIndex() - Filename is not a hash, removing file. " 2852 "[name=%s]", 2853 leaf.get())); 2854 file->Remove(false); 2855 continue; 2856 } 2857 2858 CacheIndexEntry* entry = mIndex.GetEntry(hash); 2859 if (entry && entry->IsRemoved()) { 2860 LOG( 2861 ("CacheIndex::BuildIndex() - Found file that should not exist. " 2862 "[name=%s]", 2863 leaf.get())); 2864 entry->Log(); 2865 MOZ_ASSERT(entry->IsFresh()); 2866 entry = nullptr; 2867 } 2868 2869 #ifdef DEBUG 2870 RefPtr<CacheFileHandle> handle; 2871 CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, 2872 getter_AddRefs(handle)); 2873 #endif 2874 2875 if (entry) { 2876 // the entry is up to date 2877 LOG( 2878 ("CacheIndex::BuildIndex() - Skipping file because the entry is up to" 2879 " date. [name=%s]", 2880 leaf.get())); 2881 entry->Log(); 2882 MOZ_ASSERT(entry->IsFresh()); // The entry must be from this session 2883 // there must be an active CacheFile if the entry is not initialized 2884 MOZ_ASSERT(entry->IsInitialized() || handle); 2885 continue; 2886 } 2887 2888 MOZ_ASSERT(!handle); 2889 2890 RefPtr<CacheFileMetadata> meta = new CacheFileMetadata(); 2891 int64_t size = 0; 2892 2893 { 2894 // Do not do IO under the lock. 2895 StaticMutexAutoUnlock unlock(sLock); 2896 rv = meta->SyncReadMetadata(file); 2897 2898 if (NS_SUCCEEDED(rv)) { 2899 rv = file->GetFileSize(&size); 2900 if (NS_FAILED(rv)) { 2901 LOG( 2902 ("CacheIndex::BuildIndex() - Cannot get filesize of file that was" 2903 " successfully parsed. [name=%s]", 2904 leaf.get())); 2905 } 2906 } 2907 } 2908 if (mState == SHUTDOWN) { 2909 return; 2910 } 2911 2912 // Nobody could add the entry while the lock was released since we modify 2913 // the index only on IO thread and this loop is executed on IO thread too. 2914 entry = mIndex.GetEntry(hash); 2915 MOZ_ASSERT(!entry || entry->IsRemoved()); 2916 2917 if (NS_FAILED(rv)) { 2918 LOG( 2919 ("CacheIndex::BuildIndex() - CacheFileMetadata::SyncReadMetadata() " 2920 "failed, removing file. [name=%s]", 2921 leaf.get())); 2922 file->Remove(false); 2923 } else { 2924 CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock); 2925 entry = mIndex.PutEntry(hash); 2926 if (NS_FAILED(InitEntryFromDiskData(entry, meta, size))) { 2927 LOG( 2928 ("CacheIndex::BuildIndex() - CacheFile::InitEntryFromDiskData() " 2929 "failed, removing file. [name=%s]", 2930 leaf.get())); 2931 file->Remove(false); 2932 entry->MarkRemoved(); 2933 } else { 2934 LOG(("CacheIndex::BuildIndex() - Added entry to index. [name=%s]", 2935 leaf.get())); 2936 entry->Log(); 2937 } 2938 } 2939 } 2940 2941 MOZ_ASSERT_UNREACHABLE("We should never get here"); 2942 } 2943 2944 bool CacheIndex::StartUpdatingIndexIfNeeded( 2945 const StaticMutexAutoLock& aProofOfLock, bool aSwitchingToReadyState) { 2946 sLock.AssertCurrentThreadOwns(); 2947 // Start updating process when we are in or we are switching to READY state 2948 // and index needs update, but not during shutdown or when removing all 2949 // entries. 2950 if ((mState == READY || aSwitchingToReadyState) && mIndexNeedsUpdate && 2951 !mShuttingDown && !mRemovingAll) { 2952 LOG(("CacheIndex::StartUpdatingIndexIfNeeded() - starting update process")); 2953 mIndexNeedsUpdate = false; 2954 StartUpdatingIndex(false, aProofOfLock); 2955 return true; 2956 } 2957 2958 return false; 2959 } 2960 2961 void CacheIndex::StartUpdatingIndex(bool aRebuild, 2962 const StaticMutexAutoLock& aProofOfLock) { 2963 sLock.AssertCurrentThreadOwns(); 2964 LOG(("CacheIndex::StartUpdatingIndex() [rebuild=%d]", aRebuild)); 2965 2966 nsresult rv; 2967 2968 mIndexStats.Log(); 2969 2970 ChangeState(aRebuild ? BUILDING : UPDATING, aProofOfLock); 2971 mDontMarkIndexClean = false; 2972 2973 if (mShuttingDown || mRemovingAll) { 2974 FinishUpdate(false, aProofOfLock); 2975 return; 2976 } 2977 2978 if (IsUpdatePending()) { 2979 LOG(("CacheIndex::StartUpdatingIndex() - Update is already pending")); 2980 return; 2981 } 2982 2983 uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds(); 2984 if (elapsed < kUpdateIndexStartDelay) { 2985 LOG( 2986 ("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, " 2987 "scheduling timer to fire in %u ms.", 2988 elapsed, kUpdateIndexStartDelay - elapsed)); 2989 rv = ScheduleUpdateTimer(kUpdateIndexStartDelay - elapsed); 2990 if (NS_SUCCEEDED(rv)) { 2991 return; 2992 } 2993 2994 LOG( 2995 ("CacheIndex::StartUpdatingIndex() - ScheduleUpdateTimer() failed. " 2996 "Starting update immediately.")); 2997 } else { 2998 LOG( 2999 ("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, " 3000 "starting update now.", 3001 elapsed)); 3002 } 3003 3004 RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread(); 3005 MOZ_ASSERT(ioThread); 3006 3007 // We need to dispatch an event even if we are on IO thread since we need to 3008 // update the index with the correct priority. 3009 mUpdateEventPending = true; 3010 rv = ioThread->Dispatch(this, CacheIOThread::INDEX); 3011 if (NS_FAILED(rv)) { 3012 mUpdateEventPending = false; 3013 NS_WARNING("CacheIndex::StartUpdatingIndex() - Can't dispatch event"); 3014 LOG(("CacheIndex::StartUpdatingIndex() - Can't dispatch event")); 3015 FinishUpdate(false, aProofOfLock); 3016 } 3017 } 3018 3019 void CacheIndex::UpdateIndex(const StaticMutexAutoLock& aProofOfLock) { 3020 sLock.AssertCurrentThreadOwns(); 3021 LOG(("CacheIndex::UpdateIndex()")); 3022 3023 MOZ_ASSERT(mPendingUpdates.Count() == 0); 3024 sLock.AssertCurrentThreadOwns(); 3025 3026 nsresult rv; 3027 3028 if (!mDirEnumerator) { 3029 rv = SetupDirectoryEnumerator(); 3030 if (mState == SHUTDOWN) { 3031 // The index was shut down while we released the lock. FinishUpdate() was 3032 // already called from Shutdown(), so just simply return here. 3033 return; 3034 } 3035 3036 if (NS_FAILED(rv)) { 3037 FinishUpdate(false, aProofOfLock); 3038 return; 3039 } 3040 } 3041 3042 while (true) { 3043 if (CacheIOThread::YieldAndRerun()) { 3044 LOG( 3045 ("CacheIndex::UpdateIndex() - Breaking loop for higher level " 3046 "events.")); 3047 mUpdateEventPending = true; 3048 return; 3049 } 3050 3051 bool fileExists = false; 3052 nsCOMPtr<nsIFile> file; 3053 { 3054 // Do not do IO under the lock. 3055 nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator(mDirEnumerator); 3056 StaticMutexAutoUnlock unlock(sLock); 3057 rv = dirEnumerator->GetNextFile(getter_AddRefs(file)); 3058 3059 if (file) { 3060 file->Exists(&fileExists); 3061 } 3062 } 3063 if (mState == SHUTDOWN) { 3064 return; 3065 } 3066 if (!file) { 3067 FinishUpdate(NS_SUCCEEDED(rv), aProofOfLock); 3068 return; 3069 } 3070 3071 nsAutoCString leaf; 3072 rv = file->GetNativeLeafName(leaf); 3073 if (NS_FAILED(rv)) { 3074 LOG( 3075 ("CacheIndex::UpdateIndex() - GetNativeLeafName() failed! Skipping " 3076 "file.")); 3077 mDontMarkIndexClean = true; 3078 continue; 3079 } 3080 3081 if (!fileExists) { 3082 LOG( 3083 ("CacheIndex::UpdateIndex() - File returned by the iterator was " 3084 "removed in the meantime [name=%s]", 3085 leaf.get())); 3086 continue; 3087 } 3088 3089 SHA1Sum::Hash hash; 3090 rv = CacheFileIOManager::StrToHash(leaf, &hash); 3091 if (NS_FAILED(rv)) { 3092 LOG( 3093 ("CacheIndex::UpdateIndex() - Filename is not a hash, removing file. " 3094 "[name=%s]", 3095 leaf.get())); 3096 file->Remove(false); 3097 continue; 3098 } 3099 3100 CacheIndexEntry* entry = mIndex.GetEntry(hash); 3101 if (entry && entry->IsRemoved()) { 3102 if (entry->IsFresh()) { 3103 LOG( 3104 ("CacheIndex::UpdateIndex() - Found file that should not exist. " 3105 "[name=%s]", 3106 leaf.get())); 3107 entry->Log(); 3108 } 3109 entry = nullptr; 3110 } 3111 3112 #ifdef DEBUG 3113 RefPtr<CacheFileHandle> handle; 3114 CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, 3115 getter_AddRefs(handle)); 3116 #endif 3117 3118 if (entry && entry->IsFresh()) { 3119 // the entry is up to date 3120 LOG( 3121 ("CacheIndex::UpdateIndex() - Skipping file because the entry is up " 3122 " to date. [name=%s]", 3123 leaf.get())); 3124 entry->Log(); 3125 // there must be an active CacheFile if the entry is not initialized 3126 MOZ_ASSERT(entry->IsInitialized() || handle); 3127 continue; 3128 } 3129 3130 MOZ_ASSERT(!handle); 3131 3132 if (entry) { 3133 PRTime lastModifiedTime; 3134 { 3135 // Do not do IO under the lock. 3136 StaticMutexAutoUnlock unlock(sLock); 3137 rv = file->GetLastModifiedTime(&lastModifiedTime); 3138 } 3139 if (mState == SHUTDOWN) { 3140 return; 3141 } 3142 if (NS_FAILED(rv)) { 3143 LOG( 3144 ("CacheIndex::UpdateIndex() - Cannot get lastModifiedTime. " 3145 "[name=%s]", 3146 leaf.get())); 3147 // Assume the file is newer than index 3148 } else { 3149 if (mIndexTimeStamp > (lastModifiedTime / PR_MSEC_PER_SEC)) { 3150 LOG( 3151 ("CacheIndex::UpdateIndex() - Skipping file because of last " 3152 "modified time. [name=%s, indexTimeStamp=%" PRIu32 ", " 3153 "lastModifiedTime=%" PRId64 "]", 3154 leaf.get(), mIndexTimeStamp, 3155 lastModifiedTime / PR_MSEC_PER_SEC)); 3156 3157 CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock); 3158 entry->MarkFresh(); 3159 continue; 3160 } 3161 } 3162 } 3163 3164 RefPtr<CacheFileMetadata> meta = new CacheFileMetadata(); 3165 int64_t size = 0; 3166 3167 { 3168 // Do not do IO under the lock. 3169 StaticMutexAutoUnlock unlock(sLock); 3170 rv = meta->SyncReadMetadata(file); 3171 3172 if (NS_SUCCEEDED(rv)) { 3173 rv = file->GetFileSize(&size); 3174 if (NS_FAILED(rv)) { 3175 LOG( 3176 ("CacheIndex::UpdateIndex() - Cannot get filesize of file that " 3177 "was successfully parsed. [name=%s]", 3178 leaf.get())); 3179 } 3180 } 3181 } 3182 if (mState == SHUTDOWN) { 3183 return; 3184 } 3185 3186 // Nobody could add the entry while the lock was released since we modify 3187 // the index only on IO thread and this loop is executed on IO thread too. 3188 entry = mIndex.GetEntry(hash); 3189 MOZ_ASSERT(!entry || !entry->IsFresh()); 3190 3191 CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock); 3192 3193 if (NS_FAILED(rv)) { 3194 LOG( 3195 ("CacheIndex::UpdateIndex() - CacheFileMetadata::SyncReadMetadata() " 3196 "failed, removing file. [name=%s]", 3197 leaf.get())); 3198 } else { 3199 entry = mIndex.PutEntry(hash); 3200 rv = InitEntryFromDiskData(entry, meta, size); 3201 if (NS_FAILED(rv)) { 3202 LOG( 3203 ("CacheIndex::UpdateIndex() - CacheIndex::InitEntryFromDiskData " 3204 "failed, removing file. [name=%s]", 3205 leaf.get())); 3206 } 3207 } 3208 3209 if (NS_FAILED(rv)) { 3210 file->Remove(false); 3211 if (entry) { 3212 entry->MarkRemoved(); 3213 entry->MarkFresh(); 3214 entry->MarkDirty(); 3215 } 3216 } else { 3217 LOG( 3218 ("CacheIndex::UpdateIndex() - Added/updated entry to/in index. " 3219 "[name=%s]", 3220 leaf.get())); 3221 entry->Log(); 3222 } 3223 } 3224 3225 MOZ_ASSERT_UNREACHABLE("We should never get here"); 3226 } 3227 3228 void CacheIndex::FinishUpdate(bool aSucceeded, 3229 const StaticMutexAutoLock& aProofOfLock) { 3230 LOG(("CacheIndex::FinishUpdate() [succeeded=%d]", aSucceeded)); 3231 3232 MOZ_ASSERT(mState == UPDATING || mState == BUILDING || 3233 (!aSucceeded && mState == SHUTDOWN)); 3234 3235 if (mDirEnumerator) { 3236 if (NS_IsMainThread()) { 3237 LOG( 3238 ("CacheIndex::FinishUpdate() - posting of PreShutdownInternal failed?" 3239 " Cannot safely release mDirEnumerator, leaking it!")); 3240 NS_WARNING(("CacheIndex::FinishUpdate() - Leaking mDirEnumerator!")); 3241 // This can happen only in case dispatching event to IO thread failed in 3242 // CacheIndex::PreShutdown(). 3243 mDirEnumerator.forget() 3244 .leak(); // Leak it since dir enumerator is not threadsafe 3245 } else { 3246 mDirEnumerator->Close(); 3247 mDirEnumerator = nullptr; 3248 } 3249 } 3250 3251 if (!aSucceeded) { 3252 mDontMarkIndexClean = true; 3253 } 3254 3255 if (mState == SHUTDOWN) { 3256 return; 3257 } 3258 3259 if (mState == UPDATING && aSucceeded) { 3260 // If we've iterated over all entries successfully then all entries that 3261 // really exist on the disk are now marked as fresh. All non-fresh entries 3262 // don't exist anymore and must be removed from the index. 3263 RemoveNonFreshEntries(aProofOfLock); 3264 } 3265 3266 // Make sure we won't start update. If the build or update failed, there is no 3267 // reason to believe that it will succeed next time. 3268 mIndexNeedsUpdate = false; 3269 3270 ChangeState(READY, aProofOfLock); 3271 mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately 3272 } 3273 3274 void CacheIndex::RemoveNonFreshEntries( 3275 const StaticMutexAutoLock& aProofOfLock) { 3276 sLock.AssertCurrentThreadOwns(); 3277 for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) { 3278 CacheIndexEntry* entry = iter.Get(); 3279 if (entry->IsFresh()) { 3280 continue; 3281 } 3282 3283 LOG( 3284 ("CacheIndex::RemoveNonFreshEntries() - Removing entry. " 3285 "[hash=%08x%08x%08x%08x%08x]", 3286 LOGSHA1(entry->Hash()))); 3287 3288 { 3289 CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock); 3290 emng.DoNotSearchInIndex(); 3291 } 3292 3293 iter.Remove(); 3294 } 3295 } 3296 3297 // static 3298 char const* CacheIndex::StateString(EState aState) { 3299 switch (aState) { 3300 case INITIAL: 3301 return "INITIAL"; 3302 case READING: 3303 return "READING"; 3304 case WRITING: 3305 return "WRITING"; 3306 case BUILDING: 3307 return "BUILDING"; 3308 case UPDATING: 3309 return "UPDATING"; 3310 case READY: 3311 return "READY"; 3312 case SHUTDOWN: 3313 return "SHUTDOWN"; 3314 } 3315 3316 MOZ_ASSERT(false, "Unexpected state!"); 3317 return "?"; 3318 } 3319 3320 void CacheIndex::ChangeState(EState aNewState, 3321 const StaticMutexAutoLock& aProofOfLock) { 3322 sLock.AssertCurrentThreadOwns(); 3323 LOG(("CacheIndex::ChangeState() changing state %s -> %s", StateString(mState), 3324 StateString(aNewState))); 3325 3326 // All pending updates should be processed before changing state 3327 MOZ_ASSERT(mPendingUpdates.Count() == 0); 3328 3329 // PreShutdownInternal() should change the state to READY from every state. It 3330 // may go through different states, but once we are in READY state the only 3331 // possible transition is to SHUTDOWN state. 3332 MOZ_ASSERT(!mShuttingDown || mState != READY || aNewState == SHUTDOWN); 3333 3334 // Start updating process when switching to READY state if needed 3335 if (aNewState == READY && StartUpdatingIndexIfNeeded(aProofOfLock, true)) { 3336 return; 3337 } 3338 3339 // Try to evict entries over limit everytime we're leaving state READING, 3340 // BUILDING or UPDATING, but not during shutdown or when removing all 3341 // entries. 3342 if (!mShuttingDown && !mRemovingAll && aNewState != SHUTDOWN && 3343 (mState == READING || mState == BUILDING || mState == UPDATING)) { 3344 CacheFileIOManager::EvictIfOverLimit(); 3345 } 3346 3347 mState = aNewState; 3348 3349 if (mState != SHUTDOWN) { 3350 CacheFileIOManager::CacheIndexStateChanged(); 3351 } 3352 3353 NotifyAsyncGetDiskConsumptionCallbacks(); 3354 } 3355 3356 void CacheIndex::NotifyAsyncGetDiskConsumptionCallbacks() { 3357 if ((mState == READY || mState == WRITING) && 3358 !mAsyncGetDiskConsumptionBlocked && mDiskConsumptionObservers.Length()) { 3359 for (uint32_t i = 0; i < mDiskConsumptionObservers.Length(); ++i) { 3360 DiskConsumptionObserver* o = mDiskConsumptionObservers[i]; 3361 // Safe to call under the lock. We always post to the main thread. 3362 o->OnDiskConsumption(mIndexStats.Size() << 10); 3363 } 3364 3365 mDiskConsumptionObservers.Clear(); 3366 } 3367 } 3368 3369 void CacheIndex::AllocBuffer() { 3370 switch (mState) { 3371 case WRITING: 3372 mRWBufSize = sizeof(CacheIndexHeader) + sizeof(CacheHash::Hash32_t) + 3373 mProcessEntries * sizeof(CacheIndexRecord); 3374 if (mRWBufSize > kMaxBufSize) { 3375 mRWBufSize = kMaxBufSize; 3376 } 3377 break; 3378 case READING: 3379 mRWBufSize = kMaxBufSize; 3380 break; 3381 default: 3382 MOZ_ASSERT(false, "Unexpected state!"); 3383 } 3384 3385 mRWBuf = static_cast<char*>(moz_xmalloc(mRWBufSize)); 3386 } 3387 3388 void CacheIndex::ReleaseBuffer() { 3389 sLock.AssertCurrentThreadOwns(); 3390 3391 if (!mRWBuf || mRWPending) { 3392 return; 3393 } 3394 3395 LOG(("CacheIndex::ReleaseBuffer() releasing buffer")); 3396 3397 free(mRWBuf); 3398 mRWBuf = nullptr; 3399 mRWBufSize = 0; 3400 mRWBufPos = 0; 3401 } 3402 3403 void CacheIndex::FrecencyStorage::AppendRecord( 3404 CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) { 3405 sLock.AssertCurrentThreadOwns(); 3406 LOG( 3407 ("CacheIndex::FrecencyStorage::AppendRecord() [record=%p, " 3408 "hash=%08x%08x%08x" 3409 "%08x%08x]", 3410 aRecord, LOGSHA1(aRecord->Get()->mHash))); 3411 MOZ_DIAGNOSTIC_ASSERT(!mRecs.Contains(aRecord)); 3412 mRecs.PutEntry(aRecord); 3413 } 3414 3415 void CacheIndex::FrecencyStorage::RemoveRecord( 3416 CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) { 3417 sLock.AssertCurrentThreadOwns(); 3418 LOG(("CacheIndex::FrecencyStorage::RemoveRecord() [record=%p]", aRecord)); 3419 MOZ_RELEASE_ASSERT(mRecs.Contains(aRecord)); 3420 mRecs.RemoveEntry(aRecord); 3421 } 3422 3423 void CacheIndex::FrecencyStorage::ReplaceRecord( 3424 CacheIndexRecordWrapper* aOldRecord, CacheIndexRecordWrapper* aNewRecord, 3425 const StaticMutexAutoLock& aProofOfLock) { 3426 sLock.AssertCurrentThreadOwns(); 3427 LOG( 3428 ("CacheIndex::FrecencyStorage::ReplaceRecord() [oldRecord=%p, " 3429 "newRecord=%p]", 3430 aOldRecord, aNewRecord)); 3431 3432 MOZ_RELEASE_ASSERT(mRecs.Contains(aOldRecord), 3433 "Tried to replace a record that doesn't exist"); 3434 mRecs.RemoveEntry(aOldRecord); 3435 mRecs.PutEntry(aNewRecord); 3436 } 3437 3438 // static 3439 CacheIndex::EvictionSortedSnapshot CacheIndex::GetSortedSnapshotForEviction() { 3440 StaticMutexAutoLock lock(sLock); 3441 RefPtr<CacheIndex> index = gInstance; 3442 return index->mFrecencyStorage.GetSortedSnapshotForEviction(); 3443 } 3444 3445 CacheIndex::EvictionSortedSnapshot 3446 CacheIndex::FrecencyStorage::GetSortedSnapshotForEviction() { 3447 CacheIndex::EvictionSortedSnapshot snapshot; 3448 snapshot.SetCapacity(mRecs.Count()); 3449 for (const auto& item : mRecs) { 3450 snapshot.AppendElement(item.GetKey()); 3451 } 3452 snapshot.Sort(FrecencyComparator()); 3453 return snapshot; 3454 } 3455 3456 bool CacheIndex::FrecencyStorage::RecordExistedUnlocked( 3457 CacheIndexRecordWrapper* aRecord) { 3458 return mRecs.Contains(aRecord); 3459 } 3460 3461 void CacheIndex::AddRecordToIterators(CacheIndexRecordWrapper* aRecord, 3462 const StaticMutexAutoLock& aProofOfLock) { 3463 sLock.AssertCurrentThreadOwns(); 3464 for (uint32_t i = 0; i < mIterators.Length(); ++i) { 3465 // Add a new record only when iterator is supposed to be updated. 3466 if (mIterators[i]->ShouldBeNewAdded()) { 3467 mIterators[i]->AddRecord(aRecord, aProofOfLock); 3468 } 3469 } 3470 } 3471 3472 void CacheIndex::RemoveRecordFromIterators( 3473 CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) { 3474 sLock.AssertCurrentThreadOwns(); 3475 for (uint32_t i = 0; i < mIterators.Length(); ++i) { 3476 // Remove the record from iterator always, it makes no sence to return 3477 // non-existing entries. Also the pointer to the record is no longer valid 3478 // once the entry is removed from index. 3479 mIterators[i]->RemoveRecord(aRecord, aProofOfLock); 3480 } 3481 } 3482 3483 void CacheIndex::ReplaceRecordInIterators( 3484 CacheIndexRecordWrapper* aOldRecord, CacheIndexRecordWrapper* aNewRecord, 3485 const StaticMutexAutoLock& aProofOfLock) { 3486 sLock.AssertCurrentThreadOwns(); 3487 for (uint32_t i = 0; i < mIterators.Length(); ++i) { 3488 // We have to replace the record always since the pointer is no longer 3489 // valid after this point. NOTE: Replacing the record doesn't mean that 3490 // a new entry was added, it just means that the data in the entry was 3491 // changed (e.g. a file size) and we had to track this change in 3492 // mPendingUpdates since mIndex was read-only. 3493 mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord, aProofOfLock); 3494 } 3495 } 3496 3497 nsresult CacheIndex::Run() { 3498 LOG(("CacheIndex::Run()")); 3499 3500 StaticMutexAutoLock lock(sLock); 3501 3502 if (!IsIndexUsable()) { 3503 return NS_ERROR_NOT_AVAILABLE; 3504 } 3505 3506 if (mState == READY && mShuttingDown) { 3507 return NS_OK; 3508 } 3509 3510 mUpdateEventPending = false; 3511 3512 switch (mState) { 3513 case BUILDING: 3514 BuildIndex(lock); 3515 break; 3516 case UPDATING: 3517 UpdateIndex(lock); 3518 break; 3519 default: 3520 LOG(("CacheIndex::Run() - Update/Build was canceled")); 3521 } 3522 3523 return NS_OK; 3524 } 3525 3526 void CacheIndex::OnFileOpenedInternal(FileOpenHelper* aOpener, 3527 CacheFileHandle* aHandle, 3528 nsresult aResult, 3529 const StaticMutexAutoLock& aProofOfLock) { 3530 sLock.AssertCurrentThreadOwns(); 3531 LOG( 3532 ("CacheIndex::OnFileOpenedInternal() [opener=%p, handle=%p, " 3533 "result=0x%08" PRIx32 "]", 3534 aOpener, aHandle, static_cast<uint32_t>(aResult))); 3535 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 3536 3537 nsresult rv; 3538 3539 MOZ_RELEASE_ASSERT(IsIndexUsable()); 3540 3541 if (mState == READY && mShuttingDown) { 3542 return; 3543 } 3544 3545 switch (mState) { 3546 case WRITING: 3547 MOZ_ASSERT(aOpener == mIndexFileOpener); 3548 mIndexFileOpener = nullptr; 3549 3550 if (NS_FAILED(aResult)) { 3551 LOG( 3552 ("CacheIndex::OnFileOpenedInternal() - Can't open index file for " 3553 "writing [rv=0x%08" PRIx32 "]", 3554 static_cast<uint32_t>(aResult))); 3555 FinishWrite(false, aProofOfLock); 3556 } else { 3557 mIndexHandle = aHandle; 3558 WriteRecords(aProofOfLock); 3559 } 3560 break; 3561 case READING: 3562 if (aOpener == mIndexFileOpener) { 3563 mIndexFileOpener = nullptr; 3564 3565 if (NS_SUCCEEDED(aResult)) { 3566 if (aHandle->FileSize() == 0) { 3567 FinishRead(false, aProofOfLock); 3568 CacheFileIOManager::DoomFile(aHandle, nullptr); 3569 break; 3570 } 3571 mIndexHandle = aHandle; 3572 } else { 3573 FinishRead(false, aProofOfLock); 3574 break; 3575 } 3576 } else if (aOpener == mJournalFileOpener) { 3577 mJournalFileOpener = nullptr; 3578 mJournalHandle = aHandle; 3579 } else if (aOpener == mTmpFileOpener) { 3580 mTmpFileOpener = nullptr; 3581 mTmpHandle = aHandle; 3582 } else { 3583 MOZ_ASSERT(false, "Unexpected state!"); 3584 } 3585 3586 if (mIndexFileOpener || mJournalFileOpener || mTmpFileOpener) { 3587 // Some opener still didn't finish 3588 break; 3589 } 3590 3591 // We fail and cancel all other openers when we opening index file fails. 3592 MOZ_ASSERT(mIndexHandle); 3593 3594 if (mTmpHandle) { 3595 CacheFileIOManager::DoomFile(mTmpHandle, nullptr); 3596 mTmpHandle = nullptr; 3597 3598 if (mJournalHandle) { // this shouldn't normally happen 3599 LOG( 3600 ("CacheIndex::OnFileOpenedInternal() - Unexpected state, all " 3601 "files [%s, %s, %s] should never exist. Removing whole index.", 3602 INDEX_NAME, JOURNAL_NAME, TEMP_INDEX_NAME)); 3603 FinishRead(false, aProofOfLock); 3604 break; 3605 } 3606 } 3607 3608 if (mJournalHandle) { 3609 // Rename journal to make sure we update index on next start in case 3610 // firefox crashes 3611 rv = CacheFileIOManager::RenameFile( 3612 mJournalHandle, nsLiteralCString(TEMP_INDEX_NAME), this); 3613 if (NS_FAILED(rv)) { 3614 LOG( 3615 ("CacheIndex::OnFileOpenedInternal() - CacheFileIOManager::" 3616 "RenameFile() failed synchronously [rv=0x%08" PRIx32 "]", 3617 static_cast<uint32_t>(rv))); 3618 FinishRead(false, aProofOfLock); 3619 break; 3620 } 3621 } else { 3622 StartReadingIndex(aProofOfLock); 3623 } 3624 3625 break; 3626 default: 3627 MOZ_ASSERT(false, "Unexpected state!"); 3628 } 3629 } 3630 3631 nsresult CacheIndex::OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) { 3632 MOZ_CRASH("CacheIndex::OnFileOpened should not be called!"); 3633 return NS_ERROR_UNEXPECTED; 3634 } 3635 3636 nsresult CacheIndex::OnDataWritten(CacheFileHandle* aHandle, const char* aBuf, 3637 nsresult aResult) { 3638 LOG(("CacheIndex::OnDataWritten() [handle=%p, result=0x%08" PRIx32 "]", 3639 aHandle, static_cast<uint32_t>(aResult))); 3640 3641 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 3642 3643 nsresult rv; 3644 3645 StaticMutexAutoLock lock(sLock); 3646 3647 MOZ_RELEASE_ASSERT(IsIndexUsable()); 3648 MOZ_RELEASE_ASSERT(mRWPending); 3649 mRWPending = false; 3650 3651 if (mState == READY && mShuttingDown) { 3652 return NS_OK; 3653 } 3654 3655 switch (mState) { 3656 case WRITING: 3657 MOZ_ASSERT(mIndexHandle == aHandle); 3658 3659 if (NS_FAILED(aResult)) { 3660 FinishWrite(false, lock); 3661 } else { 3662 if (mSkipEntries == mProcessEntries) { 3663 rv = CacheFileIOManager::RenameFile( 3664 mIndexHandle, nsLiteralCString(INDEX_NAME), this); 3665 if (NS_FAILED(rv)) { 3666 LOG( 3667 ("CacheIndex::OnDataWritten() - CacheFileIOManager::" 3668 "RenameFile() failed synchronously [rv=0x%08" PRIx32 "]", 3669 static_cast<uint32_t>(rv))); 3670 FinishWrite(false, lock); 3671 } 3672 } else { 3673 WriteRecords(lock); 3674 } 3675 } 3676 break; 3677 default: 3678 // Writing was canceled. 3679 LOG( 3680 ("CacheIndex::OnDataWritten() - ignoring notification since the " 3681 "operation was previously canceled [state=%d]", 3682 mState)); 3683 ReleaseBuffer(); 3684 } 3685 3686 return NS_OK; 3687 } 3688 3689 nsresult CacheIndex::OnDataRead(CacheFileHandle* aHandle, char* aBuf, 3690 nsresult aResult) { 3691 LOG(("CacheIndex::OnDataRead() [handle=%p, result=0x%08" PRIx32 "]", aHandle, 3692 static_cast<uint32_t>(aResult))); 3693 3694 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 3695 3696 StaticMutexAutoLock lock(sLock); 3697 3698 MOZ_RELEASE_ASSERT(IsIndexUsable()); 3699 MOZ_RELEASE_ASSERT(mRWPending); 3700 mRWPending = false; 3701 3702 switch (mState) { 3703 case READING: 3704 MOZ_ASSERT(mIndexHandle == aHandle || mJournalHandle == aHandle); 3705 3706 if (NS_FAILED(aResult)) { 3707 FinishRead(false, lock); 3708 } else { 3709 if (!mIndexOnDiskIsValid) { 3710 ParseRecords(lock); 3711 } else { 3712 ParseJournal(lock); 3713 } 3714 } 3715 break; 3716 default: 3717 // Reading was canceled. 3718 LOG( 3719 ("CacheIndex::OnDataRead() - ignoring notification since the " 3720 "operation was previously canceled [state=%d]", 3721 mState)); 3722 ReleaseBuffer(); 3723 } 3724 3725 return NS_OK; 3726 } 3727 3728 nsresult CacheIndex::OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) { 3729 MOZ_CRASH("CacheIndex::OnFileDoomed should not be called!"); 3730 return NS_ERROR_UNEXPECTED; 3731 } 3732 3733 nsresult CacheIndex::OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) { 3734 MOZ_CRASH("CacheIndex::OnEOFSet should not be called!"); 3735 return NS_ERROR_UNEXPECTED; 3736 } 3737 3738 nsresult CacheIndex::OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) { 3739 LOG(("CacheIndex::OnFileRenamed() [handle=%p, result=0x%08" PRIx32 "]", 3740 aHandle, static_cast<uint32_t>(aResult))); 3741 3742 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 3743 3744 StaticMutexAutoLock lock(sLock); 3745 3746 MOZ_RELEASE_ASSERT(IsIndexUsable()); 3747 3748 if (mState == READY && mShuttingDown) { 3749 return NS_OK; 3750 } 3751 3752 switch (mState) { 3753 case WRITING: 3754 // This is a result of renaming the new index written to tmpfile to index 3755 // file. This is the last step when writing the index and the whole 3756 // writing process is successful iff renaming was successful. 3757 3758 if (mIndexHandle != aHandle) { 3759 LOG( 3760 ("CacheIndex::OnFileRenamed() - ignoring notification since it " 3761 "belongs to previously canceled operation [state=%d]", 3762 mState)); 3763 break; 3764 } 3765 3766 FinishWrite(NS_SUCCEEDED(aResult), lock); 3767 break; 3768 case READING: 3769 // This is a result of renaming journal file to tmpfile. It is renamed 3770 // before we start reading index and journal file and it should normally 3771 // succeed. If it fails give up reading of index. 3772 3773 if (mJournalHandle != aHandle) { 3774 LOG( 3775 ("CacheIndex::OnFileRenamed() - ignoring notification since it " 3776 "belongs to previously canceled operation [state=%d]", 3777 mState)); 3778 break; 3779 } 3780 3781 if (NS_FAILED(aResult)) { 3782 FinishRead(false, lock); 3783 } else { 3784 StartReadingIndex(lock); 3785 } 3786 break; 3787 default: 3788 // Reading/writing was canceled. 3789 LOG( 3790 ("CacheIndex::OnFileRenamed() - ignoring notification since the " 3791 "operation was previously canceled [state=%d]", 3792 mState)); 3793 } 3794 3795 return NS_OK; 3796 } 3797 3798 // Memory reporting 3799 3800 size_t CacheIndex::SizeOfExcludingThisInternal( 3801 mozilla::MallocSizeOf mallocSizeOf) const { 3802 sLock.AssertCurrentThreadOwns(); 3803 3804 size_t n = 0; 3805 3806 // mIndexHandle and mJournalHandle are reported via SizeOfHandlesRunnable 3807 // in CacheFileIOManager::SizeOfExcludingThisInternal as part of special 3808 // handles array. 3809 3810 // mCacheDirectory is an nsIFile which we don't have reporting for. 3811 3812 // mUpdateTimer is an nsITimer which we don't have reporting for. 3813 3814 n += mallocSizeOf(mRWBuf); 3815 n += mallocSizeOf(mRWHash); 3816 3817 n += mIndex.SizeOfExcludingThis(mallocSizeOf); 3818 n += mPendingUpdates.SizeOfExcludingThis(mallocSizeOf); 3819 n += mTmpJournal.SizeOfExcludingThis(mallocSizeOf); 3820 3821 // mFrecencyStorage items are reported by mIndex/mPendingUpdates 3822 n += mFrecencyStorage.mRecs.ShallowSizeOfExcludingThis(mallocSizeOf); 3823 n += mDiskConsumptionObservers.ShallowSizeOfExcludingThis(mallocSizeOf); 3824 3825 return n; 3826 } 3827 3828 // static 3829 size_t CacheIndex::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { 3830 StaticMutexAutoLock lock(sLock); 3831 3832 if (!gInstance) return 0; 3833 3834 return gInstance->SizeOfExcludingThisInternal(mallocSizeOf); 3835 } 3836 3837 // static 3838 size_t CacheIndex::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { 3839 StaticMutexAutoLock lock(sLock); 3840 3841 return mallocSizeOf(gInstance) + 3842 (gInstance ? gInstance->SizeOfExcludingThisInternal(mallocSizeOf) : 0); 3843 } 3844 3845 // static 3846 void CacheIndex::UpdateTotalBytesWritten(uint32_t aBytesWritten) { 3847 StaticMutexAutoLock lock(sLock); 3848 3849 RefPtr<CacheIndex> index = gInstance; 3850 if (!index) { 3851 return; 3852 } 3853 3854 index->mTotalBytesWritten += aBytesWritten; 3855 3856 // Do telemetry report if enough data has been written and the index is 3857 // in READY state. The data is available also in WRITING state, but we would 3858 // need to deal with pending updates. 3859 if (index->mTotalBytesWritten >= kTelemetryReportBytesLimit && 3860 index->mState == READY && !index->mIndexNeedsUpdate && 3861 !index->mShuttingDown) { 3862 index->DoTelemetryReport(); 3863 index->mTotalBytesWritten = 0; 3864 return; 3865 } 3866 } 3867 3868 void CacheIndex::DoTelemetryReport() { 3869 static const nsLiteralCString 3870 contentTypeNames[nsICacheEntry::CONTENT_TYPE_LAST] = { 3871 "UNKNOWN"_ns, "OTHER"_ns, "JAVASCRIPT"_ns, "IMAGE"_ns, 3872 "MEDIA"_ns, "STYLESHEET"_ns, "WASM"_ns, "DICTIONARY"_ns}; 3873 3874 for (uint32_t i = 0; i < nsICacheEntry::CONTENT_TYPE_LAST; ++i) { 3875 if (mIndexStats.Size() > 0) { 3876 glean::network::cache_size_share.Get(contentTypeNames[i]) 3877 .AccumulateSingleSample( 3878 round(static_cast<double>(mIndexStats.SizeByType(i)) * 100.0 / 3879 static_cast<double>(mIndexStats.Size()))); 3880 } 3881 3882 if (mIndexStats.Count() > 0) { 3883 glean::network::cache_entry_count_share.Get(contentTypeNames[i]) 3884 .AccumulateSingleSample( 3885 round(static_cast<double>(mIndexStats.CountByType(i)) * 100.0 / 3886 static_cast<double>(mIndexStats.Count()))); 3887 } 3888 } 3889 3890 nsCString probeKey; 3891 if (CacheObserver::SmartCacheSizeEnabled()) { 3892 probeKey = "SMARTSIZE"_ns; 3893 } else { 3894 probeKey = "USERDEFINEDSIZE"_ns; 3895 } 3896 glean::network::cache_entry_count.Get(probeKey).AccumulateSingleSample( 3897 mIndexStats.Count()); 3898 glean::network::cache_size.Get(probeKey).Accumulate(mIndexStats.Size() >> 10); 3899 } 3900 3901 // static 3902 void CacheIndex::OnAsyncEviction(bool aEvicting) { 3903 StaticMutexAutoLock lock(sLock); 3904 3905 RefPtr<CacheIndex> index = gInstance; 3906 if (!index) { 3907 return; 3908 } 3909 3910 index->mAsyncGetDiskConsumptionBlocked = aEvicting; 3911 if (!aEvicting) { 3912 index->NotifyAsyncGetDiskConsumptionCallbacks(); 3913 } 3914 } 3915 } // namespace mozilla::net