CacheStorageService.cpp (77294B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "CacheLog.h" 8 #include "CacheStorageService.h" 9 #include "CacheFileIOManager.h" 10 #include "CacheObserver.h" 11 #include "CacheIndex.h" 12 #include "CacheIndexIterator.h" 13 #include "CacheStorage.h" 14 #include "CacheEntry.h" 15 #include "CacheFileUtils.h" 16 17 #include "ErrorList.h" 18 #include "nsICacheStorageVisitor.h" 19 #include "nsIObserverService.h" 20 #include "nsIFile.h" 21 #include "nsIURI.h" 22 #include "nsCOMPtr.h" 23 #include "nsContentUtils.h" 24 #include "nsNetCID.h" 25 #include "nsNetUtil.h" 26 #include "nsServiceManagerUtils.h" 27 #include "nsXULAppAPI.h" 28 #include "mozilla/AtomicBitfields.h" 29 #include "mozilla/TimeStamp.h" 30 #include "mozilla/DebugOnly.h" 31 #include "mozilla/glean/NetwerkMetrics.h" 32 #include "mozilla/Services.h" 33 #include "mozilla/StoragePrincipalHelper.h" 34 #include "mozilla/IntegerPrintfMacros.h" 35 #include "mozilla/glean/NetwerkCache2Metrics.h" 36 #include "mozilla/StaticPrefs_network.h" 37 38 namespace mozilla::net { 39 40 // static 41 GlobalEntryTables* CacheStorageService::sGlobalEntryTables = nullptr; 42 StaticMutex CacheStorageService::sLock; 43 44 namespace { 45 46 void AppendMemoryStorageTag(nsAutoCString& key) { 47 // Using DEL as the very last ascii-7 character we can use in the list of 48 // attributes 49 key.Append('\x7f'); 50 key.Append(','); 51 } 52 53 } // namespace 54 55 CacheMemoryConsumer::CacheMemoryConsumer(uint32_t aFlags) { 56 StoreFlags(aFlags); 57 } 58 59 void CacheMemoryConsumer::DoMemoryReport(uint32_t aCurrentSize) { 60 if (!(LoadFlags() & DONT_REPORT) && CacheStorageService::Self()) { 61 CacheStorageService::Self()->OnMemoryConsumptionChange(this, aCurrentSize); 62 } 63 } 64 65 CacheStorageService::MemoryPool::MemoryPool(EType aType) : mType(aType) {} 66 67 CacheStorageService::MemoryPool::~MemoryPool() { 68 if (mMemorySize != 0) { 69 NS_ERROR( 70 "Network cache reported memory consumption is not at 0, probably " 71 "leaking?"); 72 } 73 } 74 75 uint32_t CacheStorageService::MemoryPool::Limit() const { 76 uint32_t limit = 0; 77 78 switch (mType) { 79 case DISK: 80 limit = CacheObserver::MetadataMemoryLimit(); 81 break; 82 case MEMORY: 83 limit = CacheObserver::MemoryCacheCapacity(); 84 break; 85 default: 86 MOZ_CRASH("Bad pool type"); 87 } 88 89 static const uint32_t kMaxLimit = 0x3FFFFF; 90 if (limit > kMaxLimit) { 91 LOG((" a memory limit (%u) is unexpectedly high, clipping to %u", limit, 92 kMaxLimit)); 93 limit = kMaxLimit; 94 } 95 96 return limit << 10; 97 } 98 99 NS_IMPL_ISUPPORTS(CacheStorageService, nsICacheStorageService, 100 nsIMemoryReporter, nsITimerCallback, nsICacheTesting, 101 nsINamed) 102 103 CacheStorageService* CacheStorageService::sSelf = nullptr; 104 105 CacheStorageService::CacheStorageService() { 106 CacheFileIOManager::Init(); 107 108 MOZ_ASSERT(XRE_IsParentProcess()); 109 MOZ_ASSERT(!sSelf); 110 111 sSelf = this; 112 sGlobalEntryTables = new GlobalEntryTables(); 113 114 RegisterStrongMemoryReporter(this); 115 } 116 117 CacheStorageService::~CacheStorageService() { 118 LOG(("CacheStorageService::~CacheStorageService")); 119 sSelf = nullptr; 120 } 121 122 void CacheStorageService::Shutdown() { 123 StaticMutexAutoLock lock(sLock); 124 125 if (mShutdown) return; 126 127 LOG(("CacheStorageService::Shutdown - start")); 128 129 mShutdown = true; 130 131 nsCOMPtr<nsIRunnable> event = 132 NewRunnableMethod("net::CacheStorageService::ShutdownBackground", this, 133 &CacheStorageService::ShutdownBackground); 134 Dispatch(event); 135 136 #ifdef NS_FREE_PERMANENT_DATA 137 sGlobalEntryTables->Clear(); 138 delete sGlobalEntryTables; 139 #endif 140 sGlobalEntryTables = nullptr; 141 142 LOG(("CacheStorageService::Shutdown - done")); 143 } 144 145 void CacheStorageService::ShutdownBackground() { 146 LOG(("CacheStorageService::ShutdownBackground - start")); 147 148 MOZ_ASSERT(IsOnManagementThread()); 149 150 { 151 StaticMutexAutoLock lock(sLock); 152 153 // Cancel purge timer to avoid leaking. 154 if (mPurgeTimer) { 155 LOG((" freeing the timer")); 156 mPurgeTimer->Cancel(); 157 } 158 } 159 160 #ifdef NS_FREE_PERMANENT_DATA 161 Pool(MemoryPool::EType::DISK).mManagedEntries.clear(); 162 Pool(MemoryPool::EType::MEMORY).mManagedEntries.clear(); 163 #endif 164 165 LOG(("CacheStorageService::ShutdownBackground - done")); 166 } 167 168 // Internal management methods 169 170 namespace CacheStorageServiceInternal { 171 172 // WalkCacheRunnable 173 // Base class for particular storage entries visiting 174 class WalkCacheRunnable : public Runnable, 175 public CacheStorageService::EntryInfoCallback { 176 protected: 177 WalkCacheRunnable(nsICacheStorageVisitor* aVisitor, bool aVisitEntries) 178 : Runnable("net::WalkCacheRunnable"), 179 mService(CacheStorageService::Self()), 180 mCallback(aVisitor) { 181 MOZ_ASSERT(NS_IsMainThread()); 182 StoreNotifyStorage(true); 183 StoreVisitEntries(aVisitEntries); 184 } 185 186 virtual ~WalkCacheRunnable() { 187 if (mCallback) { 188 ProxyReleaseMainThread("WalkCacheRunnable::mCallback", mCallback); 189 } 190 } 191 192 RefPtr<CacheStorageService> mService; 193 nsCOMPtr<nsICacheStorageVisitor> mCallback; 194 195 uint64_t mSize{0}; 196 197 // clang-format off 198 MOZ_ATOMIC_BITFIELDS(mAtomicBitfields, 8, ( 199 (bool, NotifyStorage, 1), 200 (bool, VisitEntries, 1) 201 )) 202 // clang-format on 203 204 Atomic<bool> mCancel{false}; 205 }; 206 207 // WalkMemoryCacheRunnable 208 // Responsible to visit memory storage and walk 209 // all entries on it asynchronously. 210 class WalkMemoryCacheRunnable : public WalkCacheRunnable { 211 public: 212 WalkMemoryCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries, 213 nsICacheStorageVisitor* aVisitor) 214 : WalkCacheRunnable(aVisitor, aVisitEntries) { 215 CacheFileUtils::AppendKeyPrefix(aLoadInfo, mContextKey); 216 MOZ_ASSERT(NS_IsMainThread()); 217 } 218 219 nsresult Walk() { return mService->Dispatch(this); } 220 221 private: 222 NS_IMETHOD Run() override { 223 if (CacheStorageService::IsOnManagementThread()) { 224 LOG(("WalkMemoryCacheRunnable::Run - collecting [this=%p]", this)); 225 // First, walk, count and grab all entries from the storage 226 227 StaticMutexAutoLock lock(CacheStorageService::sLock); 228 229 if (!CacheStorageService::IsRunning()) return NS_ERROR_NOT_INITIALIZED; 230 231 // Count the entries to allocate the array memory all at once. 232 size_t numEntries = 0; 233 for (const auto& entries : 234 CacheStorageService::sGlobalEntryTables->Values()) { 235 if (entries->Type() != CacheEntryTable::MEMORY_ONLY) { 236 continue; 237 } 238 numEntries += entries->Values().Count(); 239 } 240 mEntryArray.SetCapacity(numEntries); 241 242 // Collect the entries. 243 for (const auto& entries : 244 CacheStorageService::sGlobalEntryTables->Values()) { 245 if (entries->Type() != CacheEntryTable::MEMORY_ONLY) { 246 continue; 247 } 248 249 for (CacheEntry* entry : entries->Values()) { 250 MOZ_ASSERT(!entry->IsUsingDisk()); 251 252 mSize += entry->GetMetadataMemoryConsumption(); 253 254 int64_t size; 255 if (NS_SUCCEEDED(entry->GetDataSize(&size))) { 256 mSize += size; 257 } 258 mEntryArray.AppendElement(entry); 259 } 260 } 261 262 // Next, we dispatch to the main thread 263 } else if (NS_IsMainThread()) { 264 LOG(("WalkMemoryCacheRunnable::Run - notifying [this=%p]", this)); 265 266 if (LoadNotifyStorage()) { 267 LOG((" storage")); 268 269 uint64_t capacity = CacheObserver::MemoryCacheCapacity(); 270 capacity <<= 10; // kilobytes to bytes 271 272 // Second, notify overall storage info 273 mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize, capacity, 274 nullptr); 275 if (!LoadVisitEntries()) return NS_OK; // done 276 277 StoreNotifyStorage(false); 278 279 } else { 280 LOG((" entry [left=%zu, canceled=%d]", mEntryArray.Length(), 281 (bool)mCancel)); 282 283 // Third, notify each entry until depleted or canceled. 284 if (mNextEntryIdx >= mEntryArray.Length() || mCancel) { 285 mCallback->OnCacheEntryVisitCompleted(); 286 return NS_OK; // done 287 } 288 289 // Grab the next entry. 290 RefPtr<CacheEntry> entry = std::move(mEntryArray[mNextEntryIdx++]); 291 292 // Invokes this->OnEntryInfo, that calls the callback with all 293 // information of the entry. 294 CacheStorageService::GetCacheEntryInfo(entry, this); 295 } 296 } else { 297 MOZ_CRASH("Bad thread"); 298 return NS_ERROR_FAILURE; 299 } 300 301 NS_DispatchToMainThread(this); 302 return NS_OK; 303 } 304 305 virtual ~WalkMemoryCacheRunnable() { 306 if (mCallback) { 307 ProxyReleaseMainThread("WalkMemoryCacheRunnable::mCallback", mCallback); 308 } 309 } 310 311 virtual void OnEntryInfo(const nsACString& aURISpec, 312 const nsACString& aIdEnhance, int64_t aDataSize, 313 int64_t aAltDataSize, uint32_t aFetchCount, 314 uint32_t aLastModifiedTime, uint32_t aExpirationTime, 315 bool aPinned, nsILoadContextInfo* aInfo) override { 316 nsresult rv; 317 318 nsCOMPtr<nsIURI> uri; 319 rv = NS_NewURI(getter_AddRefs(uri), aURISpec); 320 if (NS_FAILED(rv)) { 321 return; 322 } 323 324 rv = mCallback->OnCacheEntryInfo(uri, aIdEnhance, aDataSize, aAltDataSize, 325 aFetchCount, aLastModifiedTime, 326 aExpirationTime, aPinned, aInfo); 327 if (NS_FAILED(rv)) { 328 LOG((" callback failed, canceling the walk")); 329 mCancel = true; 330 } 331 } 332 333 private: 334 nsCString mContextKey; 335 nsTArray<RefPtr<CacheEntry>> mEntryArray; 336 size_t mNextEntryIdx{0}; 337 }; 338 339 // WalkDiskCacheRunnable 340 // Using the cache index information to get the list of files per context. 341 class WalkDiskCacheRunnable : public WalkCacheRunnable { 342 public: 343 WalkDiskCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries, 344 nsICacheStorageVisitor* aVisitor) 345 : WalkCacheRunnable(aVisitor, aVisitEntries), 346 mLoadInfo(aLoadInfo), 347 mPass(COLLECT_STATS), 348 mCount(0) {} 349 350 nsresult Walk() { 351 // TODO, bug 998693 352 // Initial index build should be forced here so that about:cache soon 353 // after startup gives some meaningfull results. 354 355 // Dispatch to the INDEX level in hope that very recent cache entries 356 // information gets to the index list before we grab the index iterator 357 // for the first time. This tries to avoid miss of entries that has 358 // been created right before the visit is required. 359 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread(); 360 NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED); 361 362 return thread->Dispatch(this, CacheIOThread::INDEX); 363 } 364 365 private: 366 // Invokes OnCacheEntryInfo callback for each single found entry. 367 // There is one instance of this class per one entry. 368 class OnCacheEntryInfoRunnable : public Runnable { 369 public: 370 explicit OnCacheEntryInfoRunnable(WalkDiskCacheRunnable* aWalker) 371 : Runnable("net::WalkDiskCacheRunnable::OnCacheEntryInfoRunnable"), 372 mWalker(aWalker) {} 373 374 NS_IMETHOD Run() override { 375 MOZ_ASSERT(NS_IsMainThread()); 376 377 nsresult rv; 378 379 nsCOMPtr<nsIURI> uri; 380 rv = NS_NewURI(getter_AddRefs(uri), mURISpec); 381 if (NS_FAILED(rv)) { 382 return NS_OK; 383 } 384 385 rv = mWalker->mCallback->OnCacheEntryInfo( 386 uri, mIdEnhance, mDataSize, mAltDataSize, mFetchCount, 387 mLastModifiedTime, mExpirationTime, mPinned, mInfo); 388 if (NS_FAILED(rv)) { 389 mWalker->mCancel = true; 390 } 391 392 return NS_OK; 393 } 394 395 RefPtr<WalkDiskCacheRunnable> mWalker; 396 397 nsCString mURISpec; 398 nsCString mIdEnhance; 399 int64_t mDataSize{0}; 400 int64_t mAltDataSize{0}; 401 uint32_t mFetchCount{0}; 402 uint32_t mLastModifiedTime{0}; 403 uint32_t mExpirationTime{0}; 404 bool mPinned{false}; 405 nsCOMPtr<nsILoadContextInfo> mInfo; 406 }; 407 408 NS_IMETHOD Run() override { 409 // The main loop 410 nsresult rv; 411 412 if (CacheStorageService::IsOnManagementThread()) { 413 switch (mPass) { 414 case COLLECT_STATS: 415 // Get quickly the cache stats. 416 uint32_t size; 417 rv = CacheIndex::GetCacheStats(mLoadInfo, &size, &mCount); 418 if (NS_FAILED(rv)) { 419 if (LoadVisitEntries()) { 420 // both onStorageInfo and onCompleted are expected 421 NS_DispatchToMainThread(this); 422 } 423 return NS_DispatchToMainThread(this); 424 } 425 426 mSize = static_cast<uint64_t>(size) << 10; 427 428 // Invoke onCacheStorageInfo with valid information. 429 NS_DispatchToMainThread(this); 430 431 if (!LoadVisitEntries()) { 432 return NS_OK; // done 433 } 434 435 mPass = ITERATE_METADATA; 436 [[fallthrough]]; 437 438 case ITERATE_METADATA: 439 // Now grab the context iterator. 440 if (!mIter) { 441 rv = 442 CacheIndex::GetIterator(mLoadInfo, true, getter_AddRefs(mIter)); 443 if (NS_FAILED(rv)) { 444 // Invoke onCacheEntryVisitCompleted now 445 return NS_DispatchToMainThread(this); 446 } 447 } 448 449 while (!mCancel && !CacheObserver::ShuttingDown()) { 450 if (CacheIOThread::YieldAndRerun()) return NS_OK; 451 452 SHA1Sum::Hash hash; 453 rv = mIter->GetNextHash(&hash); 454 if (NS_FAILED(rv)) break; // done (or error?) 455 456 // This synchronously invokes OnEntryInfo on this class where we 457 // redispatch to the main thread for the consumer callback. 458 CacheFileIOManager::GetEntryInfo(&hash, this); 459 } 460 461 // Invoke onCacheEntryVisitCompleted on the main thread 462 NS_DispatchToMainThread(this); 463 } 464 } else if (NS_IsMainThread()) { 465 if (LoadNotifyStorage()) { 466 nsCOMPtr<nsIFile> dir; 467 CacheFileIOManager::GetCacheDirectory(getter_AddRefs(dir)); 468 uint64_t capacity = CacheObserver::DiskCacheCapacity(); 469 capacity <<= 10; // kilobytes to bytes 470 mCallback->OnCacheStorageInfo(mCount, mSize, capacity, dir); 471 StoreNotifyStorage(false); 472 } else { 473 mCallback->OnCacheEntryVisitCompleted(); 474 } 475 } else { 476 MOZ_CRASH("Bad thread"); 477 return NS_ERROR_FAILURE; 478 } 479 480 return NS_OK; 481 } 482 483 virtual void OnEntryInfo(const nsACString& aURISpec, 484 const nsACString& aIdEnhance, int64_t aDataSize, 485 int64_t aAltDataSize, uint32_t aFetchCount, 486 uint32_t aLastModifiedTime, uint32_t aExpirationTime, 487 bool aPinned, nsILoadContextInfo* aInfo) override { 488 // Called directly from CacheFileIOManager::GetEntryInfo. 489 490 // Invoke onCacheEntryInfo on the main thread for this entry. 491 RefPtr<OnCacheEntryInfoRunnable> info = new OnCacheEntryInfoRunnable(this); 492 info->mURISpec = aURISpec; 493 info->mIdEnhance = aIdEnhance; 494 info->mDataSize = aDataSize; 495 info->mAltDataSize = aAltDataSize; 496 info->mFetchCount = aFetchCount; 497 info->mLastModifiedTime = aLastModifiedTime; 498 info->mExpirationTime = aExpirationTime; 499 info->mPinned = aPinned; 500 info->mInfo = aInfo; 501 502 NS_DispatchToMainThread(info); 503 } 504 505 RefPtr<nsILoadContextInfo> mLoadInfo; 506 enum { 507 // First, we collect stats for the load context. 508 COLLECT_STATS, 509 510 // Second, if demanded, we iterate over the entries gethered 511 // from the iterator and call CacheFileIOManager::GetEntryInfo 512 // for each found entry. 513 ITERATE_METADATA, 514 } mPass; 515 516 RefPtr<CacheIndexIterator> mIter; 517 uint32_t mCount; 518 }; 519 520 } // namespace CacheStorageServiceInternal 521 522 void CacheStorageService::DropPrivateBrowsingEntries() { 523 StaticMutexAutoLock lock(sLock); 524 525 if (mShutdown) return; 526 527 nsTArray<nsCString> keys; 528 for (const nsACString& key : sGlobalEntryTables->Keys()) { 529 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(key); 530 if (info && info->IsPrivate()) { 531 keys.AppendElement(key); 532 } 533 } 534 535 for (uint32_t i = 0; i < keys.Length(); ++i) { 536 DoomStorageEntries(keys[i], nullptr, true, false, nullptr); 537 } 538 } 539 540 // Helper methods 541 542 // static 543 bool CacheStorageService::IsOnManagementThread() { 544 RefPtr<CacheStorageService> service = Self(); 545 if (!service) return false; 546 547 nsCOMPtr<nsIEventTarget> target = service->Thread(); 548 if (!target) return false; 549 550 bool currentThread; 551 nsresult rv = target->IsOnCurrentThread(¤tThread); 552 return NS_SUCCEEDED(rv) && currentThread; 553 } 554 555 already_AddRefed<nsIEventTarget> CacheStorageService::Thread() const { 556 return CacheFileIOManager::IOTarget(); 557 } 558 559 nsresult CacheStorageService::Dispatch(nsIRunnable* aEvent) { 560 RefPtr<CacheIOThread> cacheIOThread = CacheFileIOManager::IOThread(); 561 if (!cacheIOThread) return NS_ERROR_NOT_AVAILABLE; 562 563 return cacheIOThread->Dispatch(aEvent, CacheIOThread::MANAGEMENT); 564 } 565 566 namespace CacheStorageEvictHelper { 567 568 nsresult ClearStorage(bool const aPrivate, bool const aAnonymous, 569 OriginAttributes& aOa) { 570 nsresult rv; 571 572 aOa.SyncAttributesWithPrivateBrowsing(aPrivate); 573 RefPtr<LoadContextInfo> info = GetLoadContextInfo(aAnonymous, aOa); 574 575 nsCOMPtr<nsICacheStorage> storage; 576 RefPtr<CacheStorageService> service = CacheStorageService::Self(); 577 NS_ENSURE_TRUE(service, NS_ERROR_FAILURE); 578 579 // Clear disk storage 580 rv = service->DiskCacheStorage(info, getter_AddRefs(storage)); 581 NS_ENSURE_SUCCESS(rv, rv); 582 rv = storage->AsyncEvictStorage(nullptr); 583 NS_ENSURE_SUCCESS(rv, rv); 584 585 // Clear memory storage 586 rv = service->MemoryCacheStorage(info, getter_AddRefs(storage)); 587 NS_ENSURE_SUCCESS(rv, rv); 588 rv = storage->AsyncEvictStorage(nullptr); 589 NS_ENSURE_SUCCESS(rv, rv); 590 591 return NS_OK; 592 } 593 594 nsresult Run(OriginAttributes& aOa) { 595 nsresult rv; 596 597 // Clear all [private X anonymous] combinations 598 rv = ClearStorage(false, false, aOa); 599 NS_ENSURE_SUCCESS(rv, rv); 600 rv = ClearStorage(false, true, aOa); 601 NS_ENSURE_SUCCESS(rv, rv); 602 rv = ClearStorage(true, false, aOa); 603 NS_ENSURE_SUCCESS(rv, rv); 604 rv = ClearStorage(true, true, aOa); 605 NS_ENSURE_SUCCESS(rv, rv); 606 607 return NS_OK; 608 } 609 610 } // namespace CacheStorageEvictHelper 611 612 // nsICacheStorageService 613 614 NS_IMETHODIMP CacheStorageService::MemoryCacheStorage( 615 nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) { 616 NS_ENSURE_ARG(_retval); 617 618 nsCOMPtr<nsICacheStorage> storage = 619 new CacheStorage(aLoadContextInfo, false, false, false); 620 storage.forget(_retval); 621 return NS_OK; 622 } 623 624 NS_IMETHODIMP CacheStorageService::DiskCacheStorage( 625 nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) { 626 NS_ENSURE_ARG(_retval); 627 628 // TODO save some heap granularity - cache commonly used storages. 629 630 // When disk cache is disabled, still provide a storage, but just keep stuff 631 // in memory. 632 bool useDisk = CacheObserver::UseDiskCache(); 633 634 nsCOMPtr<nsICacheStorage> storage = new CacheStorage( 635 aLoadContextInfo, useDisk, false /* size limit */, false /* don't pin */); 636 storage.forget(_retval); 637 return NS_OK; 638 } 639 640 NS_IMETHODIMP CacheStorageService::PinningCacheStorage( 641 nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) { 642 NS_ENSURE_ARG(aLoadContextInfo); 643 NS_ENSURE_ARG(_retval); 644 645 // When disk cache is disabled don't pretend we cache. 646 if (!CacheObserver::UseDiskCache()) { 647 return NS_ERROR_NOT_AVAILABLE; 648 } 649 650 nsCOMPtr<nsICacheStorage> storage = 651 new CacheStorage(aLoadContextInfo, true /* use disk */, 652 true /* ignore size checks */, true /* pin */); 653 storage.forget(_retval); 654 return NS_OK; 655 } 656 657 NS_IMETHODIMP CacheStorageService::Clear() { 658 nsresult rv; 659 660 // Tell the index to block notification to AsyncGetDiskConsumption. 661 // Will be allowed again from CacheFileContextEvictor::EvictEntries() 662 // when all the context have been removed from disk. 663 CacheIndex::OnAsyncEviction(true); 664 665 StaticMutexAutoLock lock(sLock); 666 667 { 668 mozilla::MutexAutoLock forcedValidEntriesLock(mForcedValidEntriesLock); 669 mForcedValidEntries.Clear(); 670 } 671 672 NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED); 673 674 const auto keys = ToTArray<nsTArray<nsCString>>(sGlobalEntryTables->Keys()); 675 for (const auto& key : keys) { 676 DoomStorageEntries(key, nullptr, true, false, nullptr); 677 } 678 679 // Passing null as a load info means to evict all contexts. 680 // EvictByContext() respects the entry pinning. EvictAll() does not. 681 rv = CacheFileIOManager::EvictByContext(nullptr, false, u""_ns); 682 NS_ENSURE_SUCCESS(rv, rv); 683 684 return NS_OK; 685 } 686 687 NS_IMETHODIMP CacheStorageService::ClearOriginsByPrincipal( 688 nsIPrincipal* aPrincipal) { 689 nsresult rv; 690 691 if (NS_WARN_IF(!aPrincipal)) { 692 return NS_ERROR_FAILURE; 693 } 694 695 nsAutoString origin; 696 rv = nsContentUtils::GetWebExposedOriginSerialization(aPrincipal, origin); 697 NS_ENSURE_SUCCESS(rv, rv); 698 LOG(("CacheStorageService::ClearOriginsByPrincipal %s", 699 NS_ConvertUTF16toUTF8(origin).get())); 700 701 rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), true); 702 NS_ENSURE_SUCCESS(rv, rv); 703 704 rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), false); 705 NS_ENSURE_SUCCESS(rv, rv); 706 707 return NS_OK; 708 } 709 710 NS_IMETHODIMP CacheStorageService::ClearOriginsByOriginAttributes( 711 const nsAString& aOriginAttributes) { 712 nsresult rv; 713 LOG(("CacheStorageService::ClearOriginsByOriginAttributes %s", 714 NS_ConvertUTF16toUTF8(aOriginAttributes).get())); 715 716 if (NS_WARN_IF(aOriginAttributes.IsEmpty())) { 717 return NS_ERROR_FAILURE; 718 } 719 720 OriginAttributes oa; 721 if (!oa.Init(aOriginAttributes)) { 722 NS_ERROR("Could not parse the argument for OriginAttributes"); 723 return NS_ERROR_FAILURE; 724 } 725 726 rv = CacheStorageEvictHelper::Run(oa); 727 NS_ENSURE_SUCCESS(rv, rv); 728 729 return NS_OK; 730 } 731 732 static bool RemoveExactEntry(CacheEntryTable* aEntries, nsACString const& aKey, 733 CacheEntry* aEntry, bool aOverwrite) { 734 RefPtr<CacheEntry> existingEntry; 735 if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) { 736 LOG(("RemoveExactEntry [entry=%p already gone]", aEntry)); 737 return false; // Already removed... 738 } 739 740 if (!aOverwrite && existingEntry != aEntry) { 741 LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry)); 742 return false; // Already replaced... 743 } 744 745 LOG(("RemoveExactEntry [entry=%p removed]", aEntry)); 746 aEntries->Remove(aKey); 747 return true; 748 } 749 750 NS_IMETHODIMP CacheStorageService::ClearBaseDomain( 751 const nsAString& aBaseDomain) { 752 LOG(("CacheStorageService::ClearBaseDomain %s", 753 NS_ConvertUTF16toUTF8(aBaseDomain).get())); 754 StaticMutexAutoLock lock(sLock); 755 if (sGlobalEntryTables) { 756 if (mShutdown) return NS_ERROR_NOT_AVAILABLE; 757 758 nsCString cBaseDomain = NS_ConvertUTF16toUTF8(aBaseDomain); 759 760 nsTArray<nsCString> keys; 761 for (const auto& globalEntry : *sGlobalEntryTables) { 762 // Match by partitionKey base domain. This should cover most cache entries 763 // because we statically partition the cache. Most first party cache 764 // entries will also have a partitionKey set where the partitionKey base 765 // domain will match the entry URI base domain. 766 const nsACString& key = globalEntry.GetKey(); 767 nsCOMPtr<nsILoadContextInfo> info = 768 CacheFileUtils::ParseKey(globalEntry.GetKey()); 769 770 if (info && 771 StoragePrincipalHelper::PartitionKeyHasBaseDomain( 772 info->OriginAttributesPtr()->mPartitionKey, aBaseDomain)) { 773 keys.AppendElement(key); 774 continue; 775 } 776 777 // If we didn't get a partitionKey match, try to match by entry URI. This 778 // requires us to iterate over all entries. 779 CacheEntryTable* table = globalEntry.GetWeak(); 780 MOZ_ASSERT(table); 781 782 nsTArray<RefPtr<CacheEntry>> entriesToDelete; 783 784 for (CacheEntry* entry : table->Values()) { 785 nsCOMPtr<nsIURI> uri; 786 nsresult rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI()); 787 if (NS_WARN_IF(NS_FAILED(rv))) { 788 continue; 789 } 790 791 nsAutoCString host; 792 rv = uri->GetHost(host); 793 // Some entries may not have valid hosts. We can skip them. 794 if (NS_FAILED(rv) || host.IsEmpty()) { 795 continue; 796 } 797 798 bool hasRootDomain = false; 799 rv = HasRootDomain(host, cBaseDomain, &hasRootDomain); 800 if (NS_WARN_IF(NS_FAILED(rv))) { 801 continue; 802 } 803 if (hasRootDomain) { 804 entriesToDelete.AppendElement(entry); 805 } 806 } 807 808 // Clear individual matched entries. 809 for (RefPtr<CacheEntry>& entry : entriesToDelete) { 810 nsAutoCString entryKey; 811 nsresult rv = entry->HashingKey(entryKey); 812 if (NS_FAILED(rv)) { 813 NS_ERROR("aEntry->HashingKey() failed?"); 814 return rv; 815 } 816 817 RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */); 818 } 819 } 820 821 // Clear matched keys. 822 for (uint32_t i = 0; i < keys.Length(); ++i) { 823 LOG(("CacheStorageService::ClearBaseDomain Dooming %s", keys[i].get())); 824 DoomStorageEntries(keys[i], nullptr, true, false, nullptr); 825 } 826 } 827 828 return CacheFileIOManager::EvictByContext(nullptr, false /* pinned */, u""_ns, 829 aBaseDomain); 830 } 831 832 nsresult CacheStorageService::ClearOriginInternal( 833 const nsAString& aOrigin, const OriginAttributes& aOriginAttributes, 834 bool aAnonymous) { 835 nsresult rv; 836 837 RefPtr<LoadContextInfo> info = 838 GetLoadContextInfo(aAnonymous, aOriginAttributes); 839 if (NS_WARN_IF(!info)) { 840 return NS_ERROR_FAILURE; 841 } 842 843 StaticMutexAutoLock lock(sLock); 844 845 if (sGlobalEntryTables) { 846 for (const auto& globalEntry : *sGlobalEntryTables) { 847 bool matches = false; 848 rv = CacheFileUtils::KeyMatchesLoadContextInfo(globalEntry.GetKey(), info, 849 &matches); 850 NS_ENSURE_SUCCESS(rv, rv); 851 if (!matches) { 852 continue; 853 } 854 855 CacheEntryTable* table = globalEntry.GetWeak(); 856 MOZ_ASSERT(table); 857 858 nsTArray<RefPtr<CacheEntry>> entriesToDelete; 859 860 for (CacheEntry* entry : table->Values()) { 861 nsCOMPtr<nsIURI> uri; 862 rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI()); 863 NS_ENSURE_SUCCESS(rv, rv); 864 865 nsAutoString origin; 866 rv = nsContentUtils::GetWebExposedOriginSerialization(uri, origin); 867 NS_ENSURE_SUCCESS(rv, rv); 868 869 if (origin != aOrigin) { 870 continue; 871 } 872 873 entriesToDelete.AppendElement(entry); 874 } 875 876 for (RefPtr<CacheEntry>& entry : entriesToDelete) { 877 nsAutoCString entryKey; 878 rv = entry->HashingKey(entryKey); 879 if (NS_FAILED(rv)) { 880 NS_ERROR("aEntry->HashingKey() failed?"); 881 return rv; 882 } 883 MOZ_ASSERT_IF(info->IsPrivate(), !entry->IsUsingDisk()); 884 RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */); 885 } 886 } 887 } 888 889 if (!info->IsPrivate()) { 890 rv = CacheFileIOManager::EvictByContext(info, false /* pinned */, aOrigin); 891 } 892 NS_ENSURE_SUCCESS(rv, rv); 893 894 return NS_OK; 895 } 896 897 NS_IMETHODIMP CacheStorageService::ClearOriginDictionary(nsIURI* aURI) { 898 LOG(("CacheStorageService::ClearOriginDictionary")); 899 // Note: due to cookie samesite rules, we need to clean for all ports 900 DictionaryCache::RemoveDictionariesForOrigin(aURI); 901 return NS_OK; 902 } 903 904 NS_IMETHODIMP CacheStorageService::ClearAllOriginDictionaries() { 905 LOG(("CacheStorageService::ClearAllOriginDictionaries")); 906 DictionaryCache::RemoveAllDictionaries(); 907 return NS_OK; 908 } 909 910 NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat) { 911 uint32_t what; 912 913 switch (aWhat) { 914 case PURGE_DISK_DATA_ONLY: 915 what = CacheEntry::PURGE_DATA_ONLY_DISK_BACKED; 916 break; 917 918 case PURGE_DISK_ALL: 919 what = CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED; 920 break; 921 922 case PURGE_EVERYTHING: 923 what = CacheEntry::PURGE_WHOLE; 924 break; 925 926 default: 927 return NS_ERROR_INVALID_ARG; 928 } 929 930 nsCOMPtr<nsIRunnable> event = new PurgeFromMemoryRunnable(this, what); 931 932 return Dispatch(event); 933 } 934 935 NS_IMETHODIMP CacheStorageService::PurgeFromMemoryRunnable::Run() { 936 if (NS_IsMainThread()) { 937 nsCOMPtr<nsIObserverService> observerService = 938 mozilla::services::GetObserverService(); 939 if (observerService) { 940 observerService->NotifyObservers( 941 nullptr, "cacheservice:purge-memory-pools", nullptr); 942 } 943 944 return NS_OK; 945 } 946 947 if (mService) { 948 // Note that we seem to come here only in the case of "memory-pressure" 949 // being notified (or in case of tests), so we start from purging in-memory 950 // entries first and ignore minprogress for disk entries. 951 // TODO not all flags apply to both pools. 952 mService->Pool(MemoryPool::EType::MEMORY) 953 .PurgeAll(mWhat, StaticPrefs::network_cache_purge_minprogress_memory()); 954 mService->Pool(MemoryPool::EType::DISK).PurgeAll(mWhat, 0); 955 mService = nullptr; 956 } 957 958 NS_DispatchToMainThread(this); 959 return NS_OK; 960 } 961 962 NS_IMETHODIMP CacheStorageService::AsyncGetDiskConsumption( 963 nsICacheStorageConsumptionObserver* aObserver) { 964 NS_ENSURE_ARG(aObserver); 965 966 nsresult rv; 967 968 rv = CacheIndex::AsyncGetDiskConsumption(aObserver); 969 NS_ENSURE_SUCCESS(rv, rv); 970 971 return NS_OK; 972 } 973 974 NS_IMETHODIMP CacheStorageService::GetIoTarget(nsIEventTarget** aEventTarget) { 975 NS_ENSURE_ARG(aEventTarget); 976 977 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget(); 978 ioTarget.forget(aEventTarget); 979 980 return NS_OK; 981 } 982 983 NS_IMETHODIMP CacheStorageService::AsyncVisitAllStorages( 984 nsICacheStorageVisitor* aVisitor, bool aVisitEntries) { 985 LOG(("CacheStorageService::AsyncVisitAllStorages [cb=%p]", aVisitor)); 986 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); 987 988 // Walking the disk cache also walks the memory cache. 989 RefPtr<CacheStorageServiceInternal::WalkDiskCacheRunnable> event = 990 new CacheStorageServiceInternal::WalkDiskCacheRunnable( 991 nullptr, aVisitEntries, aVisitor); 992 return event->Walk(); 993 } 994 995 // Methods used by CacheEntry for management of in-memory structures. 996 997 void CacheStorageService::RegisterEntry(CacheEntry* aEntry) { 998 MOZ_ASSERT(IsOnManagementThread()); 999 1000 if (mShutdown || !aEntry->CanRegister()) return; 1001 1002 TelemetryRecordEntryCreation(aEntry); 1003 1004 LOG(("CacheStorageService::RegisterEntry [entry=%p]", aEntry)); 1005 1006 MemoryPool& pool = Pool(aEntry->IsUsingDisk()); 1007 pool.mManagedEntries.insertBack(aEntry); 1008 1009 aEntry->SetRegistered(true); 1010 } 1011 1012 void CacheStorageService::UnregisterEntry(CacheEntry* aEntry) { 1013 MOZ_ASSERT(IsOnManagementThread()); 1014 1015 if (!aEntry->IsRegistered()) return; 1016 1017 TelemetryRecordEntryRemoval(aEntry); 1018 1019 LOG(("CacheStorageService::UnregisterEntry [entry=%p]", aEntry)); 1020 1021 MemoryPool& pool = Pool(aEntry->IsUsingDisk()); 1022 aEntry->removeFrom(pool.mManagedEntries); 1023 1024 // Note: aEntry->CanRegister() since now returns false 1025 aEntry->SetRegistered(false); 1026 } 1027 1028 static bool AddExactEntry(CacheEntryTable* aEntries, nsACString const& aKey, 1029 CacheEntry* aEntry, bool aOverwrite) { 1030 RefPtr<CacheEntry> existingEntry; 1031 if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) { 1032 bool equals = existingEntry == aEntry; 1033 LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals)); 1034 return equals; // Already there... 1035 } 1036 1037 LOG(("AddExactEntry [entry=%p put]", aEntry)); 1038 aEntries->InsertOrUpdate(aKey, RefPtr{aEntry}); 1039 return true; 1040 } 1041 1042 bool CacheStorageService::RemoveEntry(CacheEntry* aEntry, 1043 bool aOnlyUnreferenced) { 1044 LOG(("CacheStorageService::RemoveEntry [entry=%p]", aEntry)); 1045 1046 nsAutoCString entryKey; 1047 nsresult rv = aEntry->HashingKey(entryKey); 1048 if (NS_FAILED(rv)) { 1049 NS_ERROR("aEntry->HashingKey() failed?"); 1050 return false; 1051 } 1052 1053 StaticMutexAutoLock lock(sLock); 1054 1055 if (mShutdown) { 1056 LOG((" after shutdown")); 1057 return false; 1058 } 1059 1060 if (aOnlyUnreferenced) { 1061 if (aEntry->IsReferenced()) { 1062 LOG((" still referenced, not removing")); 1063 return false; 1064 } 1065 1066 if (!aEntry->IsUsingDisk() && 1067 IsForcedValidEntry(aEntry->GetStorageID(), entryKey)) { 1068 LOG((" forced valid, not removing")); 1069 return false; 1070 } 1071 } 1072 1073 CacheEntryTable* entries; 1074 if (sGlobalEntryTables->Get(aEntry->GetStorageID(), &entries)) { 1075 RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */); 1076 } 1077 1078 nsAutoCString memoryStorageID(aEntry->GetStorageID()); 1079 AppendMemoryStorageTag(memoryStorageID); 1080 1081 if (sGlobalEntryTables->Get(memoryStorageID, &entries)) { 1082 RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */); 1083 } 1084 1085 return true; 1086 } 1087 1088 void CacheStorageService::RecordMemoryOnlyEntry(CacheEntry* aEntry, 1089 bool aOnlyInMemory, 1090 bool aOverwrite) { 1091 LOG( 1092 ("CacheStorageService::RecordMemoryOnlyEntry [entry=%p, memory=%d, " 1093 "overwrite=%d]", 1094 aEntry, aOnlyInMemory, aOverwrite)); 1095 // This method is responsible to put this entry to a special record hashtable 1096 // that contains only entries that are stored in memory. 1097 // Keep in mind that every entry, regardless of whether is in-memory-only or 1098 // not is always recorded in the storage master hash table, the one identified 1099 // by CacheEntry.StorageID(). 1100 1101 sLock.AssertCurrentThreadOwns(); 1102 1103 if (mShutdown) { 1104 LOG((" after shutdown")); 1105 return; 1106 } 1107 1108 nsresult rv; 1109 1110 nsAutoCString entryKey; 1111 rv = aEntry->HashingKey(entryKey); 1112 if (NS_FAILED(rv)) { 1113 NS_ERROR("aEntry->HashingKey() failed?"); 1114 return; 1115 } 1116 1117 CacheEntryTable* entries = nullptr; 1118 nsAutoCString memoryStorageID(aEntry->GetStorageID()); 1119 AppendMemoryStorageTag(memoryStorageID); 1120 1121 if (!sGlobalEntryTables->Get(memoryStorageID, &entries)) { 1122 if (!aOnlyInMemory) { 1123 LOG((" not recorded as memory only")); 1124 return; 1125 } 1126 1127 entries = sGlobalEntryTables 1128 ->InsertOrUpdate( 1129 memoryStorageID, 1130 MakeUnique<CacheEntryTable>(CacheEntryTable::MEMORY_ONLY)) 1131 .get(); 1132 LOG((" new memory-only storage table for %s", memoryStorageID.get())); 1133 } 1134 1135 if (aOnlyInMemory) { 1136 AddExactEntry(entries, entryKey, aEntry, aOverwrite); 1137 } else { 1138 RemoveExactEntry(entries, entryKey, aEntry, aOverwrite); 1139 } 1140 } 1141 1142 // Checks if a cache entry is forced valid (will be loaded directly from cache 1143 // without further validation) - see nsICacheEntry.idl for further details 1144 bool CacheStorageService::IsForcedValidEntry(nsACString const& aContextKey, 1145 nsACString const& aEntryKey) { 1146 return IsForcedValidEntry(aContextKey + aEntryKey); 1147 } 1148 1149 bool CacheStorageService::IsForcedValidEntry( 1150 nsACString const& aContextEntryKey) { 1151 mozilla::MutexAutoLock lock(mForcedValidEntriesLock); 1152 1153 ForcedValidData data; 1154 1155 if (!mForcedValidEntries.Get(aContextEntryKey, &data)) { 1156 return false; 1157 } 1158 1159 if (data.validUntil.IsNull()) { 1160 MOZ_ASSERT_UNREACHABLE("the timeStamp should never be null"); 1161 return false; 1162 } 1163 1164 // Entry timeout not reached yet 1165 if (TimeStamp::NowLoRes() <= data.validUntil) { 1166 return true; 1167 } 1168 1169 // Entry timeout has been reached 1170 mForcedValidEntries.Remove(aContextEntryKey); 1171 1172 return false; 1173 } 1174 1175 void CacheStorageService::MarkForcedValidEntryUse(nsACString const& aContextKey, 1176 nsACString const& aEntryKey) { 1177 mozilla::MutexAutoLock lock(mForcedValidEntriesLock); 1178 1179 ForcedValidData data; 1180 1181 if (!mForcedValidEntries.Get(aContextKey + aEntryKey, &data)) { 1182 return; 1183 } 1184 1185 data.viewed = true; 1186 mForcedValidEntries.InsertOrUpdate(aContextKey + aEntryKey, data); 1187 } 1188 1189 // Allows a cache entry to be loaded directly from cache without further 1190 // validation - see nsICacheEntry.idl for further details 1191 void CacheStorageService::ForceEntryValidFor(nsACString const& aContextKey, 1192 nsACString const& aEntryKey, 1193 uint32_t aSecondsToTheFuture) { 1194 mozilla::MutexAutoLock lock(mForcedValidEntriesLock); 1195 1196 TimeStamp now = TimeStamp::NowLoRes(); 1197 ForcedValidEntriesPrune(now); 1198 1199 ForcedValidData data; 1200 data.validUntil = now + TimeDuration::FromSeconds(aSecondsToTheFuture); 1201 data.viewed = false; 1202 1203 mForcedValidEntries.InsertOrUpdate(aContextKey + aEntryKey, data); 1204 } 1205 1206 void CacheStorageService::RemoveEntryForceValid(nsACString const& aContextKey, 1207 nsACString const& aEntryKey) { 1208 mozilla::MutexAutoLock lock(mForcedValidEntriesLock); 1209 1210 LOG(("CacheStorageService::RemoveEntryForceValid context='%s' entryKey=%s", 1211 aContextKey.BeginReading(), aEntryKey.BeginReading())); 1212 mForcedValidEntries.Remove(aContextKey + aEntryKey); 1213 } 1214 1215 // Cleans out the old entries in mForcedValidEntries 1216 void CacheStorageService::ForcedValidEntriesPrune(TimeStamp& now) { 1217 static TimeDuration const oneMinute = TimeDuration::FromSeconds(60); 1218 static TimeStamp dontPruneUntil = now + oneMinute; 1219 if (now < dontPruneUntil) return; 1220 1221 for (auto iter = mForcedValidEntries.Iter(); !iter.Done(); iter.Next()) { 1222 if (iter.Data().validUntil < now) { 1223 iter.Remove(); 1224 } 1225 } 1226 dontPruneUntil = now + oneMinute; 1227 } 1228 1229 void CacheStorageService::OnMemoryConsumptionChange( 1230 CacheMemoryConsumer* aConsumer, uint32_t aCurrentMemoryConsumption) { 1231 LOG(("CacheStorageService::OnMemoryConsumptionChange [consumer=%p, size=%u]", 1232 aConsumer, aCurrentMemoryConsumption)); 1233 1234 uint32_t savedMemorySize = aConsumer->LoadReportedMemoryConsumption(); 1235 if (savedMemorySize == aCurrentMemoryConsumption) return; 1236 1237 // Exchange saved size with current one. 1238 aConsumer->StoreReportedMemoryConsumption(aCurrentMemoryConsumption); 1239 1240 bool usingDisk = !(aConsumer->LoadFlags() & CacheMemoryConsumer::MEMORY_ONLY); 1241 bool overLimit = Pool(usingDisk).OnMemoryConsumptionChange( 1242 savedMemorySize, aCurrentMemoryConsumption); 1243 1244 if (!overLimit) return; 1245 1246 // It's likely the timer has already been set when we get here, 1247 // check outside the lock to save resources. 1248 #ifdef MOZ_TSAN 1249 if (mPurgeTimerActive) { 1250 #else 1251 if (mPurgeTimer) { 1252 #endif 1253 return; 1254 } 1255 1256 // We don't know if this is called under the service lock or not, 1257 // hence rather dispatch. 1258 RefPtr<nsIEventTarget> cacheIOTarget = Thread(); 1259 if (!cacheIOTarget) return; 1260 1261 // Dispatch as a priority task, we want to set the purge timer 1262 // ASAP to prevent vain redispatch of this event. 1263 nsCOMPtr<nsIRunnable> event = NewRunnableMethod( 1264 "net::CacheStorageService::SchedulePurgeOverMemoryLimit", this, 1265 &CacheStorageService::SchedulePurgeOverMemoryLimit); 1266 cacheIOTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); 1267 } 1268 1269 bool CacheStorageService::MemoryPool::OnMemoryConsumptionChange( 1270 uint32_t aSavedMemorySize, uint32_t aCurrentMemoryConsumption) { 1271 mMemorySize -= aSavedMemorySize; 1272 mMemorySize += aCurrentMemoryConsumption; 1273 1274 LOG((" mMemorySize=%u (+%u,-%u)", uint32_t(mMemorySize), 1275 aCurrentMemoryConsumption, aSavedMemorySize)); 1276 1277 // Bypass purging when memory has not grew up significantly 1278 if (aCurrentMemoryConsumption <= aSavedMemorySize) return false; 1279 1280 return mMemorySize > Limit(); 1281 } 1282 1283 void CacheStorageService::SchedulePurgeOverMemoryLimit() { 1284 LOG(("CacheStorageService::SchedulePurgeOverMemoryLimit")); 1285 1286 StaticMutexAutoLock lock(sLock); 1287 1288 if (mShutdown) { 1289 LOG((" past shutdown")); 1290 return; 1291 } 1292 1293 if (mPurgeTimer) { 1294 LOG((" timer already up")); 1295 return; 1296 } 1297 1298 mPurgeTimer = NS_NewTimer(); 1299 if (mPurgeTimer) { 1300 #ifdef MOZ_TSAN 1301 mPurgeTimerActive = true; 1302 #endif 1303 nsresult rv; 1304 rv = mPurgeTimer->InitWithCallback(this, 1000, nsITimer::TYPE_ONE_SHOT); 1305 LOG((" timer init rv=0x%08" PRIx32, static_cast<uint32_t>(rv))); 1306 } 1307 } 1308 1309 NS_IMETHODIMP 1310 CacheStorageService::Notify(nsITimer* aTimer) { 1311 LOG(("CacheStorageService::Notify")); 1312 1313 StaticMutexAutoLock lock(sLock); 1314 1315 if (aTimer == mPurgeTimer) { 1316 #ifdef MOZ_TSAN 1317 mPurgeTimerActive = false; 1318 #endif 1319 mPurgeTimer = nullptr; 1320 1321 if (!mShutdown) { 1322 nsCOMPtr<nsIRunnable> event = NewRunnableMethod( 1323 "net::CacheStorageService::PurgeExpiredOrOverMemoryLimit", this, 1324 &CacheStorageService::PurgeExpiredOrOverMemoryLimit); 1325 Dispatch(event); 1326 } 1327 } 1328 1329 return NS_OK; 1330 } 1331 1332 NS_IMETHODIMP 1333 CacheStorageService::GetName(nsACString& aName) { 1334 aName.AssignLiteral("CacheStorageService"); 1335 return NS_OK; 1336 } 1337 1338 void CacheStorageService::PurgeExpiredOrOverMemoryLimit() { 1339 MOZ_ASSERT(IsOnManagementThread()); 1340 1341 LOG(("CacheStorageService::PurgeExpiredOrOverMemoryLimit")); 1342 1343 if (mShutdown) return; 1344 1345 static TimeDuration const kFourSeconds = TimeDuration::FromSeconds(4); 1346 TimeStamp now = TimeStamp::NowLoRes(); 1347 1348 if (!mLastPurgeTime.IsNull() && now - mLastPurgeTime < kFourSeconds) { 1349 LOG((" bypassed, too soon")); 1350 return; 1351 } 1352 1353 mLastPurgeTime = now; 1354 1355 // We start purging memory entries first as we care more about RAM over 1356 // disk space beeing freed in case we are interrupted. 1357 Pool(MemoryPool::EType::MEMORY).PurgeExpiredOrOverMemoryLimit(); 1358 Pool(MemoryPool::EType::DISK).PurgeExpiredOrOverMemoryLimit(); 1359 } 1360 1361 void CacheStorageService::MemoryPool::PurgeExpiredOrOverMemoryLimit() { 1362 if (StaticPrefs::network_cache_purge_disable()) { 1363 return; 1364 } 1365 TimeStamp start(TimeStamp::Now()); 1366 1367 uint32_t const memoryLimit = Limit(); 1368 size_t minprogress = 1369 (mType == EType::DISK) 1370 ? StaticPrefs::network_cache_purge_minprogress_disk() 1371 : StaticPrefs::network_cache_purge_minprogress_memory(); 1372 1373 // We always purge expired entries, even if under our limit. 1374 size_t numExpired = PurgeExpired(minprogress); 1375 if (numExpired > 0) { 1376 LOG((" found and purged %zu expired entries", numExpired)); 1377 } 1378 minprogress = (minprogress > numExpired) ? minprogress - numExpired : 0; 1379 1380 // If we are still under pressure, purge LFU entries until we aren't. 1381 if (mMemorySize > memoryLimit) { 1382 // Do not enter PurgeByFrecency if we reached the minimum and are asked to 1383 // deliver entries. 1384 if (minprogress == 0 && CacheIOThread::YieldAndRerun()) { 1385 return; 1386 } 1387 1388 auto r = PurgeByFrecency(minprogress); 1389 if (MOZ_LIKELY(r.isOk())) { 1390 size_t numPurged = r.unwrap(); 1391 LOG(( 1392 " memory data consumption over the limit, abandoned %zu LFU entries", 1393 numPurged)); 1394 } else { 1395 // If we hit an error (OOM), do an emergency PurgeAll. 1396 size_t numPurged = PurgeAll(CacheEntry::PURGE_WHOLE, minprogress); 1397 LOG( 1398 (" memory data consumption over the limit, emergency purged all %zu " 1399 "entries", 1400 numPurged)); 1401 } 1402 } 1403 1404 LOG((" purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds())); 1405 } 1406 1407 // This function purges ALL expired entries. 1408 size_t CacheStorageService::MemoryPool::PurgeExpired(size_t minprogress) { 1409 MOZ_ASSERT(IsOnManagementThread()); 1410 1411 uint32_t now = NowInSeconds(); 1412 1413 size_t numPurged = 0; 1414 // Scan for items to purge. mManagedEntries is not sorted but comparing just 1415 // one integer should be faster than anything else, so go scan. 1416 RefPtr<CacheEntry> entry = mManagedEntries.getFirst(); 1417 while (entry) { 1418 // Get the next entry before we may be removed from our list. 1419 RefPtr<CacheEntry> nextEntry = entry->getNext(); 1420 1421 if (entry->GetExpirationTime() <= now) { 1422 // Purge will modify our mManagedEntries list but we are prepared for it. 1423 if (entry->Purge(CacheEntry::PURGE_WHOLE)) { 1424 numPurged++; 1425 LOG((" purged expired, entry=%p, exptime=%u (now=%u)", entry.get(), 1426 entry->GetExpirationTime(), now)); 1427 } 1428 } 1429 1430 entry = std::move(nextEntry); 1431 1432 // To have some progress even under load, we do the check only after 1433 // purging at least minprogress items if under pressure. 1434 if ((numPurged >= minprogress || mMemorySize <= Limit()) && 1435 CacheIOThread::YieldAndRerun()) { 1436 break; 1437 } 1438 } 1439 1440 return numPurged; 1441 } 1442 1443 Result<size_t, nsresult> CacheStorageService::MemoryPool::PurgeByFrecency( 1444 size_t minprogress) { 1445 MOZ_ASSERT(IsOnManagementThread()); 1446 1447 // Pretend the limit is 10% lower so that we get rid of more entries at one 1448 // shot and save the sorting below. 1449 uint32_t const memoryLimit = (uint32_t)(Limit() * 0.9); 1450 if (mMemorySize <= memoryLimit) { 1451 return 0; 1452 } 1453 1454 LOG(("MemoryPool::PurgeByFrecency, len=%zu", mManagedEntries.length())); 1455 1456 // We want to have an array snapshot for sorting and iterating. 1457 struct mayPurgeEntry { 1458 RefPtr<CacheEntry> mEntry; 1459 double mFrecency; 1460 1461 explicit mayPurgeEntry(CacheEntry* aEntry) { 1462 mEntry = aEntry; 1463 mFrecency = aEntry->GetFrecency(); 1464 } 1465 1466 bool operator<(const mayPurgeEntry& aOther) const { 1467 return mFrecency < aOther.mFrecency; 1468 } 1469 }; 1470 1471 nsTArray<mayPurgeEntry> mayPurgeSorted; 1472 if (!mayPurgeSorted.SetCapacity(mManagedEntries.length(), 1473 mozilla::fallible)) { 1474 return Err(NS_ERROR_OUT_OF_MEMORY); 1475 } 1476 { 1477 StaticMutexAutoLock lock(CacheStorageService::Self()->Lock()); 1478 1479 for (const auto& entry : mManagedEntries) { 1480 // Referenced items cannot be purged and we deliberately want to not 1481 // look at '0' frecency entries, these are new entries and can be 1482 // ignored. Also, any dict: (CompressionDictionary) entries for an 1483 // origin should not be purged unless empty - they will empty out as 1484 // the cache entries referenced by them are purged until they are empty. 1485 if (!entry->IsReferenced() && entry->GetFrecency() > 0.0 && 1486 (!entry->GetEnhanceID().EqualsLiteral("dict:") || 1487 entry->GetMetadataMemoryConsumption() == 0)) { 1488 mayPurgeEntry copy(entry); 1489 mayPurgeSorted.AppendElement(std::move(copy)); 1490 } else { 1491 if (entry->GetEnhanceID().EqualsLiteral("dict:")) { 1492 LOG( 1493 ("*** Ignored Entry is a dictionary origin, metadata size %d, " 1494 "referenced %d, Frecency %f", 1495 entry->GetMetadataMemoryConsumption(), entry->IsReferenced(), 1496 entry->GetFrecency())); 1497 } 1498 } 1499 } 1500 } 1501 if (mayPurgeSorted.Length() == 0) { 1502 return 0; 1503 } 1504 mayPurgeSorted.Sort(); 1505 1506 size_t numPurged = 0; 1507 1508 for (auto& checkPurge : mayPurgeSorted) { 1509 if (mMemorySize <= memoryLimit) { 1510 break; 1511 } 1512 1513 // Ensure it's deleted immediately if purged so we can record the 1514 // mMemorySize savings 1515 RefPtr<CacheEntry> entry = std::move(checkPurge.mEntry); 1516 1517 if (entry->Purge(CacheEntry::PURGE_WHOLE)) { 1518 numPurged++; 1519 LOG((" abandoned (%d), entry=%p, frecency=%1.10f", 1520 CacheEntry::PURGE_WHOLE, entry.get(), entry->GetFrecency())); 1521 } 1522 1523 if (numPurged >= minprogress && CacheIOThread::YieldAndRerun()) { 1524 LOG(("MemoryPool::PurgeByFrecency interrupted")); 1525 return numPurged; 1526 } 1527 } 1528 1529 LOG( 1530 ("MemoryPool::PurgeByFrecency done, purged %zu - mMemorySize %u, " 1531 "memoryLimit %u", 1532 numPurged, (uint32_t)mMemorySize, memoryLimit)); 1533 1534 return numPurged; 1535 } 1536 1537 size_t CacheStorageService::MemoryPool::PurgeAll(uint32_t aWhat, 1538 size_t minprogress) { 1539 LOG(("CacheStorageService::MemoryPool::PurgeAll aWhat=%d", aWhat)); 1540 MOZ_ASSERT(IsOnManagementThread()); 1541 1542 size_t numPurged = 0; 1543 1544 RefPtr<CacheEntry> entry = mManagedEntries.getFirst(); 1545 while (entry) { 1546 if (numPurged >= minprogress && CacheIOThread::YieldAndRerun()) break; 1547 1548 // Get the next entry before we may be removed from our list. 1549 RefPtr<CacheEntry> nextEntry = entry->getNext(); 1550 1551 if (entry->Purge(aWhat)) { 1552 numPurged++; 1553 LOG((" abandoned entry=%p", entry.get())); 1554 } 1555 1556 entry = std::move(nextEntry); 1557 } 1558 1559 return numPurged; 1560 } 1561 1562 // Methods exposed to and used by CacheStorage. 1563 1564 nsresult CacheStorageService::AddStorageEntry(CacheStorage const* aStorage, 1565 const nsACString& aURI, 1566 const nsACString& aIdExtension, 1567 uint32_t aFlags, 1568 CacheEntryHandle** aResult) { 1569 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); 1570 1571 NS_ENSURE_ARG(aStorage); 1572 1573 nsAutoCString contextKey; 1574 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey); 1575 1576 return AddStorageEntry(contextKey, aURI, aIdExtension, 1577 aStorage->WriteToDisk(), aStorage->SkipSizeCheck(), 1578 aStorage->Pinning(), aFlags, aResult); 1579 } 1580 1581 nsresult CacheStorageService::AddStorageEntry( 1582 const nsACString& aContextKey, const nsACString& aURI, 1583 const nsACString& aIdExtension, bool aWriteToDisk, bool aSkipSizeCheck, 1584 bool aPin, uint32_t aFlags, CacheEntryHandle** aResult) { 1585 nsresult rv; 1586 1587 nsAutoCString entryKey; 1588 rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey); 1589 NS_ENSURE_SUCCESS(rv, rv); 1590 1591 LOG(("CacheStorageService::AddStorageEntry [entryKey=%s, contextKey=%s]", 1592 entryKey.get(), aContextKey.BeginReading())); 1593 1594 RefPtr<CacheEntry> entry; 1595 RefPtr<CacheEntryHandle> handle; 1596 1597 { 1598 StaticMutexAutoLock lock(sLock); 1599 1600 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); 1601 1602 // Ensure storage table 1603 CacheEntryTable* const entries = 1604 sGlobalEntryTables 1605 ->LookupOrInsertWith( 1606 aContextKey, 1607 [&aContextKey] { 1608 LOG((" new storage entries table for context '%s'", 1609 aContextKey.BeginReading())); 1610 return MakeUnique<CacheEntryTable>( 1611 CacheEntryTable::ALL_ENTRIES); 1612 }) 1613 .get(); 1614 1615 bool entryExists = entries->Get(entryKey, getter_AddRefs(entry)); 1616 if (!entryExists && (aFlags & nsICacheStorage::OPEN_READONLY) && 1617 (aFlags & nsICacheStorage::OPEN_SECRETLY) && 1618 StaticPrefs::network_cache_bug1708673()) { 1619 return NS_ERROR_CACHE_KEY_NOT_FOUND; 1620 } 1621 if (entryExists && (aFlags & nsICacheStorage::OPEN_COMPLETE_ONLY)) { 1622 bool ready = false; 1623 // We're looking for complete files, even if they're being revalidated 1624 // (for dictionaries) 1625 entry->GetReadyOrRevalidating(&ready); 1626 if (!ready) { 1627 return NS_ERROR_CACHE_KEY_NOT_FOUND; 1628 } 1629 } 1630 1631 bool replace = aFlags & nsICacheStorage::OPEN_TRUNCATE; 1632 1633 if (entryExists && !replace) { 1634 // check whether we want to turn this entry to a memory-only. 1635 if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) { 1636 LOG((" entry is persistent but we want mem-only, replacing it")); 1637 replace = true; 1638 } 1639 } 1640 1641 // If truncate is demanded, delete and doom the current entry 1642 if (entryExists && replace) { 1643 entries->Remove(entryKey); 1644 1645 LOG((" dooming entry %p for %s because of OPEN_TRUNCATE", entry.get(), 1646 entryKey.get())); 1647 // On purpose called under the lock to prevent races of doom and open on 1648 // I/O thread No need to remove from both memory-only and all-entries 1649 // tables. The new entry will overwrite the shadow entry in its ctor. 1650 entry->DoomAlreadyRemoved(); 1651 1652 entry = nullptr; 1653 entryExists = false; 1654 1655 // Would only lead to deleting force-valid timestamp again. We don't need 1656 // the replace information anymore after this point anyway. 1657 replace = false; 1658 } 1659 1660 // Ensure entry for the particular URL 1661 if (!entryExists) { 1662 // When replacing with a new entry, always remove the current force-valid 1663 // timestamp, this is the only place to do it. 1664 if (replace) { 1665 RemoveEntryForceValid(aContextKey, entryKey); 1666 } 1667 1668 // Entry is not in the hashtable or has just been truncated... 1669 entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk, 1670 aSkipSizeCheck, aPin); 1671 entries->InsertOrUpdate(entryKey, RefPtr{entry}); 1672 LOG((" new entry %p for %s", entry.get(), entryKey.get())); 1673 } 1674 1675 if (entry) { 1676 // Here, if this entry was not for a long time referenced by any consumer, 1677 // gets again first 'handles count' reference. 1678 handle = entry->NewHandle(); 1679 } 1680 } 1681 1682 handle.forget(aResult); 1683 return NS_OK; 1684 } 1685 1686 nsresult CacheStorageService::CheckStorageEntry(CacheStorage const* aStorage, 1687 const nsACString& aURI, 1688 const nsACString& aIdExtension, 1689 bool* aResult) { 1690 nsresult rv; 1691 1692 nsAutoCString contextKey; 1693 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey); 1694 1695 if (!aStorage->WriteToDisk()) { 1696 AppendMemoryStorageTag(contextKey); 1697 } 1698 1699 LOG(("CacheStorageService::CheckStorageEntry [uri=%s, eid=%s, contextKey=%s]", 1700 aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get())); 1701 1702 { 1703 StaticMutexAutoLock lock(sLock); 1704 1705 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); 1706 1707 nsAutoCString entryKey; 1708 rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey); 1709 NS_ENSURE_SUCCESS(rv, rv); 1710 1711 CacheEntryTable* entries; 1712 if ((*aResult = sGlobalEntryTables->Get(contextKey, &entries)) && 1713 entries->GetWeak(entryKey, aResult)) { 1714 LOG((" found in hash tables")); 1715 return NS_OK; 1716 } 1717 } 1718 1719 if (!aStorage->WriteToDisk()) { 1720 // Memory entry, nothing more to do. 1721 LOG((" not found in hash tables")); 1722 return NS_OK; 1723 } 1724 1725 // Disk entry, not found in the hashtable, check the index. 1726 nsAutoCString fileKey; 1727 rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey); 1728 1729 CacheIndex::EntryStatus status; 1730 rv = CacheIndex::HasEntry(fileKey, &status); 1731 if (NS_FAILED(rv) || status == CacheIndex::DO_NOT_KNOW) { 1732 LOG((" index doesn't know, rv=0x%08" PRIx32, static_cast<uint32_t>(rv))); 1733 return NS_ERROR_NOT_AVAILABLE; 1734 } 1735 1736 *aResult = status == CacheIndex::EXISTS; 1737 LOG((" %sfound in index", *aResult ? "" : "not ")); 1738 return NS_OK; 1739 } 1740 1741 nsresult CacheStorageService::GetCacheIndexEntryAttrs( 1742 CacheStorage const* aStorage, const nsACString& aURI, 1743 const nsACString& aIdExtension, bool* aHasAltData, uint32_t* aFileSizeKb) { 1744 nsresult rv; 1745 1746 nsAutoCString contextKey; 1747 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey); 1748 1749 LOG( 1750 ("CacheStorageService::GetCacheIndexEntryAttrs [uri=%s, eid=%s, " 1751 "contextKey=%s]", 1752 aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get())); 1753 1754 nsAutoCString fileKey; 1755 rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey); 1756 if (NS_FAILED(rv)) { 1757 return rv; 1758 } 1759 1760 *aHasAltData = false; 1761 *aFileSizeKb = 0; 1762 auto closure = [&aHasAltData, &aFileSizeKb](const CacheIndexEntry* entry) { 1763 *aHasAltData = entry->GetHasAltData(); 1764 *aFileSizeKb = entry->GetFileSize(); 1765 }; 1766 1767 CacheIndex::EntryStatus status; 1768 rv = CacheIndex::HasEntry(fileKey, &status, closure); 1769 if (NS_FAILED(rv)) { 1770 return rv; 1771 } 1772 1773 if (status != CacheIndex::EXISTS) { 1774 return NS_ERROR_CACHE_KEY_NOT_FOUND; 1775 } 1776 1777 return NS_OK; 1778 } 1779 1780 namespace { 1781 1782 class CacheEntryDoomByKeyCallback : public CacheFileIOListener, 1783 public nsIRunnable { 1784 public: 1785 NS_DECL_THREADSAFE_ISUPPORTS 1786 NS_DECL_NSIRUNNABLE 1787 1788 explicit CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback* aCallback) 1789 : mCallback(aCallback), mResult(NS_ERROR_NOT_INITIALIZED) {} 1790 1791 private: 1792 virtual ~CacheEntryDoomByKeyCallback(); 1793 1794 NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override { 1795 return NS_OK; 1796 } 1797 NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf, 1798 nsresult aResult) override { 1799 return NS_OK; 1800 } 1801 NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf, 1802 nsresult aResult) override { 1803 return NS_OK; 1804 } 1805 NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override; 1806 NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override { 1807 return NS_OK; 1808 } 1809 NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle, 1810 nsresult aResult) override { 1811 return NS_OK; 1812 } 1813 1814 nsCOMPtr<nsICacheEntryDoomCallback> mCallback; 1815 nsresult mResult; 1816 }; 1817 1818 CacheEntryDoomByKeyCallback::~CacheEntryDoomByKeyCallback() { 1819 if (mCallback) { 1820 ProxyReleaseMainThread("CacheEntryDoomByKeyCallback::mCallback", mCallback); 1821 } 1822 } 1823 1824 NS_IMETHODIMP CacheEntryDoomByKeyCallback::OnFileDoomed( 1825 CacheFileHandle* aHandle, nsresult aResult) { 1826 if (!mCallback) return NS_OK; 1827 1828 mResult = aResult; 1829 if (NS_IsMainThread()) { 1830 Run(); 1831 } else { 1832 NS_DispatchToMainThread(this); 1833 } 1834 1835 return NS_OK; 1836 } 1837 1838 NS_IMETHODIMP CacheEntryDoomByKeyCallback::Run() { 1839 mCallback->OnCacheEntryDoomed(mResult); 1840 return NS_OK; 1841 } 1842 1843 NS_IMPL_ISUPPORTS(CacheEntryDoomByKeyCallback, CacheFileIOListener, 1844 nsIRunnable); 1845 1846 } // namespace 1847 1848 nsresult CacheStorageService::DoomStorageEntry( 1849 CacheStorage const* aStorage, const nsACString& aURI, 1850 const nsACString& aIdExtension, nsICacheEntryDoomCallback* aCallback) { 1851 LOG(("CacheStorageService::DoomStorageEntry %s", 1852 PromiseFlatCString(aURI).get())); 1853 1854 NS_ENSURE_ARG(aStorage); 1855 1856 nsAutoCString contextKey; 1857 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey); 1858 1859 nsAutoCString entryKey; 1860 nsresult rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey); 1861 NS_ENSURE_SUCCESS(rv, rv); 1862 1863 RefPtr<CacheEntry> entry; 1864 { 1865 StaticMutexAutoLock lock(sLock); 1866 1867 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); 1868 1869 CacheEntryTable* entries; 1870 if (sGlobalEntryTables->Get(contextKey, &entries)) { 1871 if (entries->Get(entryKey, getter_AddRefs(entry))) { 1872 if (aStorage->WriteToDisk() || !entry->IsUsingDisk()) { 1873 // When evicting from disk storage, purge 1874 // When evicting from memory storage and the entry is memory-only, 1875 // purge 1876 LOG( 1877 (" purging entry %p for %s [storage use disk=%d, entry use " 1878 "disk=%d]", 1879 entry.get(), entryKey.get(), aStorage->WriteToDisk(), 1880 entry->IsUsingDisk())); 1881 entries->Remove(entryKey); 1882 } else { 1883 // Otherwise, leave it 1884 LOG( 1885 (" leaving entry %p for %s [storage use disk=%d, entry use " 1886 "disk=%d]", 1887 entry.get(), entryKey.get(), aStorage->WriteToDisk(), 1888 entry->IsUsingDisk())); 1889 entry = nullptr; 1890 } 1891 } 1892 } 1893 1894 if (!entry) { 1895 RemoveEntryForceValid(contextKey, entryKey); 1896 } 1897 } 1898 1899 if (entry) { 1900 LOG((" dooming entry %p for %s", entry.get(), entryKey.get())); 1901 return entry->AsyncDoom(aCallback); 1902 } 1903 1904 LOG((" no entry loaded for %s", entryKey.get())); 1905 1906 if (aStorage->WriteToDisk()) { 1907 nsAutoCString contextKey; 1908 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey); 1909 1910 rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, entryKey); 1911 NS_ENSURE_SUCCESS(rv, rv); 1912 1913 LOG((" dooming file only for %s", entryKey.get())); 1914 1915 RefPtr<CacheEntryDoomByKeyCallback> callback( 1916 new CacheEntryDoomByKeyCallback(aCallback)); 1917 rv = CacheFileIOManager::DoomFileByKey(entryKey, callback); 1918 NS_ENSURE_SUCCESS(rv, rv); 1919 1920 return NS_OK; 1921 } 1922 1923 class Callback : public Runnable { 1924 public: 1925 explicit Callback(nsICacheEntryDoomCallback* aCallback) 1926 : mozilla::Runnable("Callback"), mCallback(aCallback) {} 1927 NS_IMETHOD Run() override { 1928 mCallback->OnCacheEntryDoomed(NS_ERROR_NOT_AVAILABLE); 1929 return NS_OK; 1930 } 1931 nsCOMPtr<nsICacheEntryDoomCallback> mCallback; 1932 }; 1933 1934 if (aCallback) { 1935 RefPtr<Runnable> callback = new Callback(aCallback); 1936 return NS_DispatchToMainThread(callback); 1937 } 1938 1939 return NS_OK; 1940 } 1941 1942 nsresult CacheStorageService::DoomStorageEntries( 1943 CacheStorage const* aStorage, nsICacheEntryDoomCallback* aCallback) { 1944 LOG(("CacheStorageService::DoomStorageEntries")); 1945 1946 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); 1947 NS_ENSURE_ARG(aStorage); 1948 1949 nsAutoCString contextKey; 1950 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey); 1951 1952 StaticMutexAutoLock lock(sLock); 1953 1954 return DoomStorageEntries(contextKey, aStorage->LoadInfo(), 1955 aStorage->WriteToDisk(), aStorage->Pinning(), 1956 aCallback); 1957 } 1958 1959 nsresult CacheStorageService::DoomStorageEntries( 1960 const nsACString& aContextKey, nsILoadContextInfo* aContext, 1961 bool aDiskStorage, bool aPinned, nsICacheEntryDoomCallback* aCallback) { 1962 LOG(("CacheStorageService::DoomStorageEntries [context=%s]", 1963 aContextKey.BeginReading())); 1964 1965 sLock.AssertCurrentThreadOwns(); 1966 1967 NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED); 1968 1969 nsAutoCString memoryStorageID(aContextKey); 1970 AppendMemoryStorageTag(memoryStorageID); 1971 1972 if (aDiskStorage) { 1973 LOG((" dooming disk+memory storage of %s", aContextKey.BeginReading())); 1974 1975 // Walk one by one and remove entries according their pin status 1976 CacheEntryTable *diskEntries, *memoryEntries; 1977 if (sGlobalEntryTables->Get(aContextKey, &diskEntries)) { 1978 sGlobalEntryTables->Get(memoryStorageID, &memoryEntries); 1979 1980 for (auto iter = diskEntries->Iter(); !iter.Done(); iter.Next()) { 1981 auto entry = iter.Data(); 1982 if (entry->DeferOrBypassRemovalOnPinStatus(aPinned)) { 1983 continue; 1984 } 1985 1986 if (memoryEntries) { 1987 RemoveExactEntry(memoryEntries, iter.Key(), entry, false); 1988 } 1989 iter.Remove(); 1990 } 1991 } 1992 1993 if (aContext && !aContext->IsPrivate()) { 1994 LOG((" dooming disk entries")); 1995 CacheFileIOManager::EvictByContext(aContext, aPinned, u""_ns); 1996 } 1997 } else { 1998 LOG((" dooming memory-only storage of %s", aContextKey.BeginReading())); 1999 2000 // Remove the memory entries table from the global tables. 2001 // Since we store memory entries also in the disk entries table 2002 // we need to remove the memory entries from the disk table one 2003 // by one manually. 2004 mozilla::UniquePtr<CacheEntryTable> memoryEntries; 2005 sGlobalEntryTables->Remove(memoryStorageID, &memoryEntries); 2006 2007 CacheEntryTable* diskEntries; 2008 if (memoryEntries && sGlobalEntryTables->Get(aContextKey, &diskEntries)) { 2009 for (const auto& memoryEntry : *memoryEntries) { 2010 const auto& entry = memoryEntry.GetData(); 2011 RemoveExactEntry(diskEntries, memoryEntry.GetKey(), entry, false); 2012 } 2013 } 2014 } 2015 2016 { 2017 mozilla::MutexAutoLock lock(mForcedValidEntriesLock); 2018 2019 if (aContext) { 2020 for (auto iter = mForcedValidEntries.Iter(); !iter.Done(); iter.Next()) { 2021 bool matches; 2022 DebugOnly<nsresult> rv = CacheFileUtils::KeyMatchesLoadContextInfo( 2023 iter.Key(), aContext, &matches); 2024 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2025 2026 if (matches) { 2027 iter.Remove(); 2028 } 2029 } 2030 } else { 2031 mForcedValidEntries.Clear(); 2032 } 2033 } 2034 2035 // An artificial callback. This is a candidate for removal tho. In the new 2036 // cache any 'doom' or 'evict' function ensures that the entry or entries 2037 // being doomed is/are not accessible after the function returns. So there is 2038 // probably no need for a callback - has no meaning. But for compatibility 2039 // with the old cache that is still in the tree we keep the API similar to be 2040 // able to make tests as well as other consumers work for now. 2041 class Callback : public Runnable { 2042 public: 2043 explicit Callback(nsICacheEntryDoomCallback* aCallback) 2044 : mozilla::Runnable("Callback"), mCallback(aCallback) {} 2045 NS_IMETHOD Run() override { 2046 mCallback->OnCacheEntryDoomed(NS_OK); 2047 return NS_OK; 2048 } 2049 nsCOMPtr<nsICacheEntryDoomCallback> mCallback; 2050 }; 2051 2052 if (aCallback) { 2053 RefPtr<Runnable> callback = new Callback(aCallback); 2054 return NS_DispatchToMainThread(callback); 2055 } 2056 2057 return NS_OK; 2058 } 2059 2060 nsresult CacheStorageService::WalkStorageEntries( 2061 CacheStorage const* aStorage, bool aVisitEntries, 2062 nsICacheStorageVisitor* aVisitor) { 2063 LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]", 2064 aVisitor, aVisitEntries)); 2065 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); 2066 2067 NS_ENSURE_ARG(aStorage); 2068 2069 if (aStorage->WriteToDisk()) { 2070 RefPtr<CacheStorageServiceInternal::WalkDiskCacheRunnable> event = 2071 new CacheStorageServiceInternal::WalkDiskCacheRunnable( 2072 aStorage->LoadInfo(), aVisitEntries, aVisitor); 2073 return event->Walk(); 2074 } 2075 2076 RefPtr<CacheStorageServiceInternal::WalkMemoryCacheRunnable> event = 2077 new CacheStorageServiceInternal::WalkMemoryCacheRunnable( 2078 aStorage->LoadInfo(), aVisitEntries, aVisitor); 2079 return event->Walk(); 2080 } 2081 2082 void CacheStorageService::CacheFileDoomed(const nsACString& aKey, 2083 nsILoadContextInfo* aLoadContextInfo, 2084 const nsACString& aIdExtension, 2085 const nsACString& aURISpec) { 2086 nsAutoCString contextKey; 2087 CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey); 2088 2089 nsAutoCString entryKey; 2090 CacheEntry::HashingKey(""_ns, aIdExtension, aURISpec, entryKey); 2091 2092 StaticMutexAutoLock lock(sLock); 2093 2094 if (mShutdown) { 2095 return; 2096 } 2097 2098 CacheEntryTable* entries; 2099 RefPtr<CacheEntry> entry; 2100 2101 if (sGlobalEntryTables->Get(contextKey, &entries) && 2102 entries->Get(entryKey, getter_AddRefs(entry))) { 2103 if (entry->IsFileDoomed()) { 2104 // Need to remove under the lock to avoid possible race leading 2105 // to duplication of the entry per its key. 2106 RemoveExactEntry(entries, entryKey, entry, false); 2107 entry->DoomAlreadyRemoved(); 2108 } 2109 2110 // Entry found, but it's not the entry that has been found doomed 2111 // by the lower eviction layer. Just leave everything unchanged. 2112 return; 2113 } 2114 2115 RemoveEntryForceValid(contextKey, entryKey); 2116 } 2117 2118 bool CacheStorageService::GetCacheEntryInfo( 2119 nsILoadContextInfo* aLoadContextInfo, const nsACString& aIdExtension, 2120 const nsACString& aURISpec, EntryInfoCallback* aCallback) { 2121 nsAutoCString contextKey; 2122 CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey); 2123 2124 nsAutoCString entryKey; 2125 CacheEntry::HashingKey(""_ns, aIdExtension, aURISpec, entryKey); 2126 2127 RefPtr<CacheEntry> entry; 2128 { 2129 StaticMutexAutoLock lock(sLock); 2130 2131 if (mShutdown) { 2132 return false; 2133 } 2134 2135 CacheEntryTable* entries; 2136 if (!sGlobalEntryTables->Get(contextKey, &entries)) { 2137 return false; 2138 } 2139 2140 if (!entries->Get(entryKey, getter_AddRefs(entry))) { 2141 return false; 2142 } 2143 } 2144 2145 GetCacheEntryInfo(entry, aCallback); 2146 return true; 2147 } 2148 2149 // static 2150 void CacheStorageService::GetCacheEntryInfo(CacheEntry* aEntry, 2151 EntryInfoCallback* aCallback) { 2152 nsCString const uriSpec = aEntry->GetURI(); 2153 nsCString const enhanceId = aEntry->GetEnhanceID(); 2154 2155 nsAutoCString entryKey; 2156 aEntry->HashingKeyWithStorage(entryKey); 2157 2158 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(entryKey); 2159 2160 uint32_t dataSize; 2161 if (NS_FAILED(aEntry->GetStorageDataSize(&dataSize))) { 2162 dataSize = 0; 2163 } 2164 int64_t altDataSize; 2165 if (NS_FAILED(aEntry->GetAltDataSize(&altDataSize))) { 2166 altDataSize = 0; 2167 } 2168 uint32_t fetchCount; 2169 if (NS_FAILED(aEntry->GetFetchCount(&fetchCount))) { 2170 fetchCount = 0; 2171 } 2172 uint32_t lastModified; 2173 if (NS_FAILED(aEntry->GetLastModified(&lastModified))) { 2174 lastModified = 0; 2175 } 2176 uint32_t expirationTime; 2177 if (NS_FAILED(aEntry->GetExpirationTime(&expirationTime))) { 2178 expirationTime = 0; 2179 } 2180 2181 aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, altDataSize, fetchCount, 2182 lastModified, expirationTime, aEntry->IsPinned(), 2183 info); 2184 } 2185 2186 // static 2187 uint32_t CacheStorageService::CacheQueueSize(bool highPriority) { 2188 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread(); 2189 // The thread will be null at shutdown. 2190 if (!thread) { 2191 return 0; 2192 } 2193 return thread->QueueSize(highPriority); 2194 } 2195 2196 // Telemetry collection 2197 2198 namespace { 2199 2200 bool TelemetryEntryKey(CacheEntry const* entry, nsAutoCString& key) { 2201 nsAutoCString entryKey; 2202 nsresult rv = entry->HashingKey(entryKey); 2203 if (NS_FAILED(rv)) return false; 2204 2205 if (entry->GetStorageID().IsEmpty()) { 2206 // Hopefully this will be const-copied, saves some memory 2207 key = entryKey; 2208 } else { 2209 key.Assign(entry->GetStorageID()); 2210 key.Append(':'); 2211 key.Append(entryKey); 2212 } 2213 2214 return true; 2215 } 2216 2217 } // namespace 2218 2219 void CacheStorageService::TelemetryPrune(TimeStamp& now) { 2220 static TimeDuration const oneMinute = TimeDuration::FromSeconds(60); 2221 static TimeStamp dontPruneUntil = now + oneMinute; 2222 if (now < dontPruneUntil) return; 2223 2224 static TimeDuration const fifteenMinutes = TimeDuration::FromSeconds(900); 2225 for (auto iter = mPurgeTimeStamps.Iter(); !iter.Done(); iter.Next()) { 2226 if (now - iter.Data() > fifteenMinutes) { 2227 // We are not interested in resurrection of entries after 15 minutes 2228 // of time. This is also the limit for the telemetry. 2229 iter.Remove(); 2230 } 2231 } 2232 dontPruneUntil = now + oneMinute; 2233 } 2234 2235 void CacheStorageService::TelemetryRecordEntryCreation( 2236 CacheEntry const* entry) { 2237 MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 2238 2239 nsAutoCString key; 2240 if (!TelemetryEntryKey(entry, key)) return; 2241 2242 TimeStamp now = TimeStamp::NowLoRes(); 2243 TelemetryPrune(now); 2244 2245 // When an entry is craeted (registered actually) we check if there is 2246 // a timestamp marked when this very same cache entry has been removed 2247 // (deregistered) because of over-memory-limit purging. If there is such 2248 // a timestamp found accumulate telemetry on how long the entry was away. 2249 TimeStamp timeStamp; 2250 if (!mPurgeTimeStamps.Get(key, &timeStamp)) return; 2251 2252 mPurgeTimeStamps.Remove(key); 2253 2254 glean::network::http_cache_entry_reload_time.AccumulateRawDuration( 2255 TimeStamp::NowLoRes() - timeStamp); 2256 } 2257 2258 void CacheStorageService::TelemetryRecordEntryRemoval(CacheEntry* entry) { 2259 MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 2260 2261 // Doomed entries must not be considered, we are only interested in purged 2262 // entries. Note that the mIsDoomed flag is always set before deregistration 2263 // happens. 2264 if (entry->IsDoomed()) return; 2265 2266 nsAutoCString key; 2267 if (!TelemetryEntryKey(entry, key)) return; 2268 2269 // When an entry is removed (deregistered actually) we put a timestamp for 2270 // this entry to the hashtable so that when the entry is created (registered) 2271 // again we know how long it was away. Also accumulate number of AsyncOpen 2272 // calls on the entry, this tells us how efficiently the pool actually works. 2273 2274 TimeStamp now = TimeStamp::NowLoRes(); 2275 TelemetryPrune(now); 2276 mPurgeTimeStamps.InsertOrUpdate(key, now); 2277 2278 glean::network::http_cache_entry_reuse_count.AccumulateSingleSample( 2279 entry->UseCount()); 2280 if (Telemetry::CanRecordPrereleaseData()) { 2281 glean::network::http_cache_entry_alive_time.AccumulateRawDuration( 2282 TimeStamp::NowLoRes() - entry->LoadStart()); 2283 } 2284 } 2285 2286 // nsIMemoryReporter 2287 2288 size_t CacheStorageService::SizeOfExcludingThis( 2289 mozilla::MallocSizeOf mallocSizeOf) const { 2290 sLock.AssertCurrentThreadOwns(); 2291 2292 size_t n = 0; 2293 // The elemets are referenced by sGlobalEntryTables and are reported from 2294 // there. 2295 2296 // Entries reported manually in CacheStorageService::CollectReports callback 2297 if (sGlobalEntryTables) { 2298 n += sGlobalEntryTables->ShallowSizeOfIncludingThis(mallocSizeOf); 2299 } 2300 n += mPurgeTimeStamps.SizeOfExcludingThis(mallocSizeOf); 2301 2302 return n; 2303 } 2304 2305 size_t CacheStorageService::SizeOfIncludingThis( 2306 mozilla::MallocSizeOf mallocSizeOf) const { 2307 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); 2308 } 2309 2310 NS_IMETHODIMP 2311 CacheStorageService::CollectReports(nsIHandleReportCallback* aHandleReport, 2312 nsISupports* aData, bool aAnonymize) { 2313 StaticMutexAutoLock lock(sLock); 2314 MOZ_COLLECT_REPORT("explicit/network/cache2/io", KIND_HEAP, UNITS_BYTES, 2315 CacheFileIOManager::SizeOfIncludingThis(MallocSizeOf), 2316 "Memory used by the cache IO manager."); 2317 2318 MOZ_COLLECT_REPORT("explicit/network/cache2/index", KIND_HEAP, UNITS_BYTES, 2319 CacheIndex::SizeOfIncludingThis(MallocSizeOf), 2320 "Memory used by the cache index."); 2321 2322 // Report the service instance, this doesn't report entries, done lower 2323 MOZ_COLLECT_REPORT("explicit/network/cache2/service", KIND_HEAP, UNITS_BYTES, 2324 SizeOfIncludingThis(MallocSizeOf), 2325 "Memory used by the cache storage service."); 2326 2327 // Report all entries, each storage separately (by the context key) 2328 // 2329 // References are: 2330 // sGlobalEntryTables to N CacheEntryTable 2331 // CacheEntryTable to N CacheEntry 2332 // CacheEntry to 1 CacheFile 2333 // CacheFile to 2334 // N CacheFileChunk (keeping the actual data) 2335 // 1 CacheFileMetadata (keeping http headers etc.) 2336 // 1 CacheFileOutputStream 2337 // N CacheFileInputStream 2338 if (sGlobalEntryTables) { 2339 for (const auto& globalEntry : *sGlobalEntryTables) { 2340 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); 2341 2342 CacheEntryTable* table = globalEntry.GetWeak(); 2343 2344 size_t size = 0; 2345 mozilla::MallocSizeOf mallocSizeOf = CacheStorageService::MallocSizeOf; 2346 2347 size += table->ShallowSizeOfIncludingThis(mallocSizeOf); 2348 for (const auto& tableEntry : *table) { 2349 size += tableEntry.GetKey().SizeOfExcludingThisIfUnshared(mallocSizeOf); 2350 2351 // Bypass memory-only entries, those will be reported when iterating the 2352 // memory only table. Memory-only entries are stored in both ALL_ENTRIES 2353 // and MEMORY_ONLY hashtables. 2354 RefPtr<mozilla::net::CacheEntry> const& entry = tableEntry.GetData(); 2355 if (table->Type() == CacheEntryTable::MEMORY_ONLY || 2356 entry->IsUsingDisk()) { 2357 size += entry->SizeOfIncludingThis(mallocSizeOf); 2358 } 2359 } 2360 2361 aHandleReport->Callback( 2362 ""_ns, 2363 nsPrintfCString( 2364 "explicit/network/cache2/%s-storage(%s)", 2365 table->Type() == CacheEntryTable::MEMORY_ONLY ? "memory" : "disk", 2366 aAnonymize ? "<anonymized>" 2367 : globalEntry.GetKey().BeginReading()), 2368 nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, size, 2369 "Memory used by the cache storage."_ns, aData); 2370 } 2371 } 2372 2373 return NS_OK; 2374 } 2375 2376 // nsICacheTesting 2377 2378 NS_IMETHODIMP 2379 CacheStorageService::IOThreadSuspender::Run() { 2380 MonitorAutoLock mon(mMon); 2381 while (!mSignaled) { 2382 mon.Wait(); 2383 } 2384 return NS_OK; 2385 } 2386 2387 void CacheStorageService::IOThreadSuspender::Notify() { 2388 MonitorAutoLock mon(mMon); 2389 mSignaled = true; 2390 mon.Notify(); 2391 } 2392 2393 NS_IMETHODIMP 2394 CacheStorageService::SuspendCacheIOThread(uint32_t aLevel) { 2395 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread(); 2396 if (!thread) { 2397 return NS_ERROR_NOT_AVAILABLE; 2398 } 2399 2400 MOZ_ASSERT(!mActiveIOSuspender); 2401 mActiveIOSuspender = new IOThreadSuspender(); 2402 return thread->Dispatch(mActiveIOSuspender, aLevel); 2403 } 2404 2405 NS_IMETHODIMP 2406 CacheStorageService::ResumeCacheIOThread() { 2407 MOZ_ASSERT(mActiveIOSuspender); 2408 2409 RefPtr<IOThreadSuspender> suspender; 2410 suspender.swap(mActiveIOSuspender); 2411 suspender->Notify(); 2412 return NS_OK; 2413 } 2414 2415 NS_IMETHODIMP 2416 CacheStorageService::Flush(nsIObserver* aObserver) { 2417 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread(); 2418 if (!thread) { 2419 return NS_ERROR_NOT_AVAILABLE; 2420 } 2421 2422 nsCOMPtr<nsIObserverService> observerService = 2423 mozilla::services::GetObserverService(); 2424 if (!observerService) { 2425 return NS_ERROR_NOT_AVAILABLE; 2426 } 2427 2428 // Adding as weak, the consumer is responsible to keep the reference 2429 // until notified. 2430 observerService->AddObserver(aObserver, "cacheservice:purge-memory-pools", 2431 false); 2432 2433 // This runnable will do the purging and when done, notifies the above 2434 // observer. We dispatch it to the CLOSE level, so all data writes scheduled 2435 // up to this time will be done before this purging happens. 2436 RefPtr<CacheStorageService::PurgeFromMemoryRunnable> r = 2437 new CacheStorageService::PurgeFromMemoryRunnable(this, 2438 CacheEntry::PURGE_WHOLE); 2439 2440 return thread->Dispatch(r, CacheIOThread::WRITE); 2441 } 2442 2443 NS_IMETHODIMP 2444 CacheStorageService::ClearDictionaryCacheMemory() { 2445 LOG(("CacheStorageService::ClearDictionaryCacheMemory")); 2446 RefPtr<DictionaryCache> cache = DictionaryCache::GetInstance(); 2447 if (cache) { 2448 cache->Clear(); 2449 } 2450 return NS_OK; 2451 } 2452 2453 NS_IMETHODIMP 2454 CacheStorageService::CorruptDictionaryHash(const nsACString& aURI) { 2455 LOG(("CacheStorageService::CorruptDictionaryHash [uri=%s]", 2456 PromiseFlatCString(aURI).get())); 2457 RefPtr<DictionaryCache> cache = DictionaryCache::GetInstance(); 2458 if (cache) { 2459 cache->CorruptHashForTesting(aURI); 2460 } 2461 return NS_OK; 2462 } 2463 2464 NS_IMETHODIMP 2465 CacheStorageService::ClearDictionaryDataForTesting(const nsACString& aURI) { 2466 LOG(("CacheStorageService::ClearDictionaryDataForTesting [uri=%s]", 2467 PromiseFlatCString(aURI).get())); 2468 RefPtr<DictionaryCache> cache = DictionaryCache::GetInstance(); 2469 if (cache) { 2470 cache->ClearDictionaryDataForTesting(aURI); 2471 } 2472 return NS_OK; 2473 } 2474 2475 } // namespace mozilla::net