Bug 1875866 [wpt PR 44121] - Update wpt metadata, a=testonly
[gecko.git] / netwerk / cache2 / CacheStorageService.cpp
blob80b06df010f1d828e98adb9d878082529223fdf6
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/. */
7 #include "CacheLog.h"
8 #include "CacheStorageService.h"
9 #include <iterator>
10 #include "CacheFileIOManager.h"
11 #include "CacheObserver.h"
12 #include "CacheIndex.h"
13 #include "CacheIndexIterator.h"
14 #include "CacheStorage.h"
15 #include "CacheEntry.h"
16 #include "CacheFileUtils.h"
18 #include "ErrorList.h"
19 #include "nsICacheStorageVisitor.h"
20 #include "nsIObserverService.h"
21 #include "nsIFile.h"
22 #include "nsIURI.h"
23 #include "nsINetworkPredictor.h"
24 #include "nsCOMPtr.h"
25 #include "nsContentUtils.h"
26 #include "nsNetCID.h"
27 #include "nsNetUtil.h"
28 #include "nsServiceManagerUtils.h"
29 #include "nsXULAppAPI.h"
30 #include "mozilla/AtomicBitfields.h"
31 #include "mozilla/TimeStamp.h"
32 #include "mozilla/DebugOnly.h"
33 #include "mozilla/Services.h"
34 #include "mozilla/StoragePrincipalHelper.h"
35 #include "mozilla/IntegerPrintfMacros.h"
36 #include "mozilla/Telemetry.h"
37 #include "mozilla/StaticPrefs_network.h"
39 namespace mozilla::net {
41 namespace {
43 void AppendMemoryStorageTag(nsAutoCString& key) {
44 // Using DEL as the very last ascii-7 character we can use in the list of
45 // attributes
46 key.Append('\x7f');
47 key.Append(',');
50 } // namespace
52 // Not defining as static or class member of CacheStorageService since
53 // it would otherwise need to include CacheEntry.h and that then would
54 // need to be exported to make nsNetModule.cpp compilable.
55 using GlobalEntryTables = nsClassHashtable<nsCStringHashKey, CacheEntryTable>;
57 /**
58 * Keeps tables of entries. There is one entries table for each distinct load
59 * context type. The distinction is based on following load context info
60 * states: <isPrivate|isAnon|inIsolatedMozBrowser> which builds a mapping
61 * key.
63 * Thread-safe to access, protected by the service mutex.
65 static GlobalEntryTables* sGlobalEntryTables;
67 CacheMemoryConsumer::CacheMemoryConsumer(uint32_t aFlags) {
68 StoreFlags(aFlags);
71 void CacheMemoryConsumer::DoMemoryReport(uint32_t aCurrentSize) {
72 if (!(LoadFlags() & DONT_REPORT) && CacheStorageService::Self()) {
73 CacheStorageService::Self()->OnMemoryConsumptionChange(this, aCurrentSize);
77 CacheStorageService::MemoryPool::MemoryPool(EType aType) : mType(aType) {}
79 CacheStorageService::MemoryPool::~MemoryPool() {
80 if (mMemorySize != 0) {
81 NS_ERROR(
82 "Network cache reported memory consumption is not at 0, probably "
83 "leaking?");
87 uint32_t CacheStorageService::MemoryPool::Limit() const {
88 uint32_t limit = 0;
90 switch (mType) {
91 case DISK:
92 limit = CacheObserver::MetadataMemoryLimit();
93 break;
94 case MEMORY:
95 limit = CacheObserver::MemoryCacheCapacity();
96 break;
97 default:
98 MOZ_CRASH("Bad pool type");
101 static const uint32_t kMaxLimit = 0x3FFFFF;
102 if (limit > kMaxLimit) {
103 LOG((" a memory limit (%u) is unexpectedly high, clipping to %u", limit,
104 kMaxLimit));
105 limit = kMaxLimit;
108 return limit << 10;
111 NS_IMPL_ISUPPORTS(CacheStorageService, nsICacheStorageService,
112 nsIMemoryReporter, nsITimerCallback, nsICacheTesting,
113 nsINamed)
115 CacheStorageService* CacheStorageService::sSelf = nullptr;
117 CacheStorageService::CacheStorageService() {
118 CacheFileIOManager::Init();
120 MOZ_ASSERT(XRE_IsParentProcess());
121 MOZ_ASSERT(!sSelf);
123 sSelf = this;
124 sGlobalEntryTables = new GlobalEntryTables();
126 RegisterStrongMemoryReporter(this);
129 CacheStorageService::~CacheStorageService() {
130 LOG(("CacheStorageService::~CacheStorageService"));
131 sSelf = nullptr;
134 void CacheStorageService::Shutdown() {
135 mozilla::MutexAutoLock lock(mLock);
137 if (mShutdown) return;
139 LOG(("CacheStorageService::Shutdown - start"));
141 mShutdown = true;
143 nsCOMPtr<nsIRunnable> event =
144 NewRunnableMethod("net::CacheStorageService::ShutdownBackground", this,
145 &CacheStorageService::ShutdownBackground);
146 Dispatch(event);
148 #ifdef NS_FREE_PERMANENT_DATA
149 sGlobalEntryTables->Clear();
150 delete sGlobalEntryTables;
151 #endif
152 sGlobalEntryTables = nullptr;
154 LOG(("CacheStorageService::Shutdown - done"));
157 void CacheStorageService::ShutdownBackground() {
158 LOG(("CacheStorageService::ShutdownBackground - start"));
160 MOZ_ASSERT(IsOnManagementThread());
163 mozilla::MutexAutoLock lock(mLock);
165 // Cancel purge timer to avoid leaking.
166 if (mPurgeTimer) {
167 LOG((" freeing the timer"));
168 mPurgeTimer->Cancel();
172 #ifdef NS_FREE_PERMANENT_DATA
173 Pool(MemoryPool::EType::DISK).mManagedEntries.clear();
174 Pool(MemoryPool::EType::MEMORY).mManagedEntries.clear();
175 #endif
177 LOG(("CacheStorageService::ShutdownBackground - done"));
180 // Internal management methods
182 namespace {
184 // WalkCacheRunnable
185 // Base class for particular storage entries visiting
186 class WalkCacheRunnable : public Runnable,
187 public CacheStorageService::EntryInfoCallback {
188 protected:
189 WalkCacheRunnable(nsICacheStorageVisitor* aVisitor, bool aVisitEntries)
190 : Runnable("net::WalkCacheRunnable"),
191 mService(CacheStorageService::Self()),
192 mCallback(aVisitor) {
193 MOZ_ASSERT(NS_IsMainThread());
194 StoreNotifyStorage(true);
195 StoreVisitEntries(aVisitEntries);
198 virtual ~WalkCacheRunnable() {
199 if (mCallback) {
200 ProxyReleaseMainThread("WalkCacheRunnable::mCallback", mCallback);
204 RefPtr<CacheStorageService> mService;
205 nsCOMPtr<nsICacheStorageVisitor> mCallback;
207 uint64_t mSize{0};
209 // clang-format off
210 MOZ_ATOMIC_BITFIELDS(mAtomicBitfields, 8, (
211 (bool, NotifyStorage, 1),
212 (bool, VisitEntries, 1)
214 // clang-format on
216 Atomic<bool> mCancel{false};
219 // WalkMemoryCacheRunnable
220 // Responsible to visit memory storage and walk
221 // all entries on it asynchronously.
222 class WalkMemoryCacheRunnable : public WalkCacheRunnable {
223 public:
224 WalkMemoryCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries,
225 nsICacheStorageVisitor* aVisitor)
226 : WalkCacheRunnable(aVisitor, aVisitEntries) {
227 CacheFileUtils::AppendKeyPrefix(aLoadInfo, mContextKey);
228 MOZ_ASSERT(NS_IsMainThread());
231 nsresult Walk() { return mService->Dispatch(this); }
233 private:
234 NS_IMETHOD Run() override {
235 if (CacheStorageService::IsOnManagementThread()) {
236 LOG(("WalkMemoryCacheRunnable::Run - collecting [this=%p]", this));
237 // First, walk, count and grab all entries from the storage
239 mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
241 if (!CacheStorageService::IsRunning()) return NS_ERROR_NOT_INITIALIZED;
243 // Count the entries to allocate the array memory all at once.
244 size_t numEntries = 0;
245 for (const auto& entries : sGlobalEntryTables->Values()) {
246 if (entries->Type() != CacheEntryTable::MEMORY_ONLY) {
247 continue;
249 numEntries += entries->Values().Count();
251 mEntryArray.SetCapacity(numEntries);
253 // Collect the entries.
254 for (const auto& entries : sGlobalEntryTables->Values()) {
255 if (entries->Type() != CacheEntryTable::MEMORY_ONLY) {
256 continue;
259 for (CacheEntry* entry : entries->Values()) {
260 MOZ_ASSERT(!entry->IsUsingDisk());
262 mSize += entry->GetMetadataMemoryConsumption();
264 int64_t size;
265 if (NS_SUCCEEDED(entry->GetDataSize(&size))) {
266 mSize += size;
268 mEntryArray.AppendElement(entry);
272 // Next, we dispatch to the main thread
273 } else if (NS_IsMainThread()) {
274 LOG(("WalkMemoryCacheRunnable::Run - notifying [this=%p]", this));
276 if (LoadNotifyStorage()) {
277 LOG((" storage"));
279 uint64_t capacity = CacheObserver::MemoryCacheCapacity();
280 capacity <<= 10; // kilobytes to bytes
282 // Second, notify overall storage info
283 mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize, capacity,
284 nullptr);
285 if (!LoadVisitEntries()) return NS_OK; // done
287 StoreNotifyStorage(false);
289 } else {
290 LOG((" entry [left=%zu, canceled=%d]", mEntryArray.Length(),
291 (bool)mCancel));
293 // Third, notify each entry until depleted or canceled.
294 if (mNextEntryIdx >= mEntryArray.Length() || mCancel) {
295 mCallback->OnCacheEntryVisitCompleted();
296 return NS_OK; // done
299 // Grab the next entry.
300 RefPtr<CacheEntry> entry = std::move(mEntryArray[mNextEntryIdx++]);
302 // Invokes this->OnEntryInfo, that calls the callback with all
303 // information of the entry.
304 CacheStorageService::GetCacheEntryInfo(entry, this);
306 } else {
307 MOZ_CRASH("Bad thread");
308 return NS_ERROR_FAILURE;
311 NS_DispatchToMainThread(this);
312 return NS_OK;
315 virtual ~WalkMemoryCacheRunnable() {
316 if (mCallback) {
317 ProxyReleaseMainThread("WalkMemoryCacheRunnable::mCallback", mCallback);
321 virtual void OnEntryInfo(const nsACString& aURISpec,
322 const nsACString& aIdEnhance, int64_t aDataSize,
323 int64_t aAltDataSize, uint32_t aFetchCount,
324 uint32_t aLastModifiedTime, uint32_t aExpirationTime,
325 bool aPinned, nsILoadContextInfo* aInfo) override {
326 nsresult rv;
328 nsCOMPtr<nsIURI> uri;
329 rv = NS_NewURI(getter_AddRefs(uri), aURISpec);
330 if (NS_FAILED(rv)) {
331 return;
334 rv = mCallback->OnCacheEntryInfo(uri, aIdEnhance, aDataSize, aAltDataSize,
335 aFetchCount, aLastModifiedTime,
336 aExpirationTime, aPinned, aInfo);
337 if (NS_FAILED(rv)) {
338 LOG((" callback failed, canceling the walk"));
339 mCancel = true;
343 private:
344 nsCString mContextKey;
345 nsTArray<RefPtr<CacheEntry>> mEntryArray;
346 size_t mNextEntryIdx{0};
349 // WalkDiskCacheRunnable
350 // Using the cache index information to get the list of files per context.
351 class WalkDiskCacheRunnable : public WalkCacheRunnable {
352 public:
353 WalkDiskCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries,
354 nsICacheStorageVisitor* aVisitor)
355 : WalkCacheRunnable(aVisitor, aVisitEntries),
356 mLoadInfo(aLoadInfo),
357 mPass(COLLECT_STATS),
358 mCount(0) {}
360 nsresult Walk() {
361 // TODO, bug 998693
362 // Initial index build should be forced here so that about:cache soon
363 // after startup gives some meaningfull results.
365 // Dispatch to the INDEX level in hope that very recent cache entries
366 // information gets to the index list before we grab the index iterator
367 // for the first time. This tries to avoid miss of entries that has
368 // been created right before the visit is required.
369 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
370 NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED);
372 return thread->Dispatch(this, CacheIOThread::INDEX);
375 private:
376 // Invokes OnCacheEntryInfo callback for each single found entry.
377 // There is one instance of this class per one entry.
378 class OnCacheEntryInfoRunnable : public Runnable {
379 public:
380 explicit OnCacheEntryInfoRunnable(WalkDiskCacheRunnable* aWalker)
381 : Runnable("net::WalkDiskCacheRunnable::OnCacheEntryInfoRunnable"),
382 mWalker(aWalker) {}
384 NS_IMETHOD Run() override {
385 MOZ_ASSERT(NS_IsMainThread());
387 nsresult rv;
389 nsCOMPtr<nsIURI> uri;
390 rv = NS_NewURI(getter_AddRefs(uri), mURISpec);
391 if (NS_FAILED(rv)) {
392 return NS_OK;
395 rv = mWalker->mCallback->OnCacheEntryInfo(
396 uri, mIdEnhance, mDataSize, mAltDataSize, mFetchCount,
397 mLastModifiedTime, mExpirationTime, mPinned, mInfo);
398 if (NS_FAILED(rv)) {
399 mWalker->mCancel = true;
402 return NS_OK;
405 RefPtr<WalkDiskCacheRunnable> mWalker;
407 nsCString mURISpec;
408 nsCString mIdEnhance;
409 int64_t mDataSize{0};
410 int64_t mAltDataSize{0};
411 uint32_t mFetchCount{0};
412 uint32_t mLastModifiedTime{0};
413 uint32_t mExpirationTime{0};
414 bool mPinned{false};
415 nsCOMPtr<nsILoadContextInfo> mInfo;
418 NS_IMETHOD Run() override {
419 // The main loop
420 nsresult rv;
422 if (CacheStorageService::IsOnManagementThread()) {
423 switch (mPass) {
424 case COLLECT_STATS:
425 // Get quickly the cache stats.
426 uint32_t size;
427 rv = CacheIndex::GetCacheStats(mLoadInfo, &size, &mCount);
428 if (NS_FAILED(rv)) {
429 if (LoadVisitEntries()) {
430 // both onStorageInfo and onCompleted are expected
431 NS_DispatchToMainThread(this);
433 return NS_DispatchToMainThread(this);
436 mSize = static_cast<uint64_t>(size) << 10;
438 // Invoke onCacheStorageInfo with valid information.
439 NS_DispatchToMainThread(this);
441 if (!LoadVisitEntries()) {
442 return NS_OK; // done
445 mPass = ITERATE_METADATA;
446 [[fallthrough]];
448 case ITERATE_METADATA:
449 // Now grab the context iterator.
450 if (!mIter) {
451 rv =
452 CacheIndex::GetIterator(mLoadInfo, true, getter_AddRefs(mIter));
453 if (NS_FAILED(rv)) {
454 // Invoke onCacheEntryVisitCompleted now
455 return NS_DispatchToMainThread(this);
459 while (!mCancel && !CacheObserver::ShuttingDown()) {
460 if (CacheIOThread::YieldAndRerun()) return NS_OK;
462 SHA1Sum::Hash hash;
463 rv = mIter->GetNextHash(&hash);
464 if (NS_FAILED(rv)) break; // done (or error?)
466 // This synchronously invokes OnEntryInfo on this class where we
467 // redispatch to the main thread for the consumer callback.
468 CacheFileIOManager::GetEntryInfo(&hash, this);
471 // Invoke onCacheEntryVisitCompleted on the main thread
472 NS_DispatchToMainThread(this);
474 } else if (NS_IsMainThread()) {
475 if (LoadNotifyStorage()) {
476 nsCOMPtr<nsIFile> dir;
477 CacheFileIOManager::GetCacheDirectory(getter_AddRefs(dir));
478 uint64_t capacity = CacheObserver::DiskCacheCapacity();
479 capacity <<= 10; // kilobytes to bytes
480 mCallback->OnCacheStorageInfo(mCount, mSize, capacity, dir);
481 StoreNotifyStorage(false);
482 } else {
483 mCallback->OnCacheEntryVisitCompleted();
485 } else {
486 MOZ_CRASH("Bad thread");
487 return NS_ERROR_FAILURE;
490 return NS_OK;
493 virtual void OnEntryInfo(const nsACString& aURISpec,
494 const nsACString& aIdEnhance, int64_t aDataSize,
495 int64_t aAltDataSize, uint32_t aFetchCount,
496 uint32_t aLastModifiedTime, uint32_t aExpirationTime,
497 bool aPinned, nsILoadContextInfo* aInfo) override {
498 // Called directly from CacheFileIOManager::GetEntryInfo.
500 // Invoke onCacheEntryInfo on the main thread for this entry.
501 RefPtr<OnCacheEntryInfoRunnable> info = new OnCacheEntryInfoRunnable(this);
502 info->mURISpec = aURISpec;
503 info->mIdEnhance = aIdEnhance;
504 info->mDataSize = aDataSize;
505 info->mAltDataSize = aAltDataSize;
506 info->mFetchCount = aFetchCount;
507 info->mLastModifiedTime = aLastModifiedTime;
508 info->mExpirationTime = aExpirationTime;
509 info->mPinned = aPinned;
510 info->mInfo = aInfo;
512 NS_DispatchToMainThread(info);
515 RefPtr<nsILoadContextInfo> mLoadInfo;
516 enum {
517 // First, we collect stats for the load context.
518 COLLECT_STATS,
520 // Second, if demanded, we iterate over the entries gethered
521 // from the iterator and call CacheFileIOManager::GetEntryInfo
522 // for each found entry.
523 ITERATE_METADATA,
524 } mPass;
526 RefPtr<CacheIndexIterator> mIter;
527 uint32_t mCount;
530 } // namespace
532 void CacheStorageService::DropPrivateBrowsingEntries() {
533 mozilla::MutexAutoLock lock(mLock);
535 if (mShutdown) return;
537 nsTArray<nsCString> keys;
538 for (const nsACString& key : sGlobalEntryTables->Keys()) {
539 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(key);
540 if (info && info->IsPrivate()) {
541 keys.AppendElement(key);
545 for (uint32_t i = 0; i < keys.Length(); ++i) {
546 DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
550 // Helper methods
552 // static
553 bool CacheStorageService::IsOnManagementThread() {
554 RefPtr<CacheStorageService> service = Self();
555 if (!service) return false;
557 nsCOMPtr<nsIEventTarget> target = service->Thread();
558 if (!target) return false;
560 bool currentThread;
561 nsresult rv = target->IsOnCurrentThread(&currentThread);
562 return NS_SUCCEEDED(rv) && currentThread;
565 already_AddRefed<nsIEventTarget> CacheStorageService::Thread() const {
566 return CacheFileIOManager::IOTarget();
569 nsresult CacheStorageService::Dispatch(nsIRunnable* aEvent) {
570 RefPtr<CacheIOThread> cacheIOThread = CacheFileIOManager::IOThread();
571 if (!cacheIOThread) return NS_ERROR_NOT_AVAILABLE;
573 return cacheIOThread->Dispatch(aEvent, CacheIOThread::MANAGEMENT);
576 namespace CacheStorageEvictHelper {
578 nsresult ClearStorage(bool const aPrivate, bool const aAnonymous,
579 OriginAttributes& aOa) {
580 nsresult rv;
582 aOa.SyncAttributesWithPrivateBrowsing(aPrivate);
583 RefPtr<LoadContextInfo> info = GetLoadContextInfo(aAnonymous, aOa);
585 nsCOMPtr<nsICacheStorage> storage;
586 RefPtr<CacheStorageService> service = CacheStorageService::Self();
587 NS_ENSURE_TRUE(service, NS_ERROR_FAILURE);
589 // Clear disk storage
590 rv = service->DiskCacheStorage(info, getter_AddRefs(storage));
591 NS_ENSURE_SUCCESS(rv, rv);
592 rv = storage->AsyncEvictStorage(nullptr);
593 NS_ENSURE_SUCCESS(rv, rv);
595 // Clear memory storage
596 rv = service->MemoryCacheStorage(info, getter_AddRefs(storage));
597 NS_ENSURE_SUCCESS(rv, rv);
598 rv = storage->AsyncEvictStorage(nullptr);
599 NS_ENSURE_SUCCESS(rv, rv);
601 return NS_OK;
604 nsresult Run(OriginAttributes& aOa) {
605 nsresult rv;
607 // Clear all [private X anonymous] combinations
608 rv = ClearStorage(false, false, aOa);
609 NS_ENSURE_SUCCESS(rv, rv);
610 rv = ClearStorage(false, true, aOa);
611 NS_ENSURE_SUCCESS(rv, rv);
612 rv = ClearStorage(true, false, aOa);
613 NS_ENSURE_SUCCESS(rv, rv);
614 rv = ClearStorage(true, true, aOa);
615 NS_ENSURE_SUCCESS(rv, rv);
617 return NS_OK;
620 } // namespace CacheStorageEvictHelper
622 // nsICacheStorageService
624 NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(
625 nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
626 NS_ENSURE_ARG(_retval);
628 nsCOMPtr<nsICacheStorage> storage =
629 new CacheStorage(aLoadContextInfo, false, false, false);
630 storage.forget(_retval);
631 return NS_OK;
634 NS_IMETHODIMP CacheStorageService::DiskCacheStorage(
635 nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
636 NS_ENSURE_ARG(_retval);
638 // TODO save some heap granularity - cache commonly used storages.
640 // When disk cache is disabled, still provide a storage, but just keep stuff
641 // in memory.
642 bool useDisk = CacheObserver::UseDiskCache();
644 nsCOMPtr<nsICacheStorage> storage = new CacheStorage(
645 aLoadContextInfo, useDisk, false /* size limit */, false /* don't pin */);
646 storage.forget(_retval);
647 return NS_OK;
650 NS_IMETHODIMP CacheStorageService::PinningCacheStorage(
651 nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
652 NS_ENSURE_ARG(aLoadContextInfo);
653 NS_ENSURE_ARG(_retval);
655 // When disk cache is disabled don't pretend we cache.
656 if (!CacheObserver::UseDiskCache()) {
657 return NS_ERROR_NOT_AVAILABLE;
660 nsCOMPtr<nsICacheStorage> storage =
661 new CacheStorage(aLoadContextInfo, true /* use disk */,
662 true /* ignore size checks */, true /* pin */);
663 storage.forget(_retval);
664 return NS_OK;
667 NS_IMETHODIMP CacheStorageService::Clear() {
668 nsresult rv;
670 // Tell the index to block notification to AsyncGetDiskConsumption.
671 // Will be allowed again from CacheFileContextEvictor::EvictEntries()
672 // when all the context have been removed from disk.
673 CacheIndex::OnAsyncEviction(true);
675 mozilla::MutexAutoLock lock(mLock);
678 mozilla::MutexAutoLock forcedValidEntriesLock(mForcedValidEntriesLock);
679 mForcedValidEntries.Clear();
682 NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
684 const auto keys = ToTArray<nsTArray<nsCString>>(sGlobalEntryTables->Keys());
685 for (const auto& key : keys) {
686 DoomStorageEntries(key, nullptr, true, false, nullptr);
689 // Passing null as a load info means to evict all contexts.
690 // EvictByContext() respects the entry pinning. EvictAll() does not.
691 rv = CacheFileIOManager::EvictByContext(nullptr, false, u""_ns);
692 NS_ENSURE_SUCCESS(rv, rv);
694 return NS_OK;
697 NS_IMETHODIMP CacheStorageService::ClearOrigin(nsIPrincipal* aPrincipal) {
698 nsresult rv;
700 if (NS_WARN_IF(!aPrincipal)) {
701 return NS_ERROR_FAILURE;
704 nsAutoString origin;
705 rv = nsContentUtils::GetWebExposedOriginSerialization(aPrincipal, origin);
706 NS_ENSURE_SUCCESS(rv, rv);
708 rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), true);
709 NS_ENSURE_SUCCESS(rv, rv);
711 rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), false);
712 NS_ENSURE_SUCCESS(rv, rv);
714 return NS_OK;
717 NS_IMETHODIMP CacheStorageService::ClearOriginAttributes(
718 const nsAString& aOriginAttributes) {
719 nsresult rv;
721 if (NS_WARN_IF(aOriginAttributes.IsEmpty())) {
722 return NS_ERROR_FAILURE;
725 OriginAttributes oa;
726 if (!oa.Init(aOriginAttributes)) {
727 NS_ERROR("Could not parse the argument for OriginAttributes");
728 return NS_ERROR_FAILURE;
731 rv = CacheStorageEvictHelper::Run(oa);
732 NS_ENSURE_SUCCESS(rv, rv);
734 return NS_OK;
737 static bool RemoveExactEntry(CacheEntryTable* aEntries, nsACString const& aKey,
738 CacheEntry* aEntry, bool aOverwrite) {
739 RefPtr<CacheEntry> existingEntry;
740 if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
741 LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
742 return false; // Already removed...
745 if (!aOverwrite && existingEntry != aEntry) {
746 LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry));
747 return false; // Already replaced...
750 LOG(("RemoveExactEntry [entry=%p removed]", aEntry));
751 aEntries->Remove(aKey);
752 return true;
755 NS_IMETHODIMP CacheStorageService::ClearBaseDomain(
756 const nsAString& aBaseDomain) {
757 if (sGlobalEntryTables) {
758 mozilla::MutexAutoLock lock(mLock);
760 if (mShutdown) return NS_ERROR_NOT_AVAILABLE;
762 nsCString cBaseDomain = NS_ConvertUTF16toUTF8(aBaseDomain);
764 nsTArray<nsCString> keys;
765 for (const auto& globalEntry : *sGlobalEntryTables) {
766 // Match by partitionKey base domain. This should cover most cache entries
767 // because we statically partition the cache. Most first party cache
768 // entries will also have a partitionKey set where the partitionKey base
769 // domain will match the entry URI base domain.
770 const nsACString& key = globalEntry.GetKey();
771 nsCOMPtr<nsILoadContextInfo> info =
772 CacheFileUtils::ParseKey(globalEntry.GetKey());
774 if (info &&
775 StoragePrincipalHelper::PartitionKeyHasBaseDomain(
776 info->OriginAttributesPtr()->mPartitionKey, aBaseDomain)) {
777 keys.AppendElement(key);
778 continue;
781 // If we didn't get a partitionKey match, try to match by entry URI. This
782 // requires us to iterate over all entries.
783 CacheEntryTable* table = globalEntry.GetWeak();
784 MOZ_ASSERT(table);
786 nsTArray<RefPtr<CacheEntry>> entriesToDelete;
788 for (CacheEntry* entry : table->Values()) {
789 nsCOMPtr<nsIURI> uri;
790 nsresult rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI());
791 if (NS_WARN_IF(NS_FAILED(rv))) {
792 continue;
795 nsAutoCString host;
796 rv = uri->GetHost(host);
797 // Some entries may not have valid hosts. We can skip them.
798 if (NS_FAILED(rv) || host.IsEmpty()) {
799 continue;
802 bool hasRootDomain = false;
803 rv = HasRootDomain(host, cBaseDomain, &hasRootDomain);
804 if (NS_WARN_IF(NS_FAILED(rv))) {
805 continue;
807 if (hasRootDomain) {
808 entriesToDelete.AppendElement(entry);
812 // Clear individual matched entries.
813 for (RefPtr<CacheEntry>& entry : entriesToDelete) {
814 nsAutoCString entryKey;
815 nsresult rv = entry->HashingKey(entryKey);
816 if (NS_FAILED(rv)) {
817 NS_ERROR("aEntry->HashingKey() failed?");
818 return rv;
821 RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */);
825 // Clear matched keys.
826 for (uint32_t i = 0; i < keys.Length(); ++i) {
827 DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
831 return CacheFileIOManager::EvictByContext(nullptr, false /* pinned */, u""_ns,
832 aBaseDomain);
835 nsresult CacheStorageService::ClearOriginInternal(
836 const nsAString& aOrigin, const OriginAttributes& aOriginAttributes,
837 bool aAnonymous) {
838 nsresult rv;
840 RefPtr<LoadContextInfo> info =
841 GetLoadContextInfo(aAnonymous, aOriginAttributes);
842 if (NS_WARN_IF(!info)) {
843 return NS_ERROR_FAILURE;
846 mozilla::MutexAutoLock lock(mLock);
848 if (sGlobalEntryTables) {
849 for (const auto& globalEntry : *sGlobalEntryTables) {
850 bool matches = false;
851 rv = CacheFileUtils::KeyMatchesLoadContextInfo(globalEntry.GetKey(), info,
852 &matches);
853 NS_ENSURE_SUCCESS(rv, rv);
854 if (!matches) {
855 continue;
858 CacheEntryTable* table = globalEntry.GetWeak();
859 MOZ_ASSERT(table);
861 nsTArray<RefPtr<CacheEntry>> entriesToDelete;
863 for (CacheEntry* entry : table->Values()) {
864 nsCOMPtr<nsIURI> uri;
865 rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI());
866 NS_ENSURE_SUCCESS(rv, rv);
868 nsAutoString origin;
869 rv = nsContentUtils::GetWebExposedOriginSerialization(uri, origin);
870 NS_ENSURE_SUCCESS(rv, rv);
872 if (origin != aOrigin) {
873 continue;
876 entriesToDelete.AppendElement(entry);
879 for (RefPtr<CacheEntry>& entry : entriesToDelete) {
880 nsAutoCString entryKey;
881 rv = entry->HashingKey(entryKey);
882 if (NS_FAILED(rv)) {
883 NS_ERROR("aEntry->HashingKey() failed?");
884 return rv;
887 RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */);
892 rv = CacheFileIOManager::EvictByContext(info, false /* pinned */, aOrigin);
893 NS_ENSURE_SUCCESS(rv, rv);
895 return NS_OK;
898 NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat) {
899 uint32_t what;
901 switch (aWhat) {
902 case PURGE_DISK_DATA_ONLY:
903 what = CacheEntry::PURGE_DATA_ONLY_DISK_BACKED;
904 break;
906 case PURGE_DISK_ALL:
907 what = CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED;
908 break;
910 case PURGE_EVERYTHING:
911 what = CacheEntry::PURGE_WHOLE;
912 break;
914 default:
915 return NS_ERROR_INVALID_ARG;
918 nsCOMPtr<nsIRunnable> event = new PurgeFromMemoryRunnable(this, what);
920 return Dispatch(event);
923 NS_IMETHODIMP CacheStorageService::PurgeFromMemoryRunnable::Run() {
924 if (NS_IsMainThread()) {
925 nsCOMPtr<nsIObserverService> observerService =
926 mozilla::services::GetObserverService();
927 if (observerService) {
928 observerService->NotifyObservers(
929 nullptr, "cacheservice:purge-memory-pools", nullptr);
932 return NS_OK;
935 if (mService) {
936 // Note that we seem to come here only in the case of "memory-pressure"
937 // being notified (or in case of tests), so we start from purging in-memory
938 // entries first and ignore minprogress for disk entries.
939 // TODO not all flags apply to both pools.
940 mService->Pool(MemoryPool::EType::MEMORY)
941 .PurgeAll(mWhat, StaticPrefs::network_cache_purge_minprogress_memory());
942 mService->Pool(MemoryPool::EType::DISK).PurgeAll(mWhat, 0);
943 mService = nullptr;
946 NS_DispatchToMainThread(this);
947 return NS_OK;
950 NS_IMETHODIMP CacheStorageService::AsyncGetDiskConsumption(
951 nsICacheStorageConsumptionObserver* aObserver) {
952 NS_ENSURE_ARG(aObserver);
954 nsresult rv;
956 rv = CacheIndex::AsyncGetDiskConsumption(aObserver);
957 NS_ENSURE_SUCCESS(rv, rv);
959 return NS_OK;
962 NS_IMETHODIMP CacheStorageService::GetIoTarget(nsIEventTarget** aEventTarget) {
963 NS_ENSURE_ARG(aEventTarget);
965 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
966 ioTarget.forget(aEventTarget);
968 return NS_OK;
971 NS_IMETHODIMP CacheStorageService::AsyncVisitAllStorages(
972 nsICacheStorageVisitor* aVisitor, bool aVisitEntries) {
973 LOG(("CacheStorageService::AsyncVisitAllStorages [cb=%p]", aVisitor));
974 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
976 // Walking the disk cache also walks the memory cache.
977 RefPtr<WalkDiskCacheRunnable> event =
978 new WalkDiskCacheRunnable(nullptr, aVisitEntries, aVisitor);
979 return event->Walk();
982 // Methods used by CacheEntry for management of in-memory structures.
984 void CacheStorageService::RegisterEntry(CacheEntry* aEntry) {
985 MOZ_ASSERT(IsOnManagementThread());
987 if (mShutdown || !aEntry->CanRegister()) return;
989 TelemetryRecordEntryCreation(aEntry);
991 LOG(("CacheStorageService::RegisterEntry [entry=%p]", aEntry));
993 MemoryPool& pool = Pool(aEntry->IsUsingDisk());
994 pool.mManagedEntries.insertBack(aEntry);
996 aEntry->SetRegistered(true);
999 void CacheStorageService::UnregisterEntry(CacheEntry* aEntry) {
1000 MOZ_ASSERT(IsOnManagementThread());
1002 if (!aEntry->IsRegistered()) return;
1004 TelemetryRecordEntryRemoval(aEntry);
1006 LOG(("CacheStorageService::UnregisterEntry [entry=%p]", aEntry));
1008 MemoryPool& pool = Pool(aEntry->IsUsingDisk());
1009 aEntry->removeFrom(pool.mManagedEntries);
1011 // Note: aEntry->CanRegister() since now returns false
1012 aEntry->SetRegistered(false);
1015 static bool AddExactEntry(CacheEntryTable* aEntries, nsACString const& aKey,
1016 CacheEntry* aEntry, bool aOverwrite) {
1017 RefPtr<CacheEntry> existingEntry;
1018 if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
1019 bool equals = existingEntry == aEntry;
1020 LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals));
1021 return equals; // Already there...
1024 LOG(("AddExactEntry [entry=%p put]", aEntry));
1025 aEntries->InsertOrUpdate(aKey, RefPtr{aEntry});
1026 return true;
1029 bool CacheStorageService::RemoveEntry(CacheEntry* aEntry,
1030 bool aOnlyUnreferenced) {
1031 LOG(("CacheStorageService::RemoveEntry [entry=%p]", aEntry));
1033 nsAutoCString entryKey;
1034 nsresult rv = aEntry->HashingKey(entryKey);
1035 if (NS_FAILED(rv)) {
1036 NS_ERROR("aEntry->HashingKey() failed?");
1037 return false;
1040 mozilla::MutexAutoLock lock(mLock);
1042 if (mShutdown) {
1043 LOG((" after shutdown"));
1044 return false;
1047 if (aOnlyUnreferenced) {
1048 if (aEntry->IsReferenced()) {
1049 LOG((" still referenced, not removing"));
1050 return false;
1053 if (!aEntry->IsUsingDisk() &&
1054 IsForcedValidEntry(aEntry->GetStorageID(), entryKey)) {
1055 LOG((" forced valid, not removing"));
1056 return false;
1060 CacheEntryTable* entries;
1061 if (sGlobalEntryTables->Get(aEntry->GetStorageID(), &entries)) {
1062 RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
1065 nsAutoCString memoryStorageID(aEntry->GetStorageID());
1066 AppendMemoryStorageTag(memoryStorageID);
1068 if (sGlobalEntryTables->Get(memoryStorageID, &entries)) {
1069 RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
1072 return true;
1075 void CacheStorageService::RecordMemoryOnlyEntry(CacheEntry* aEntry,
1076 bool aOnlyInMemory,
1077 bool aOverwrite) {
1078 LOG(
1079 ("CacheStorageService::RecordMemoryOnlyEntry [entry=%p, memory=%d, "
1080 "overwrite=%d]",
1081 aEntry, aOnlyInMemory, aOverwrite));
1082 // This method is responsible to put this entry to a special record hashtable
1083 // that contains only entries that are stored in memory.
1084 // Keep in mind that every entry, regardless of whether is in-memory-only or
1085 // not is always recorded in the storage master hash table, the one identified
1086 // by CacheEntry.StorageID().
1088 mLock.AssertCurrentThreadOwns();
1090 if (mShutdown) {
1091 LOG((" after shutdown"));
1092 return;
1095 nsresult rv;
1097 nsAutoCString entryKey;
1098 rv = aEntry->HashingKey(entryKey);
1099 if (NS_FAILED(rv)) {
1100 NS_ERROR("aEntry->HashingKey() failed?");
1101 return;
1104 CacheEntryTable* entries = nullptr;
1105 nsAutoCString memoryStorageID(aEntry->GetStorageID());
1106 AppendMemoryStorageTag(memoryStorageID);
1108 if (!sGlobalEntryTables->Get(memoryStorageID, &entries)) {
1109 if (!aOnlyInMemory) {
1110 LOG((" not recorded as memory only"));
1111 return;
1114 entries = sGlobalEntryTables
1115 ->InsertOrUpdate(
1116 memoryStorageID,
1117 MakeUnique<CacheEntryTable>(CacheEntryTable::MEMORY_ONLY))
1118 .get();
1119 LOG((" new memory-only storage table for %s", memoryStorageID.get()));
1122 if (aOnlyInMemory) {
1123 AddExactEntry(entries, entryKey, aEntry, aOverwrite);
1124 } else {
1125 RemoveExactEntry(entries, entryKey, aEntry, aOverwrite);
1129 // Checks if a cache entry is forced valid (will be loaded directly from cache
1130 // without further validation) - see nsICacheEntry.idl for further details
1131 bool CacheStorageService::IsForcedValidEntry(nsACString const& aContextKey,
1132 nsACString const& aEntryKey) {
1133 return IsForcedValidEntry(aContextKey + aEntryKey);
1136 bool CacheStorageService::IsForcedValidEntry(
1137 nsACString const& aContextEntryKey) {
1138 mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1140 ForcedValidData data;
1142 if (!mForcedValidEntries.Get(aContextEntryKey, &data)) {
1143 return false;
1146 if (data.validUntil.IsNull()) {
1147 MOZ_ASSERT_UNREACHABLE("the timeStamp should never be null");
1148 return false;
1151 // Entry timeout not reached yet
1152 if (TimeStamp::NowLoRes() <= data.validUntil) {
1153 return true;
1156 // Entry timeout has been reached
1157 mForcedValidEntries.Remove(aContextEntryKey);
1159 if (!data.viewed) {
1160 Telemetry::AccumulateCategorical(
1161 Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::WaitedTooLong);
1163 return false;
1166 void CacheStorageService::MarkForcedValidEntryUse(nsACString const& aContextKey,
1167 nsACString const& aEntryKey) {
1168 mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1170 ForcedValidData data;
1172 if (!mForcedValidEntries.Get(aContextKey + aEntryKey, &data)) {
1173 return;
1176 data.viewed = true;
1177 mForcedValidEntries.InsertOrUpdate(aContextKey + aEntryKey, data);
1180 // Allows a cache entry to be loaded directly from cache without further
1181 // validation - see nsICacheEntry.idl for further details
1182 void CacheStorageService::ForceEntryValidFor(nsACString const& aContextKey,
1183 nsACString const& aEntryKey,
1184 uint32_t aSecondsToTheFuture) {
1185 mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1187 TimeStamp now = TimeStamp::NowLoRes();
1188 ForcedValidEntriesPrune(now);
1190 ForcedValidData data;
1191 data.validUntil = now + TimeDuration::FromSeconds(aSecondsToTheFuture);
1192 data.viewed = false;
1194 mForcedValidEntries.InsertOrUpdate(aContextKey + aEntryKey, data);
1197 void CacheStorageService::RemoveEntryForceValid(nsACString const& aContextKey,
1198 nsACString const& aEntryKey) {
1199 mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1201 LOG(("CacheStorageService::RemoveEntryForceValid context='%s' entryKey=%s",
1202 aContextKey.BeginReading(), aEntryKey.BeginReading()));
1203 ForcedValidData data;
1204 bool ok = mForcedValidEntries.Get(aContextKey + aEntryKey, &data);
1205 if (ok && !data.viewed) {
1206 Telemetry::AccumulateCategorical(
1207 Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::WaitedTooLong);
1209 mForcedValidEntries.Remove(aContextKey + aEntryKey);
1212 // Cleans out the old entries in mForcedValidEntries
1213 void CacheStorageService::ForcedValidEntriesPrune(TimeStamp& now) {
1214 static TimeDuration const oneMinute = TimeDuration::FromSeconds(60);
1215 static TimeStamp dontPruneUntil = now + oneMinute;
1216 if (now < dontPruneUntil) return;
1218 for (auto iter = mForcedValidEntries.Iter(); !iter.Done(); iter.Next()) {
1219 if (iter.Data().validUntil < now) {
1220 if (!iter.Data().viewed) {
1221 Telemetry::AccumulateCategorical(
1222 Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::WaitedTooLong);
1224 iter.Remove();
1227 dontPruneUntil = now + oneMinute;
1230 void CacheStorageService::OnMemoryConsumptionChange(
1231 CacheMemoryConsumer* aConsumer, uint32_t aCurrentMemoryConsumption) {
1232 LOG(("CacheStorageService::OnMemoryConsumptionChange [consumer=%p, size=%u]",
1233 aConsumer, aCurrentMemoryConsumption));
1235 uint32_t savedMemorySize = aConsumer->LoadReportedMemoryConsumption();
1236 if (savedMemorySize == aCurrentMemoryConsumption) return;
1238 // Exchange saved size with current one.
1239 aConsumer->StoreReportedMemoryConsumption(aCurrentMemoryConsumption);
1241 bool usingDisk = !(aConsumer->LoadFlags() & CacheMemoryConsumer::MEMORY_ONLY);
1242 bool overLimit = Pool(usingDisk).OnMemoryConsumptionChange(
1243 savedMemorySize, aCurrentMemoryConsumption);
1245 if (!overLimit) return;
1247 // It's likely the timer has already been set when we get here,
1248 // check outside the lock to save resources.
1249 #ifdef MOZ_TSAN
1250 if (mPurgeTimerActive) {
1251 #else
1252 if (mPurgeTimer) {
1253 #endif
1254 return;
1257 // We don't know if this is called under the service lock or not,
1258 // hence rather dispatch.
1259 RefPtr<nsIEventTarget> cacheIOTarget = Thread();
1260 if (!cacheIOTarget) return;
1262 // Dispatch as a priority task, we want to set the purge timer
1263 // ASAP to prevent vain redispatch of this event.
1264 nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
1265 "net::CacheStorageService::SchedulePurgeOverMemoryLimit", this,
1266 &CacheStorageService::SchedulePurgeOverMemoryLimit);
1267 cacheIOTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1270 bool CacheStorageService::MemoryPool::OnMemoryConsumptionChange(
1271 uint32_t aSavedMemorySize, uint32_t aCurrentMemoryConsumption) {
1272 mMemorySize -= aSavedMemorySize;
1273 mMemorySize += aCurrentMemoryConsumption;
1275 LOG((" mMemorySize=%u (+%u,-%u)", uint32_t(mMemorySize),
1276 aCurrentMemoryConsumption, aSavedMemorySize));
1278 // Bypass purging when memory has not grew up significantly
1279 if (aCurrentMemoryConsumption <= aSavedMemorySize) return false;
1281 return mMemorySize > Limit();
1284 void CacheStorageService::SchedulePurgeOverMemoryLimit() {
1285 LOG(("CacheStorageService::SchedulePurgeOverMemoryLimit"));
1287 mozilla::MutexAutoLock lock(mLock);
1289 if (mShutdown) {
1290 LOG((" past shutdown"));
1291 return;
1294 if (mPurgeTimer) {
1295 LOG((" timer already up"));
1296 return;
1299 mPurgeTimer = NS_NewTimer();
1300 if (mPurgeTimer) {
1301 #ifdef MOZ_TSAN
1302 mPurgeTimerActive = true;
1303 #endif
1304 nsresult rv;
1305 rv = mPurgeTimer->InitWithCallback(this, 1000, nsITimer::TYPE_ONE_SHOT);
1306 LOG((" timer init rv=0x%08" PRIx32, static_cast<uint32_t>(rv)));
1310 NS_IMETHODIMP
1311 CacheStorageService::Notify(nsITimer* aTimer) {
1312 LOG(("CacheStorageService::Notify"));
1314 mozilla::MutexAutoLock lock(mLock);
1316 if (aTimer == mPurgeTimer) {
1317 #ifdef MOZ_TSAN
1318 mPurgeTimerActive = false;
1319 #endif
1320 mPurgeTimer = nullptr;
1322 if (!mShutdown) {
1323 nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
1324 "net::CacheStorageService::PurgeExpiredOrOverMemoryLimit", this,
1325 &CacheStorageService::PurgeExpiredOrOverMemoryLimit);
1326 Dispatch(event);
1330 return NS_OK;
1333 NS_IMETHODIMP
1334 CacheStorageService::GetName(nsACString& aName) {
1335 aName.AssignLiteral("CacheStorageService");
1336 return NS_OK;
1339 void CacheStorageService::PurgeExpiredOrOverMemoryLimit() {
1340 MOZ_ASSERT(IsOnManagementThread());
1342 LOG(("CacheStorageService::PurgeExpiredOrOverMemoryLimit"));
1344 if (mShutdown) return;
1346 static TimeDuration const kFourSeconds = TimeDuration::FromSeconds(4);
1347 TimeStamp now = TimeStamp::NowLoRes();
1349 if (!mLastPurgeTime.IsNull() && now - mLastPurgeTime < kFourSeconds) {
1350 LOG((" bypassed, too soon"));
1351 return;
1354 mLastPurgeTime = now;
1356 // We start purging memory entries first as we care more about RAM over
1357 // disk space beeing freed in case we are interrupted.
1358 Pool(MemoryPool::EType::MEMORY).PurgeExpiredOrOverMemoryLimit();
1359 Pool(MemoryPool::EType::DISK).PurgeExpiredOrOverMemoryLimit();
1362 void CacheStorageService::MemoryPool::PurgeExpiredOrOverMemoryLimit() {
1363 TimeStamp start(TimeStamp::Now());
1365 uint32_t const memoryLimit = Limit();
1366 size_t minprogress =
1367 (mType == EType::DISK)
1368 ? StaticPrefs::network_cache_purge_minprogress_disk()
1369 : StaticPrefs::network_cache_purge_minprogress_memory();
1371 // We always purge expired entries, even if under our limit.
1372 size_t numExpired = PurgeExpired(minprogress);
1373 if (numExpired > 0) {
1374 LOG((" found and purged %zu expired entries", numExpired));
1376 minprogress = (minprogress > numExpired) ? minprogress - numExpired : 0;
1378 // If we are still under pressure, purge LFU entries until we aren't.
1379 if (mMemorySize > memoryLimit) {
1380 // Do not enter PurgeByFrecency if we reached the minimum and are asked to
1381 // deliver entries.
1382 if (minprogress == 0 && CacheIOThread::YieldAndRerun()) {
1383 return;
1386 auto r = PurgeByFrecency(minprogress);
1387 if (MOZ_LIKELY(r.isOk())) {
1388 size_t numPurged = r.unwrap();
1389 LOG((
1390 " memory data consumption over the limit, abandoned %zu LFU entries",
1391 numPurged));
1392 } else {
1393 // If we hit an error (OOM), do an emergency PurgeAll.
1394 size_t numPurged = PurgeAll(CacheEntry::PURGE_WHOLE, minprogress);
1395 LOG(
1396 (" memory data consumption over the limit, emergency purged all %zu "
1397 "entries",
1398 numPurged));
1402 LOG((" purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds()));
1405 // This function purges ALL expired entries.
1406 size_t CacheStorageService::MemoryPool::PurgeExpired(size_t minprogress) {
1407 MOZ_ASSERT(IsOnManagementThread());
1409 uint32_t now = NowInSeconds();
1411 size_t numPurged = 0;
1412 // Scan for items to purge. mManagedEntries is not sorted but comparing just
1413 // one integer should be faster than anything else, so go scan.
1414 RefPtr<CacheEntry> entry = mManagedEntries.getFirst();
1415 while (entry) {
1416 // Get the next entry before we may be removed from our list.
1417 RefPtr<CacheEntry> nextEntry = entry->getNext();
1419 if (entry->GetExpirationTime() <= now) {
1420 // Purge will modify our mManagedEntries list but we are prepared for it.
1421 if (entry->Purge(CacheEntry::PURGE_WHOLE)) {
1422 numPurged++;
1423 LOG((" purged expired, entry=%p, exptime=%u (now=%u)", entry.get(),
1424 entry->GetExpirationTime(), now));
1428 entry = std::move(nextEntry);
1430 // To have some progress even under load, we do the check only after
1431 // purging at least minprogress items if under pressure.
1432 if ((numPurged >= minprogress || mMemorySize <= Limit()) &&
1433 CacheIOThread::YieldAndRerun()) {
1434 break;
1438 return numPurged;
1441 Result<size_t, nsresult> CacheStorageService::MemoryPool::PurgeByFrecency(
1442 size_t minprogress) {
1443 MOZ_ASSERT(IsOnManagementThread());
1445 // Pretend the limit is 10% lower so that we get rid of more entries at one
1446 // shot and save the sorting below.
1447 uint32_t const memoryLimit = (uint32_t)(Limit() * 0.9);
1448 if (mMemorySize <= memoryLimit) {
1449 return 0;
1452 LOG(("MemoryPool::PurgeByFrecency, len=%zu", mManagedEntries.length()));
1454 // We want to have an array snapshot for sorting and iterating.
1455 struct mayPurgeEntry {
1456 RefPtr<CacheEntry> mEntry;
1457 double mFrecency;
1459 explicit mayPurgeEntry(CacheEntry* aEntry) {
1460 mEntry = aEntry;
1461 mFrecency = aEntry->GetFrecency();
1464 bool operator<(const mayPurgeEntry& aOther) const {
1465 return mFrecency < aOther.mFrecency;
1469 nsTArray<mayPurgeEntry> mayPurgeSorted;
1470 if (!mayPurgeSorted.SetCapacity(mManagedEntries.length(),
1471 mozilla::fallible)) {
1472 return Err(NS_ERROR_OUT_OF_MEMORY);
1475 mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
1477 for (const auto& entry : mManagedEntries) {
1478 // Referenced items cannot be purged and we deliberately want to not look
1479 // at '0' frecency entries, these are new entries and can be ignored.
1480 if (!entry->IsReferenced() && entry->GetFrecency() > 0.0) {
1481 mayPurgeEntry copy(entry);
1482 mayPurgeSorted.AppendElement(std::move(copy));
1486 if (mayPurgeSorted.Length() == 0) {
1487 return 0;
1489 mayPurgeSorted.Sort();
1491 size_t numPurged = 0;
1493 for (auto& checkPurge : mayPurgeSorted) {
1494 if (mMemorySize <= memoryLimit) {
1495 break;
1498 RefPtr<CacheEntry> entry = checkPurge.mEntry;
1500 if (entry->Purge(CacheEntry::PURGE_WHOLE)) {
1501 numPurged++;
1502 LOG((" abandoned (%d), entry=%p, frecency=%1.10f",
1503 CacheEntry::PURGE_WHOLE, entry.get(), entry->GetFrecency()));
1506 if (numPurged >= minprogress && CacheIOThread::YieldAndRerun()) {
1507 LOG(("MemoryPool::PurgeByFrecency interrupted"));
1508 return numPurged;
1512 LOG(("MemoryPool::PurgeByFrecency done"));
1514 return numPurged;
1517 size_t CacheStorageService::MemoryPool::PurgeAll(uint32_t aWhat,
1518 size_t minprogress) {
1519 LOG(("CacheStorageService::MemoryPool::PurgeAll aWhat=%d", aWhat));
1520 MOZ_ASSERT(IsOnManagementThread());
1522 size_t numPurged = 0;
1524 RefPtr<CacheEntry> entry = mManagedEntries.getFirst();
1525 while (entry) {
1526 if (numPurged >= minprogress && CacheIOThread::YieldAndRerun()) break;
1528 // Get the next entry before we may be removed from our list.
1529 RefPtr<CacheEntry> nextEntry = entry->getNext();
1531 if (entry->Purge(aWhat)) {
1532 numPurged++;
1533 LOG((" abandoned entry=%p", entry.get()));
1536 entry = std::move(nextEntry);
1539 return numPurged;
1542 // Methods exposed to and used by CacheStorage.
1544 nsresult CacheStorageService::AddStorageEntry(CacheStorage const* aStorage,
1545 const nsACString& aURI,
1546 const nsACString& aIdExtension,
1547 uint32_t aFlags,
1548 CacheEntryHandle** aResult) {
1549 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1551 NS_ENSURE_ARG(aStorage);
1553 nsAutoCString contextKey;
1554 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1556 return AddStorageEntry(contextKey, aURI, aIdExtension,
1557 aStorage->WriteToDisk(), aStorage->SkipSizeCheck(),
1558 aStorage->Pinning(), aFlags, aResult);
1561 nsresult CacheStorageService::AddStorageEntry(
1562 const nsACString& aContextKey, const nsACString& aURI,
1563 const nsACString& aIdExtension, bool aWriteToDisk, bool aSkipSizeCheck,
1564 bool aPin, uint32_t aFlags, CacheEntryHandle** aResult) {
1565 nsresult rv;
1567 nsAutoCString entryKey;
1568 rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
1569 NS_ENSURE_SUCCESS(rv, rv);
1571 LOG(("CacheStorageService::AddStorageEntry [entryKey=%s, contextKey=%s]",
1572 entryKey.get(), aContextKey.BeginReading()));
1574 RefPtr<CacheEntry> entry;
1575 RefPtr<CacheEntryHandle> handle;
1578 mozilla::MutexAutoLock lock(mLock);
1580 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1582 // Ensure storage table
1583 CacheEntryTable* const entries =
1584 sGlobalEntryTables
1585 ->LookupOrInsertWith(
1586 aContextKey,
1587 [&aContextKey] {
1588 LOG((" new storage entries table for context '%s'",
1589 aContextKey.BeginReading()));
1590 return MakeUnique<CacheEntryTable>(
1591 CacheEntryTable::ALL_ENTRIES);
1593 .get();
1595 bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
1596 if (!entryExists && (aFlags & nsICacheStorage::OPEN_READONLY) &&
1597 (aFlags & nsICacheStorage::OPEN_SECRETLY) &&
1598 StaticPrefs::network_cache_bug1708673()) {
1599 return NS_ERROR_CACHE_KEY_NOT_FOUND;
1602 bool replace = aFlags & nsICacheStorage::OPEN_TRUNCATE;
1604 if (entryExists && !replace) {
1605 // check whether we want to turn this entry to a memory-only.
1606 if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) {
1607 LOG((" entry is persistent but we want mem-only, replacing it"));
1608 replace = true;
1612 // If truncate is demanded, delete and doom the current entry
1613 if (entryExists && replace) {
1614 entries->Remove(entryKey);
1616 LOG((" dooming entry %p for %s because of OPEN_TRUNCATE", entry.get(),
1617 entryKey.get()));
1618 // On purpose called under the lock to prevent races of doom and open on
1619 // I/O thread No need to remove from both memory-only and all-entries
1620 // tables. The new entry will overwrite the shadow entry in its ctor.
1621 entry->DoomAlreadyRemoved();
1623 entry = nullptr;
1624 entryExists = false;
1626 // Would only lead to deleting force-valid timestamp again. We don't need
1627 // the replace information anymore after this point anyway.
1628 replace = false;
1631 // Ensure entry for the particular URL
1632 if (!entryExists) {
1633 // When replacing with a new entry, always remove the current force-valid
1634 // timestamp, this is the only place to do it.
1635 if (replace) {
1636 RemoveEntryForceValid(aContextKey, entryKey);
1639 // Entry is not in the hashtable or has just been truncated...
1640 entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk,
1641 aSkipSizeCheck, aPin);
1642 entries->InsertOrUpdate(entryKey, RefPtr{entry});
1643 LOG((" new entry %p for %s", entry.get(), entryKey.get()));
1646 if (entry) {
1647 // Here, if this entry was not for a long time referenced by any consumer,
1648 // gets again first 'handles count' reference.
1649 handle = entry->NewHandle();
1653 handle.forget(aResult);
1654 return NS_OK;
1657 nsresult CacheStorageService::CheckStorageEntry(CacheStorage const* aStorage,
1658 const nsACString& aURI,
1659 const nsACString& aIdExtension,
1660 bool* aResult) {
1661 nsresult rv;
1663 nsAutoCString contextKey;
1664 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1666 if (!aStorage->WriteToDisk()) {
1667 AppendMemoryStorageTag(contextKey);
1670 LOG(("CacheStorageService::CheckStorageEntry [uri=%s, eid=%s, contextKey=%s]",
1671 aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get()));
1674 mozilla::MutexAutoLock lock(mLock);
1676 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1678 nsAutoCString entryKey;
1679 rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
1680 NS_ENSURE_SUCCESS(rv, rv);
1682 CacheEntryTable* entries;
1683 if ((*aResult = sGlobalEntryTables->Get(contextKey, &entries)) &&
1684 entries->GetWeak(entryKey, aResult)) {
1685 LOG((" found in hash tables"));
1686 return NS_OK;
1690 if (!aStorage->WriteToDisk()) {
1691 // Memory entry, nothing more to do.
1692 LOG((" not found in hash tables"));
1693 return NS_OK;
1696 // Disk entry, not found in the hashtable, check the index.
1697 nsAutoCString fileKey;
1698 rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey);
1700 CacheIndex::EntryStatus status;
1701 rv = CacheIndex::HasEntry(fileKey, &status);
1702 if (NS_FAILED(rv) || status == CacheIndex::DO_NOT_KNOW) {
1703 LOG((" index doesn't know, rv=0x%08" PRIx32, static_cast<uint32_t>(rv)));
1704 return NS_ERROR_NOT_AVAILABLE;
1707 *aResult = status == CacheIndex::EXISTS;
1708 LOG((" %sfound in index", *aResult ? "" : "not "));
1709 return NS_OK;
1712 nsresult CacheStorageService::GetCacheIndexEntryAttrs(
1713 CacheStorage const* aStorage, const nsACString& aURI,
1714 const nsACString& aIdExtension, bool* aHasAltData, uint32_t* aFileSizeKb) {
1715 nsresult rv;
1717 nsAutoCString contextKey;
1718 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1720 LOG(
1721 ("CacheStorageService::GetCacheIndexEntryAttrs [uri=%s, eid=%s, "
1722 "contextKey=%s]",
1723 aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get()));
1725 nsAutoCString fileKey;
1726 rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey);
1727 if (NS_FAILED(rv)) {
1728 return rv;
1731 *aHasAltData = false;
1732 *aFileSizeKb = 0;
1733 auto closure = [&aHasAltData, &aFileSizeKb](const CacheIndexEntry* entry) {
1734 *aHasAltData = entry->GetHasAltData();
1735 *aFileSizeKb = entry->GetFileSize();
1738 CacheIndex::EntryStatus status;
1739 rv = CacheIndex::HasEntry(fileKey, &status, closure);
1740 if (NS_FAILED(rv)) {
1741 return rv;
1744 if (status != CacheIndex::EXISTS) {
1745 return NS_ERROR_CACHE_KEY_NOT_FOUND;
1748 return NS_OK;
1751 namespace {
1753 class CacheEntryDoomByKeyCallback : public CacheFileIOListener,
1754 public nsIRunnable {
1755 public:
1756 NS_DECL_THREADSAFE_ISUPPORTS
1757 NS_DECL_NSIRUNNABLE
1759 explicit CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback* aCallback)
1760 : mCallback(aCallback), mResult(NS_ERROR_NOT_INITIALIZED) {}
1762 private:
1763 virtual ~CacheEntryDoomByKeyCallback();
1765 NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override {
1766 return NS_OK;
1768 NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
1769 nsresult aResult) override {
1770 return NS_OK;
1772 NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
1773 nsresult aResult) override {
1774 return NS_OK;
1776 NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override;
1777 NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override {
1778 return NS_OK;
1780 NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle,
1781 nsresult aResult) override {
1782 return NS_OK;
1785 nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
1786 nsresult mResult;
1789 CacheEntryDoomByKeyCallback::~CacheEntryDoomByKeyCallback() {
1790 if (mCallback) {
1791 ProxyReleaseMainThread("CacheEntryDoomByKeyCallback::mCallback", mCallback);
1795 NS_IMETHODIMP CacheEntryDoomByKeyCallback::OnFileDoomed(
1796 CacheFileHandle* aHandle, nsresult aResult) {
1797 if (!mCallback) return NS_OK;
1799 mResult = aResult;
1800 if (NS_IsMainThread()) {
1801 Run();
1802 } else {
1803 NS_DispatchToMainThread(this);
1806 return NS_OK;
1809 NS_IMETHODIMP CacheEntryDoomByKeyCallback::Run() {
1810 mCallback->OnCacheEntryDoomed(mResult);
1811 return NS_OK;
1814 NS_IMPL_ISUPPORTS(CacheEntryDoomByKeyCallback, CacheFileIOListener,
1815 nsIRunnable);
1817 } // namespace
1819 nsresult CacheStorageService::DoomStorageEntry(
1820 CacheStorage const* aStorage, const nsACString& aURI,
1821 const nsACString& aIdExtension, nsICacheEntryDoomCallback* aCallback) {
1822 LOG(("CacheStorageService::DoomStorageEntry"));
1824 NS_ENSURE_ARG(aStorage);
1826 nsAutoCString contextKey;
1827 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1829 nsAutoCString entryKey;
1830 nsresult rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
1831 NS_ENSURE_SUCCESS(rv, rv);
1833 RefPtr<CacheEntry> entry;
1835 mozilla::MutexAutoLock lock(mLock);
1837 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1839 CacheEntryTable* entries;
1840 if (sGlobalEntryTables->Get(contextKey, &entries)) {
1841 if (entries->Get(entryKey, getter_AddRefs(entry))) {
1842 if (aStorage->WriteToDisk() || !entry->IsUsingDisk()) {
1843 // When evicting from disk storage, purge
1844 // When evicting from memory storage and the entry is memory-only,
1845 // purge
1846 LOG(
1847 (" purging entry %p for %s [storage use disk=%d, entry use "
1848 "disk=%d]",
1849 entry.get(), entryKey.get(), aStorage->WriteToDisk(),
1850 entry->IsUsingDisk()));
1851 entries->Remove(entryKey);
1852 } else {
1853 // Otherwise, leave it
1854 LOG(
1855 (" leaving entry %p for %s [storage use disk=%d, entry use "
1856 "disk=%d]",
1857 entry.get(), entryKey.get(), aStorage->WriteToDisk(),
1858 entry->IsUsingDisk()));
1859 entry = nullptr;
1864 if (!entry) {
1865 RemoveEntryForceValid(contextKey, entryKey);
1869 if (entry) {
1870 LOG((" dooming entry %p for %s", entry.get(), entryKey.get()));
1871 return entry->AsyncDoom(aCallback);
1874 LOG((" no entry loaded for %s", entryKey.get()));
1876 if (aStorage->WriteToDisk()) {
1877 nsAutoCString contextKey;
1878 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1880 rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, entryKey);
1881 NS_ENSURE_SUCCESS(rv, rv);
1883 LOG((" dooming file only for %s", entryKey.get()));
1885 RefPtr<CacheEntryDoomByKeyCallback> callback(
1886 new CacheEntryDoomByKeyCallback(aCallback));
1887 rv = CacheFileIOManager::DoomFileByKey(entryKey, callback);
1888 NS_ENSURE_SUCCESS(rv, rv);
1890 return NS_OK;
1893 class Callback : public Runnable {
1894 public:
1895 explicit Callback(nsICacheEntryDoomCallback* aCallback)
1896 : mozilla::Runnable("Callback"), mCallback(aCallback) {}
1897 NS_IMETHOD Run() override {
1898 mCallback->OnCacheEntryDoomed(NS_ERROR_NOT_AVAILABLE);
1899 return NS_OK;
1901 nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
1904 if (aCallback) {
1905 RefPtr<Runnable> callback = new Callback(aCallback);
1906 return NS_DispatchToMainThread(callback);
1909 return NS_OK;
1912 nsresult CacheStorageService::DoomStorageEntries(
1913 CacheStorage const* aStorage, nsICacheEntryDoomCallback* aCallback) {
1914 LOG(("CacheStorageService::DoomStorageEntries"));
1916 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1917 NS_ENSURE_ARG(aStorage);
1919 nsAutoCString contextKey;
1920 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1922 mozilla::MutexAutoLock lock(mLock);
1924 return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
1925 aStorage->WriteToDisk(), aStorage->Pinning(),
1926 aCallback);
1929 nsresult CacheStorageService::DoomStorageEntries(
1930 const nsACString& aContextKey, nsILoadContextInfo* aContext,
1931 bool aDiskStorage, bool aPinned, nsICacheEntryDoomCallback* aCallback) {
1932 LOG(("CacheStorageService::DoomStorageEntries [context=%s]",
1933 aContextKey.BeginReading()));
1935 mLock.AssertCurrentThreadOwns();
1937 NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
1939 nsAutoCString memoryStorageID(aContextKey);
1940 AppendMemoryStorageTag(memoryStorageID);
1942 if (aDiskStorage) {
1943 LOG((" dooming disk+memory storage of %s", aContextKey.BeginReading()));
1945 // Walk one by one and remove entries according their pin status
1946 CacheEntryTable *diskEntries, *memoryEntries;
1947 if (sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
1948 sGlobalEntryTables->Get(memoryStorageID, &memoryEntries);
1950 for (auto iter = diskEntries->Iter(); !iter.Done(); iter.Next()) {
1951 auto entry = iter.Data();
1952 if (entry->DeferOrBypassRemovalOnPinStatus(aPinned)) {
1953 continue;
1956 if (memoryEntries) {
1957 RemoveExactEntry(memoryEntries, iter.Key(), entry, false);
1959 iter.Remove();
1963 if (aContext && !aContext->IsPrivate()) {
1964 LOG((" dooming disk entries"));
1965 CacheFileIOManager::EvictByContext(aContext, aPinned, u""_ns);
1967 } else {
1968 LOG((" dooming memory-only storage of %s", aContextKey.BeginReading()));
1970 // Remove the memory entries table from the global tables.
1971 // Since we store memory entries also in the disk entries table
1972 // we need to remove the memory entries from the disk table one
1973 // by one manually.
1974 mozilla::UniquePtr<CacheEntryTable> memoryEntries;
1975 sGlobalEntryTables->Remove(memoryStorageID, &memoryEntries);
1977 CacheEntryTable* diskEntries;
1978 if (memoryEntries && sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
1979 for (const auto& memoryEntry : *memoryEntries) {
1980 const auto& entry = memoryEntry.GetData();
1981 RemoveExactEntry(diskEntries, memoryEntry.GetKey(), entry, false);
1987 mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1989 if (aContext) {
1990 for (auto iter = mForcedValidEntries.Iter(); !iter.Done(); iter.Next()) {
1991 bool matches;
1992 DebugOnly<nsresult> rv = CacheFileUtils::KeyMatchesLoadContextInfo(
1993 iter.Key(), aContext, &matches);
1994 MOZ_ASSERT(NS_SUCCEEDED(rv));
1996 if (matches) {
1997 iter.Remove();
2000 } else {
2001 mForcedValidEntries.Clear();
2005 // An artificial callback. This is a candidate for removal tho. In the new
2006 // cache any 'doom' or 'evict' function ensures that the entry or entries
2007 // being doomed is/are not accessible after the function returns. So there is
2008 // probably no need for a callback - has no meaning. But for compatibility
2009 // with the old cache that is still in the tree we keep the API similar to be
2010 // able to make tests as well as other consumers work for now.
2011 class Callback : public Runnable {
2012 public:
2013 explicit Callback(nsICacheEntryDoomCallback* aCallback)
2014 : mozilla::Runnable("Callback"), mCallback(aCallback) {}
2015 NS_IMETHOD Run() override {
2016 mCallback->OnCacheEntryDoomed(NS_OK);
2017 return NS_OK;
2019 nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
2022 if (aCallback) {
2023 RefPtr<Runnable> callback = new Callback(aCallback);
2024 return NS_DispatchToMainThread(callback);
2027 return NS_OK;
2030 nsresult CacheStorageService::WalkStorageEntries(
2031 CacheStorage const* aStorage, bool aVisitEntries,
2032 nsICacheStorageVisitor* aVisitor) {
2033 LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]",
2034 aVisitor, aVisitEntries));
2035 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
2037 NS_ENSURE_ARG(aStorage);
2039 if (aStorage->WriteToDisk()) {
2040 RefPtr<WalkDiskCacheRunnable> event = new WalkDiskCacheRunnable(
2041 aStorage->LoadInfo(), aVisitEntries, aVisitor);
2042 return event->Walk();
2045 RefPtr<WalkMemoryCacheRunnable> event = new WalkMemoryCacheRunnable(
2046 aStorage->LoadInfo(), aVisitEntries, aVisitor);
2047 return event->Walk();
2050 void CacheStorageService::CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
2051 const nsACString& aIdExtension,
2052 const nsACString& aURISpec) {
2053 nsAutoCString contextKey;
2054 CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
2056 nsAutoCString entryKey;
2057 CacheEntry::HashingKey(""_ns, aIdExtension, aURISpec, entryKey);
2059 mozilla::MutexAutoLock lock(mLock);
2061 if (mShutdown) {
2062 return;
2065 CacheEntryTable* entries;
2066 RefPtr<CacheEntry> entry;
2068 if (sGlobalEntryTables->Get(contextKey, &entries) &&
2069 entries->Get(entryKey, getter_AddRefs(entry))) {
2070 if (entry->IsFileDoomed()) {
2071 // Need to remove under the lock to avoid possible race leading
2072 // to duplication of the entry per its key.
2073 RemoveExactEntry(entries, entryKey, entry, false);
2074 entry->DoomAlreadyRemoved();
2077 // Entry found, but it's not the entry that has been found doomed
2078 // by the lower eviction layer. Just leave everything unchanged.
2079 return;
2082 RemoveEntryForceValid(contextKey, entryKey);
2085 bool CacheStorageService::GetCacheEntryInfo(
2086 nsILoadContextInfo* aLoadContextInfo, const nsACString& aIdExtension,
2087 const nsACString& aURISpec, EntryInfoCallback* aCallback) {
2088 nsAutoCString contextKey;
2089 CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
2091 nsAutoCString entryKey;
2092 CacheEntry::HashingKey(""_ns, aIdExtension, aURISpec, entryKey);
2094 RefPtr<CacheEntry> entry;
2096 mozilla::MutexAutoLock lock(mLock);
2098 if (mShutdown) {
2099 return false;
2102 CacheEntryTable* entries;
2103 if (!sGlobalEntryTables->Get(contextKey, &entries)) {
2104 return false;
2107 if (!entries->Get(entryKey, getter_AddRefs(entry))) {
2108 return false;
2112 GetCacheEntryInfo(entry, aCallback);
2113 return true;
2116 // static
2117 void CacheStorageService::GetCacheEntryInfo(CacheEntry* aEntry,
2118 EntryInfoCallback* aCallback) {
2119 nsCString const uriSpec = aEntry->GetURI();
2120 nsCString const enhanceId = aEntry->GetEnhanceID();
2122 nsAutoCString entryKey;
2123 aEntry->HashingKeyWithStorage(entryKey);
2125 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(entryKey);
2127 uint32_t dataSize;
2128 if (NS_FAILED(aEntry->GetStorageDataSize(&dataSize))) {
2129 dataSize = 0;
2131 int64_t altDataSize;
2132 if (NS_FAILED(aEntry->GetAltDataSize(&altDataSize))) {
2133 altDataSize = 0;
2135 uint32_t fetchCount;
2136 if (NS_FAILED(aEntry->GetFetchCount(&fetchCount))) {
2137 fetchCount = 0;
2139 uint32_t lastModified;
2140 if (NS_FAILED(aEntry->GetLastModified(&lastModified))) {
2141 lastModified = 0;
2143 uint32_t expirationTime;
2144 if (NS_FAILED(aEntry->GetExpirationTime(&expirationTime))) {
2145 expirationTime = 0;
2148 aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, altDataSize, fetchCount,
2149 lastModified, expirationTime, aEntry->IsPinned(),
2150 info);
2153 // static
2154 uint32_t CacheStorageService::CacheQueueSize(bool highPriority) {
2155 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
2156 // The thread will be null at shutdown.
2157 if (!thread) {
2158 return 0;
2160 return thread->QueueSize(highPriority);
2163 // Telemetry collection
2165 namespace {
2167 bool TelemetryEntryKey(CacheEntry const* entry, nsAutoCString& key) {
2168 nsAutoCString entryKey;
2169 nsresult rv = entry->HashingKey(entryKey);
2170 if (NS_FAILED(rv)) return false;
2172 if (entry->GetStorageID().IsEmpty()) {
2173 // Hopefully this will be const-copied, saves some memory
2174 key = entryKey;
2175 } else {
2176 key.Assign(entry->GetStorageID());
2177 key.Append(':');
2178 key.Append(entryKey);
2181 return true;
2184 } // namespace
2186 void CacheStorageService::TelemetryPrune(TimeStamp& now) {
2187 static TimeDuration const oneMinute = TimeDuration::FromSeconds(60);
2188 static TimeStamp dontPruneUntil = now + oneMinute;
2189 if (now < dontPruneUntil) return;
2191 static TimeDuration const fifteenMinutes = TimeDuration::FromSeconds(900);
2192 for (auto iter = mPurgeTimeStamps.Iter(); !iter.Done(); iter.Next()) {
2193 if (now - iter.Data() > fifteenMinutes) {
2194 // We are not interested in resurrection of entries after 15 minutes
2195 // of time. This is also the limit for the telemetry.
2196 iter.Remove();
2199 dontPruneUntil = now + oneMinute;
2202 void CacheStorageService::TelemetryRecordEntryCreation(
2203 CacheEntry const* entry) {
2204 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
2206 nsAutoCString key;
2207 if (!TelemetryEntryKey(entry, key)) return;
2209 TimeStamp now = TimeStamp::NowLoRes();
2210 TelemetryPrune(now);
2212 // When an entry is craeted (registered actually) we check if there is
2213 // a timestamp marked when this very same cache entry has been removed
2214 // (deregistered) because of over-memory-limit purging. If there is such
2215 // a timestamp found accumulate telemetry on how long the entry was away.
2216 TimeStamp timeStamp;
2217 if (!mPurgeTimeStamps.Get(key, &timeStamp)) return;
2219 mPurgeTimeStamps.Remove(key);
2221 Telemetry::AccumulateTimeDelta(Telemetry::HTTP_CACHE_ENTRY_RELOAD_TIME,
2222 timeStamp, TimeStamp::NowLoRes());
2225 void CacheStorageService::TelemetryRecordEntryRemoval(CacheEntry* entry) {
2226 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
2228 // Doomed entries must not be considered, we are only interested in purged
2229 // entries. Note that the mIsDoomed flag is always set before deregistration
2230 // happens.
2231 if (entry->IsDoomed()) return;
2233 nsAutoCString key;
2234 if (!TelemetryEntryKey(entry, key)) return;
2236 // When an entry is removed (deregistered actually) we put a timestamp for
2237 // this entry to the hashtable so that when the entry is created (registered)
2238 // again we know how long it was away. Also accumulate number of AsyncOpen
2239 // calls on the entry, this tells us how efficiently the pool actually works.
2241 TimeStamp now = TimeStamp::NowLoRes();
2242 TelemetryPrune(now);
2243 mPurgeTimeStamps.InsertOrUpdate(key, now);
2245 Telemetry::Accumulate(Telemetry::HTTP_CACHE_ENTRY_REUSE_COUNT,
2246 entry->UseCount());
2247 Telemetry::AccumulateTimeDelta(Telemetry::HTTP_CACHE_ENTRY_ALIVE_TIME,
2248 entry->LoadStart(), TimeStamp::NowLoRes());
2251 // nsIMemoryReporter
2253 size_t CacheStorageService::SizeOfExcludingThis(
2254 mozilla::MallocSizeOf mallocSizeOf) const {
2255 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
2257 size_t n = 0;
2258 // The elemets are referenced by sGlobalEntryTables and are reported from
2259 // there.
2261 // Entries reported manually in CacheStorageService::CollectReports callback
2262 if (sGlobalEntryTables) {
2263 n += sGlobalEntryTables->ShallowSizeOfIncludingThis(mallocSizeOf);
2265 n += mPurgeTimeStamps.SizeOfExcludingThis(mallocSizeOf);
2267 return n;
2270 size_t CacheStorageService::SizeOfIncludingThis(
2271 mozilla::MallocSizeOf mallocSizeOf) const {
2272 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
2275 NS_IMETHODIMP
2276 CacheStorageService::CollectReports(nsIHandleReportCallback* aHandleReport,
2277 nsISupports* aData, bool aAnonymize) {
2278 MutexAutoLock lock(mLock);
2279 MOZ_COLLECT_REPORT("explicit/network/cache2/io", KIND_HEAP, UNITS_BYTES,
2280 CacheFileIOManager::SizeOfIncludingThis(MallocSizeOf),
2281 "Memory used by the cache IO manager.");
2283 MOZ_COLLECT_REPORT("explicit/network/cache2/index", KIND_HEAP, UNITS_BYTES,
2284 CacheIndex::SizeOfIncludingThis(MallocSizeOf),
2285 "Memory used by the cache index.");
2287 // Report the service instance, this doesn't report entries, done lower
2288 MOZ_COLLECT_REPORT("explicit/network/cache2/service", KIND_HEAP, UNITS_BYTES,
2289 SizeOfIncludingThis(MallocSizeOf),
2290 "Memory used by the cache storage service.");
2292 // Report all entries, each storage separately (by the context key)
2294 // References are:
2295 // sGlobalEntryTables to N CacheEntryTable
2296 // CacheEntryTable to N CacheEntry
2297 // CacheEntry to 1 CacheFile
2298 // CacheFile to
2299 // N CacheFileChunk (keeping the actual data)
2300 // 1 CacheFileMetadata (keeping http headers etc.)
2301 // 1 CacheFileOutputStream
2302 // N CacheFileInputStream
2303 if (sGlobalEntryTables) {
2304 for (const auto& globalEntry : *sGlobalEntryTables) {
2305 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
2307 CacheEntryTable* table = globalEntry.GetWeak();
2309 size_t size = 0;
2310 mozilla::MallocSizeOf mallocSizeOf = CacheStorageService::MallocSizeOf;
2312 size += table->ShallowSizeOfIncludingThis(mallocSizeOf);
2313 for (const auto& tableEntry : *table) {
2314 size += tableEntry.GetKey().SizeOfExcludingThisIfUnshared(mallocSizeOf);
2316 // Bypass memory-only entries, those will be reported when iterating the
2317 // memory only table. Memory-only entries are stored in both ALL_ENTRIES
2318 // and MEMORY_ONLY hashtables.
2319 RefPtr<mozilla::net::CacheEntry> const& entry = tableEntry.GetData();
2320 if (table->Type() == CacheEntryTable::MEMORY_ONLY ||
2321 entry->IsUsingDisk()) {
2322 size += entry->SizeOfIncludingThis(mallocSizeOf);
2326 aHandleReport->Callback(
2327 ""_ns,
2328 nsPrintfCString(
2329 "explicit/network/cache2/%s-storage(%s)",
2330 table->Type() == CacheEntryTable::MEMORY_ONLY ? "memory" : "disk",
2331 aAnonymize ? "<anonymized>"
2332 : globalEntry.GetKey().BeginReading()),
2333 nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, size,
2334 "Memory used by the cache storage."_ns, aData);
2338 return NS_OK;
2341 // nsICacheTesting
2343 NS_IMETHODIMP
2344 CacheStorageService::IOThreadSuspender::Run() {
2345 MonitorAutoLock mon(mMon);
2346 while (!mSignaled) {
2347 mon.Wait();
2349 return NS_OK;
2352 void CacheStorageService::IOThreadSuspender::Notify() {
2353 MonitorAutoLock mon(mMon);
2354 mSignaled = true;
2355 mon.Notify();
2358 NS_IMETHODIMP
2359 CacheStorageService::SuspendCacheIOThread(uint32_t aLevel) {
2360 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
2361 if (!thread) {
2362 return NS_ERROR_NOT_AVAILABLE;
2365 MOZ_ASSERT(!mActiveIOSuspender);
2366 mActiveIOSuspender = new IOThreadSuspender();
2367 return thread->Dispatch(mActiveIOSuspender, aLevel);
2370 NS_IMETHODIMP
2371 CacheStorageService::ResumeCacheIOThread() {
2372 MOZ_ASSERT(mActiveIOSuspender);
2374 RefPtr<IOThreadSuspender> suspender;
2375 suspender.swap(mActiveIOSuspender);
2376 suspender->Notify();
2377 return NS_OK;
2380 NS_IMETHODIMP
2381 CacheStorageService::Flush(nsIObserver* aObserver) {
2382 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
2383 if (!thread) {
2384 return NS_ERROR_NOT_AVAILABLE;
2387 nsCOMPtr<nsIObserverService> observerService =
2388 mozilla::services::GetObserverService();
2389 if (!observerService) {
2390 return NS_ERROR_NOT_AVAILABLE;
2393 // Adding as weak, the consumer is responsible to keep the reference
2394 // until notified.
2395 observerService->AddObserver(aObserver, "cacheservice:purge-memory-pools",
2396 false);
2398 // This runnable will do the purging and when done, notifies the above
2399 // observer. We dispatch it to the CLOSE level, so all data writes scheduled
2400 // up to this time will be done before this purging happens.
2401 RefPtr<CacheStorageService::PurgeFromMemoryRunnable> r =
2402 new CacheStorageService::PurgeFromMemoryRunnable(this,
2403 CacheEntry::PURGE_WHOLE);
2405 return thread->Dispatch(r, CacheIOThread::WRITE);
2408 } // namespace mozilla::net