Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / netwerk / cache2 / CacheStorageService.cpp
blobd26bc3446b567556d591892c778cd0ea04fd835e
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/glean/NetwerkMetrics.h"
34 #include "mozilla/Services.h"
35 #include "mozilla/StoragePrincipalHelper.h"
36 #include "mozilla/IntegerPrintfMacros.h"
37 #include "mozilla/Telemetry.h"
38 #include "mozilla/StaticPrefs_network.h"
40 namespace mozilla::net {
42 namespace {
44 void AppendMemoryStorageTag(nsAutoCString& key) {
45 // Using DEL as the very last ascii-7 character we can use in the list of
46 // attributes
47 key.Append('\x7f');
48 key.Append(',');
51 } // namespace
53 // Not defining as static or class member of CacheStorageService since
54 // it would otherwise need to include CacheEntry.h and that then would
55 // need to be exported to make nsNetModule.cpp compilable.
56 using GlobalEntryTables = nsClassHashtable<nsCStringHashKey, CacheEntryTable>;
58 /**
59 * Keeps tables of entries. There is one entries table for each distinct load
60 * context type. The distinction is based on following load context info
61 * states: <isPrivate|isAnon|inIsolatedMozBrowser> which builds a mapping
62 * key.
64 * Thread-safe to access, protected by the service mutex.
66 static GlobalEntryTables* sGlobalEntryTables;
68 CacheMemoryConsumer::CacheMemoryConsumer(uint32_t aFlags) {
69 StoreFlags(aFlags);
72 void CacheMemoryConsumer::DoMemoryReport(uint32_t aCurrentSize) {
73 if (!(LoadFlags() & DONT_REPORT) && CacheStorageService::Self()) {
74 CacheStorageService::Self()->OnMemoryConsumptionChange(this, aCurrentSize);
78 CacheStorageService::MemoryPool::MemoryPool(EType aType) : mType(aType) {}
80 CacheStorageService::MemoryPool::~MemoryPool() {
81 if (mMemorySize != 0) {
82 NS_ERROR(
83 "Network cache reported memory consumption is not at 0, probably "
84 "leaking?");
88 uint32_t CacheStorageService::MemoryPool::Limit() const {
89 uint32_t limit = 0;
91 switch (mType) {
92 case DISK:
93 limit = CacheObserver::MetadataMemoryLimit();
94 break;
95 case MEMORY:
96 limit = CacheObserver::MemoryCacheCapacity();
97 break;
98 default:
99 MOZ_CRASH("Bad pool type");
102 static const uint32_t kMaxLimit = 0x3FFFFF;
103 if (limit > kMaxLimit) {
104 LOG((" a memory limit (%u) is unexpectedly high, clipping to %u", limit,
105 kMaxLimit));
106 limit = kMaxLimit;
109 return limit << 10;
112 NS_IMPL_ISUPPORTS(CacheStorageService, nsICacheStorageService,
113 nsIMemoryReporter, nsITimerCallback, nsICacheTesting,
114 nsINamed)
116 CacheStorageService* CacheStorageService::sSelf = nullptr;
118 CacheStorageService::CacheStorageService() {
119 CacheFileIOManager::Init();
121 MOZ_ASSERT(XRE_IsParentProcess());
122 MOZ_ASSERT(!sSelf);
124 sSelf = this;
125 sGlobalEntryTables = new GlobalEntryTables();
127 RegisterStrongMemoryReporter(this);
130 CacheStorageService::~CacheStorageService() {
131 LOG(("CacheStorageService::~CacheStorageService"));
132 sSelf = nullptr;
135 void CacheStorageService::Shutdown() {
136 mozilla::MutexAutoLock lock(mLock);
138 if (mShutdown) return;
140 LOG(("CacheStorageService::Shutdown - start"));
142 mShutdown = true;
144 nsCOMPtr<nsIRunnable> event =
145 NewRunnableMethod("net::CacheStorageService::ShutdownBackground", this,
146 &CacheStorageService::ShutdownBackground);
147 Dispatch(event);
149 #ifdef NS_FREE_PERMANENT_DATA
150 sGlobalEntryTables->Clear();
151 delete sGlobalEntryTables;
152 #endif
153 sGlobalEntryTables = nullptr;
155 LOG(("CacheStorageService::Shutdown - done"));
158 void CacheStorageService::ShutdownBackground() {
159 LOG(("CacheStorageService::ShutdownBackground - start"));
161 MOZ_ASSERT(IsOnManagementThread());
164 mozilla::MutexAutoLock lock(mLock);
166 // Cancel purge timer to avoid leaking.
167 if (mPurgeTimer) {
168 LOG((" freeing the timer"));
169 mPurgeTimer->Cancel();
173 #ifdef NS_FREE_PERMANENT_DATA
174 Pool(MemoryPool::EType::DISK).mManagedEntries.clear();
175 Pool(MemoryPool::EType::MEMORY).mManagedEntries.clear();
176 #endif
178 LOG(("CacheStorageService::ShutdownBackground - done"));
181 // Internal management methods
183 namespace {
185 // WalkCacheRunnable
186 // Base class for particular storage entries visiting
187 class WalkCacheRunnable : public Runnable,
188 public CacheStorageService::EntryInfoCallback {
189 protected:
190 WalkCacheRunnable(nsICacheStorageVisitor* aVisitor, bool aVisitEntries)
191 : Runnable("net::WalkCacheRunnable"),
192 mService(CacheStorageService::Self()),
193 mCallback(aVisitor) {
194 MOZ_ASSERT(NS_IsMainThread());
195 StoreNotifyStorage(true);
196 StoreVisitEntries(aVisitEntries);
199 virtual ~WalkCacheRunnable() {
200 if (mCallback) {
201 ProxyReleaseMainThread("WalkCacheRunnable::mCallback", mCallback);
205 RefPtr<CacheStorageService> mService;
206 nsCOMPtr<nsICacheStorageVisitor> mCallback;
208 uint64_t mSize{0};
210 // clang-format off
211 MOZ_ATOMIC_BITFIELDS(mAtomicBitfields, 8, (
212 (bool, NotifyStorage, 1),
213 (bool, VisitEntries, 1)
215 // clang-format on
217 Atomic<bool> mCancel{false};
220 // WalkMemoryCacheRunnable
221 // Responsible to visit memory storage and walk
222 // all entries on it asynchronously.
223 class WalkMemoryCacheRunnable : public WalkCacheRunnable {
224 public:
225 WalkMemoryCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries,
226 nsICacheStorageVisitor* aVisitor)
227 : WalkCacheRunnable(aVisitor, aVisitEntries) {
228 CacheFileUtils::AppendKeyPrefix(aLoadInfo, mContextKey);
229 MOZ_ASSERT(NS_IsMainThread());
232 nsresult Walk() { return mService->Dispatch(this); }
234 private:
235 NS_IMETHOD Run() override {
236 if (CacheStorageService::IsOnManagementThread()) {
237 LOG(("WalkMemoryCacheRunnable::Run - collecting [this=%p]", this));
238 // First, walk, count and grab all entries from the storage
240 mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
242 if (!CacheStorageService::IsRunning()) return NS_ERROR_NOT_INITIALIZED;
244 // Count the entries to allocate the array memory all at once.
245 size_t numEntries = 0;
246 for (const auto& entries : sGlobalEntryTables->Values()) {
247 if (entries->Type() != CacheEntryTable::MEMORY_ONLY) {
248 continue;
250 numEntries += entries->Values().Count();
252 mEntryArray.SetCapacity(numEntries);
254 // Collect the entries.
255 for (const auto& entries : sGlobalEntryTables->Values()) {
256 if (entries->Type() != CacheEntryTable::MEMORY_ONLY) {
257 continue;
260 for (CacheEntry* entry : entries->Values()) {
261 MOZ_ASSERT(!entry->IsUsingDisk());
263 mSize += entry->GetMetadataMemoryConsumption();
265 int64_t size;
266 if (NS_SUCCEEDED(entry->GetDataSize(&size))) {
267 mSize += size;
269 mEntryArray.AppendElement(entry);
273 // Next, we dispatch to the main thread
274 } else if (NS_IsMainThread()) {
275 LOG(("WalkMemoryCacheRunnable::Run - notifying [this=%p]", this));
277 if (LoadNotifyStorage()) {
278 LOG((" storage"));
280 uint64_t capacity = CacheObserver::MemoryCacheCapacity();
281 capacity <<= 10; // kilobytes to bytes
283 // Second, notify overall storage info
284 mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize, capacity,
285 nullptr);
286 if (!LoadVisitEntries()) return NS_OK; // done
288 StoreNotifyStorage(false);
290 } else {
291 LOG((" entry [left=%zu, canceled=%d]", mEntryArray.Length(),
292 (bool)mCancel));
294 // Third, notify each entry until depleted or canceled.
295 if (mNextEntryIdx >= mEntryArray.Length() || mCancel) {
296 mCallback->OnCacheEntryVisitCompleted();
297 return NS_OK; // done
300 // Grab the next entry.
301 RefPtr<CacheEntry> entry = std::move(mEntryArray[mNextEntryIdx++]);
303 // Invokes this->OnEntryInfo, that calls the callback with all
304 // information of the entry.
305 CacheStorageService::GetCacheEntryInfo(entry, this);
307 } else {
308 MOZ_CRASH("Bad thread");
309 return NS_ERROR_FAILURE;
312 NS_DispatchToMainThread(this);
313 return NS_OK;
316 virtual ~WalkMemoryCacheRunnable() {
317 if (mCallback) {
318 ProxyReleaseMainThread("WalkMemoryCacheRunnable::mCallback", mCallback);
322 virtual void OnEntryInfo(const nsACString& aURISpec,
323 const nsACString& aIdEnhance, int64_t aDataSize,
324 int64_t aAltDataSize, uint32_t aFetchCount,
325 uint32_t aLastModifiedTime, uint32_t aExpirationTime,
326 bool aPinned, nsILoadContextInfo* aInfo) override {
327 nsresult rv;
329 nsCOMPtr<nsIURI> uri;
330 rv = NS_NewURI(getter_AddRefs(uri), aURISpec);
331 if (NS_FAILED(rv)) {
332 return;
335 rv = mCallback->OnCacheEntryInfo(uri, aIdEnhance, aDataSize, aAltDataSize,
336 aFetchCount, aLastModifiedTime,
337 aExpirationTime, aPinned, aInfo);
338 if (NS_FAILED(rv)) {
339 LOG((" callback failed, canceling the walk"));
340 mCancel = true;
344 private:
345 nsCString mContextKey;
346 nsTArray<RefPtr<CacheEntry>> mEntryArray;
347 size_t mNextEntryIdx{0};
350 // WalkDiskCacheRunnable
351 // Using the cache index information to get the list of files per context.
352 class WalkDiskCacheRunnable : public WalkCacheRunnable {
353 public:
354 WalkDiskCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries,
355 nsICacheStorageVisitor* aVisitor)
356 : WalkCacheRunnable(aVisitor, aVisitEntries),
357 mLoadInfo(aLoadInfo),
358 mPass(COLLECT_STATS),
359 mCount(0) {}
361 nsresult Walk() {
362 // TODO, bug 998693
363 // Initial index build should be forced here so that about:cache soon
364 // after startup gives some meaningfull results.
366 // Dispatch to the INDEX level in hope that very recent cache entries
367 // information gets to the index list before we grab the index iterator
368 // for the first time. This tries to avoid miss of entries that has
369 // been created right before the visit is required.
370 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
371 NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED);
373 return thread->Dispatch(this, CacheIOThread::INDEX);
376 private:
377 // Invokes OnCacheEntryInfo callback for each single found entry.
378 // There is one instance of this class per one entry.
379 class OnCacheEntryInfoRunnable : public Runnable {
380 public:
381 explicit OnCacheEntryInfoRunnable(WalkDiskCacheRunnable* aWalker)
382 : Runnable("net::WalkDiskCacheRunnable::OnCacheEntryInfoRunnable"),
383 mWalker(aWalker) {}
385 NS_IMETHOD Run() override {
386 MOZ_ASSERT(NS_IsMainThread());
388 nsresult rv;
390 nsCOMPtr<nsIURI> uri;
391 rv = NS_NewURI(getter_AddRefs(uri), mURISpec);
392 if (NS_FAILED(rv)) {
393 return NS_OK;
396 rv = mWalker->mCallback->OnCacheEntryInfo(
397 uri, mIdEnhance, mDataSize, mAltDataSize, mFetchCount,
398 mLastModifiedTime, mExpirationTime, mPinned, mInfo);
399 if (NS_FAILED(rv)) {
400 mWalker->mCancel = true;
403 return NS_OK;
406 RefPtr<WalkDiskCacheRunnable> mWalker;
408 nsCString mURISpec;
409 nsCString mIdEnhance;
410 int64_t mDataSize{0};
411 int64_t mAltDataSize{0};
412 uint32_t mFetchCount{0};
413 uint32_t mLastModifiedTime{0};
414 uint32_t mExpirationTime{0};
415 bool mPinned{false};
416 nsCOMPtr<nsILoadContextInfo> mInfo;
419 NS_IMETHOD Run() override {
420 // The main loop
421 nsresult rv;
423 if (CacheStorageService::IsOnManagementThread()) {
424 switch (mPass) {
425 case COLLECT_STATS:
426 // Get quickly the cache stats.
427 uint32_t size;
428 rv = CacheIndex::GetCacheStats(mLoadInfo, &size, &mCount);
429 if (NS_FAILED(rv)) {
430 if (LoadVisitEntries()) {
431 // both onStorageInfo and onCompleted are expected
432 NS_DispatchToMainThread(this);
434 return NS_DispatchToMainThread(this);
437 mSize = static_cast<uint64_t>(size) << 10;
439 // Invoke onCacheStorageInfo with valid information.
440 NS_DispatchToMainThread(this);
442 if (!LoadVisitEntries()) {
443 return NS_OK; // done
446 mPass = ITERATE_METADATA;
447 [[fallthrough]];
449 case ITERATE_METADATA:
450 // Now grab the context iterator.
451 if (!mIter) {
452 rv =
453 CacheIndex::GetIterator(mLoadInfo, true, getter_AddRefs(mIter));
454 if (NS_FAILED(rv)) {
455 // Invoke onCacheEntryVisitCompleted now
456 return NS_DispatchToMainThread(this);
460 while (!mCancel && !CacheObserver::ShuttingDown()) {
461 if (CacheIOThread::YieldAndRerun()) return NS_OK;
463 SHA1Sum::Hash hash;
464 rv = mIter->GetNextHash(&hash);
465 if (NS_FAILED(rv)) break; // done (or error?)
467 // This synchronously invokes OnEntryInfo on this class where we
468 // redispatch to the main thread for the consumer callback.
469 CacheFileIOManager::GetEntryInfo(&hash, this);
472 // Invoke onCacheEntryVisitCompleted on the main thread
473 NS_DispatchToMainThread(this);
475 } else if (NS_IsMainThread()) {
476 if (LoadNotifyStorage()) {
477 nsCOMPtr<nsIFile> dir;
478 CacheFileIOManager::GetCacheDirectory(getter_AddRefs(dir));
479 uint64_t capacity = CacheObserver::DiskCacheCapacity();
480 capacity <<= 10; // kilobytes to bytes
481 mCallback->OnCacheStorageInfo(mCount, mSize, capacity, dir);
482 StoreNotifyStorage(false);
483 } else {
484 mCallback->OnCacheEntryVisitCompleted();
486 } else {
487 MOZ_CRASH("Bad thread");
488 return NS_ERROR_FAILURE;
491 return NS_OK;
494 virtual void OnEntryInfo(const nsACString& aURISpec,
495 const nsACString& aIdEnhance, int64_t aDataSize,
496 int64_t aAltDataSize, uint32_t aFetchCount,
497 uint32_t aLastModifiedTime, uint32_t aExpirationTime,
498 bool aPinned, nsILoadContextInfo* aInfo) override {
499 // Called directly from CacheFileIOManager::GetEntryInfo.
501 // Invoke onCacheEntryInfo on the main thread for this entry.
502 RefPtr<OnCacheEntryInfoRunnable> info = new OnCacheEntryInfoRunnable(this);
503 info->mURISpec = aURISpec;
504 info->mIdEnhance = aIdEnhance;
505 info->mDataSize = aDataSize;
506 info->mAltDataSize = aAltDataSize;
507 info->mFetchCount = aFetchCount;
508 info->mLastModifiedTime = aLastModifiedTime;
509 info->mExpirationTime = aExpirationTime;
510 info->mPinned = aPinned;
511 info->mInfo = aInfo;
513 NS_DispatchToMainThread(info);
516 RefPtr<nsILoadContextInfo> mLoadInfo;
517 enum {
518 // First, we collect stats for the load context.
519 COLLECT_STATS,
521 // Second, if demanded, we iterate over the entries gethered
522 // from the iterator and call CacheFileIOManager::GetEntryInfo
523 // for each found entry.
524 ITERATE_METADATA,
525 } mPass;
527 RefPtr<CacheIndexIterator> mIter;
528 uint32_t mCount;
531 } // namespace
533 void CacheStorageService::DropPrivateBrowsingEntries() {
534 mozilla::MutexAutoLock lock(mLock);
536 if (mShutdown) return;
538 nsTArray<nsCString> keys;
539 for (const nsACString& key : sGlobalEntryTables->Keys()) {
540 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(key);
541 if (info && info->IsPrivate()) {
542 keys.AppendElement(key);
546 for (uint32_t i = 0; i < keys.Length(); ++i) {
547 DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
551 // Helper methods
553 // static
554 bool CacheStorageService::IsOnManagementThread() {
555 RefPtr<CacheStorageService> service = Self();
556 if (!service) return false;
558 nsCOMPtr<nsIEventTarget> target = service->Thread();
559 if (!target) return false;
561 bool currentThread;
562 nsresult rv = target->IsOnCurrentThread(&currentThread);
563 return NS_SUCCEEDED(rv) && currentThread;
566 already_AddRefed<nsIEventTarget> CacheStorageService::Thread() const {
567 return CacheFileIOManager::IOTarget();
570 nsresult CacheStorageService::Dispatch(nsIRunnable* aEvent) {
571 RefPtr<CacheIOThread> cacheIOThread = CacheFileIOManager::IOThread();
572 if (!cacheIOThread) return NS_ERROR_NOT_AVAILABLE;
574 return cacheIOThread->Dispatch(aEvent, CacheIOThread::MANAGEMENT);
577 namespace CacheStorageEvictHelper {
579 nsresult ClearStorage(bool const aPrivate, bool const aAnonymous,
580 OriginAttributes& aOa) {
581 nsresult rv;
583 aOa.SyncAttributesWithPrivateBrowsing(aPrivate);
584 RefPtr<LoadContextInfo> info = GetLoadContextInfo(aAnonymous, aOa);
586 nsCOMPtr<nsICacheStorage> storage;
587 RefPtr<CacheStorageService> service = CacheStorageService::Self();
588 NS_ENSURE_TRUE(service, NS_ERROR_FAILURE);
590 // Clear disk storage
591 rv = service->DiskCacheStorage(info, getter_AddRefs(storage));
592 NS_ENSURE_SUCCESS(rv, rv);
593 rv = storage->AsyncEvictStorage(nullptr);
594 NS_ENSURE_SUCCESS(rv, rv);
596 // Clear memory storage
597 rv = service->MemoryCacheStorage(info, getter_AddRefs(storage));
598 NS_ENSURE_SUCCESS(rv, rv);
599 rv = storage->AsyncEvictStorage(nullptr);
600 NS_ENSURE_SUCCESS(rv, rv);
602 return NS_OK;
605 nsresult Run(OriginAttributes& aOa) {
606 nsresult rv;
608 // Clear all [private X anonymous] combinations
609 rv = ClearStorage(false, false, aOa);
610 NS_ENSURE_SUCCESS(rv, rv);
611 rv = ClearStorage(false, true, aOa);
612 NS_ENSURE_SUCCESS(rv, rv);
613 rv = ClearStorage(true, false, aOa);
614 NS_ENSURE_SUCCESS(rv, rv);
615 rv = ClearStorage(true, true, aOa);
616 NS_ENSURE_SUCCESS(rv, rv);
618 return NS_OK;
621 } // namespace CacheStorageEvictHelper
623 // nsICacheStorageService
625 NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(
626 nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
627 NS_ENSURE_ARG(_retval);
629 nsCOMPtr<nsICacheStorage> storage =
630 new CacheStorage(aLoadContextInfo, false, false, false);
631 storage.forget(_retval);
632 return NS_OK;
635 NS_IMETHODIMP CacheStorageService::DiskCacheStorage(
636 nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
637 NS_ENSURE_ARG(_retval);
639 // TODO save some heap granularity - cache commonly used storages.
641 // When disk cache is disabled, still provide a storage, but just keep stuff
642 // in memory.
643 bool useDisk = CacheObserver::UseDiskCache();
645 nsCOMPtr<nsICacheStorage> storage = new CacheStorage(
646 aLoadContextInfo, useDisk, false /* size limit */, false /* don't pin */);
647 storage.forget(_retval);
648 return NS_OK;
651 NS_IMETHODIMP CacheStorageService::PinningCacheStorage(
652 nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
653 NS_ENSURE_ARG(aLoadContextInfo);
654 NS_ENSURE_ARG(_retval);
656 // When disk cache is disabled don't pretend we cache.
657 if (!CacheObserver::UseDiskCache()) {
658 return NS_ERROR_NOT_AVAILABLE;
661 nsCOMPtr<nsICacheStorage> storage =
662 new CacheStorage(aLoadContextInfo, true /* use disk */,
663 true /* ignore size checks */, true /* pin */);
664 storage.forget(_retval);
665 return NS_OK;
668 NS_IMETHODIMP CacheStorageService::Clear() {
669 nsresult rv;
671 // Tell the index to block notification to AsyncGetDiskConsumption.
672 // Will be allowed again from CacheFileContextEvictor::EvictEntries()
673 // when all the context have been removed from disk.
674 CacheIndex::OnAsyncEviction(true);
676 mozilla::MutexAutoLock lock(mLock);
679 mozilla::MutexAutoLock forcedValidEntriesLock(mForcedValidEntriesLock);
680 mForcedValidEntries.Clear();
683 NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
685 const auto keys = ToTArray<nsTArray<nsCString>>(sGlobalEntryTables->Keys());
686 for (const auto& key : keys) {
687 DoomStorageEntries(key, nullptr, true, false, nullptr);
690 // Passing null as a load info means to evict all contexts.
691 // EvictByContext() respects the entry pinning. EvictAll() does not.
692 rv = CacheFileIOManager::EvictByContext(nullptr, false, u""_ns);
693 NS_ENSURE_SUCCESS(rv, rv);
695 return NS_OK;
698 NS_IMETHODIMP CacheStorageService::ClearOrigin(nsIPrincipal* aPrincipal) {
699 nsresult rv;
701 if (NS_WARN_IF(!aPrincipal)) {
702 return NS_ERROR_FAILURE;
705 nsAutoString origin;
706 rv = nsContentUtils::GetWebExposedOriginSerialization(aPrincipal, origin);
707 NS_ENSURE_SUCCESS(rv, rv);
709 rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), true);
710 NS_ENSURE_SUCCESS(rv, rv);
712 rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), false);
713 NS_ENSURE_SUCCESS(rv, rv);
715 return NS_OK;
718 NS_IMETHODIMP CacheStorageService::ClearOriginAttributes(
719 const nsAString& aOriginAttributes) {
720 nsresult rv;
722 if (NS_WARN_IF(aOriginAttributes.IsEmpty())) {
723 return NS_ERROR_FAILURE;
726 OriginAttributes oa;
727 if (!oa.Init(aOriginAttributes)) {
728 NS_ERROR("Could not parse the argument for OriginAttributes");
729 return NS_ERROR_FAILURE;
732 rv = CacheStorageEvictHelper::Run(oa);
733 NS_ENSURE_SUCCESS(rv, rv);
735 return NS_OK;
738 static bool RemoveExactEntry(CacheEntryTable* aEntries, nsACString const& aKey,
739 CacheEntry* aEntry, bool aOverwrite) {
740 RefPtr<CacheEntry> existingEntry;
741 if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
742 LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
743 return false; // Already removed...
746 if (!aOverwrite && existingEntry != aEntry) {
747 LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry));
748 return false; // Already replaced...
751 LOG(("RemoveExactEntry [entry=%p removed]", aEntry));
752 aEntries->Remove(aKey);
753 return true;
756 NS_IMETHODIMP CacheStorageService::ClearBaseDomain(
757 const nsAString& aBaseDomain) {
758 if (sGlobalEntryTables) {
759 mozilla::MutexAutoLock lock(mLock);
761 if (mShutdown) return NS_ERROR_NOT_AVAILABLE;
763 nsCString cBaseDomain = NS_ConvertUTF16toUTF8(aBaseDomain);
765 nsTArray<nsCString> keys;
766 for (const auto& globalEntry : *sGlobalEntryTables) {
767 // Match by partitionKey base domain. This should cover most cache entries
768 // because we statically partition the cache. Most first party cache
769 // entries will also have a partitionKey set where the partitionKey base
770 // domain will match the entry URI base domain.
771 const nsACString& key = globalEntry.GetKey();
772 nsCOMPtr<nsILoadContextInfo> info =
773 CacheFileUtils::ParseKey(globalEntry.GetKey());
775 if (info &&
776 StoragePrincipalHelper::PartitionKeyHasBaseDomain(
777 info->OriginAttributesPtr()->mPartitionKey, aBaseDomain)) {
778 keys.AppendElement(key);
779 continue;
782 // If we didn't get a partitionKey match, try to match by entry URI. This
783 // requires us to iterate over all entries.
784 CacheEntryTable* table = globalEntry.GetWeak();
785 MOZ_ASSERT(table);
787 nsTArray<RefPtr<CacheEntry>> entriesToDelete;
789 for (CacheEntry* entry : table->Values()) {
790 nsCOMPtr<nsIURI> uri;
791 nsresult rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI());
792 if (NS_WARN_IF(NS_FAILED(rv))) {
793 continue;
796 nsAutoCString host;
797 rv = uri->GetHost(host);
798 // Some entries may not have valid hosts. We can skip them.
799 if (NS_FAILED(rv) || host.IsEmpty()) {
800 continue;
803 bool hasRootDomain = false;
804 rv = HasRootDomain(host, cBaseDomain, &hasRootDomain);
805 if (NS_WARN_IF(NS_FAILED(rv))) {
806 continue;
808 if (hasRootDomain) {
809 entriesToDelete.AppendElement(entry);
813 // Clear individual matched entries.
814 for (RefPtr<CacheEntry>& entry : entriesToDelete) {
815 nsAutoCString entryKey;
816 nsresult rv = entry->HashingKey(entryKey);
817 if (NS_FAILED(rv)) {
818 NS_ERROR("aEntry->HashingKey() failed?");
819 return rv;
822 RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */);
826 // Clear matched keys.
827 for (uint32_t i = 0; i < keys.Length(); ++i) {
828 DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
832 return CacheFileIOManager::EvictByContext(nullptr, false /* pinned */, u""_ns,
833 aBaseDomain);
836 nsresult CacheStorageService::ClearOriginInternal(
837 const nsAString& aOrigin, const OriginAttributes& aOriginAttributes,
838 bool aAnonymous) {
839 nsresult rv;
841 RefPtr<LoadContextInfo> info =
842 GetLoadContextInfo(aAnonymous, aOriginAttributes);
843 if (NS_WARN_IF(!info)) {
844 return NS_ERROR_FAILURE;
847 mozilla::MutexAutoLock lock(mLock);
849 if (sGlobalEntryTables) {
850 for (const auto& globalEntry : *sGlobalEntryTables) {
851 bool matches = false;
852 rv = CacheFileUtils::KeyMatchesLoadContextInfo(globalEntry.GetKey(), info,
853 &matches);
854 NS_ENSURE_SUCCESS(rv, rv);
855 if (!matches) {
856 continue;
859 CacheEntryTable* table = globalEntry.GetWeak();
860 MOZ_ASSERT(table);
862 nsTArray<RefPtr<CacheEntry>> entriesToDelete;
864 for (CacheEntry* entry : table->Values()) {
865 nsCOMPtr<nsIURI> uri;
866 rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI());
867 NS_ENSURE_SUCCESS(rv, rv);
869 nsAutoString origin;
870 rv = nsContentUtils::GetWebExposedOriginSerialization(uri, origin);
871 NS_ENSURE_SUCCESS(rv, rv);
873 if (origin != aOrigin) {
874 continue;
877 entriesToDelete.AppendElement(entry);
880 for (RefPtr<CacheEntry>& entry : entriesToDelete) {
881 nsAutoCString entryKey;
882 rv = entry->HashingKey(entryKey);
883 if (NS_FAILED(rv)) {
884 NS_ERROR("aEntry->HashingKey() failed?");
885 return rv;
887 MOZ_ASSERT_IF(info->IsPrivate(), !entry->IsUsingDisk());
888 RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */);
893 if (!info->IsPrivate()) {
894 rv = CacheFileIOManager::EvictByContext(info, false /* pinned */, aOrigin);
896 NS_ENSURE_SUCCESS(rv, rv);
898 return NS_OK;
901 NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat) {
902 uint32_t what;
904 switch (aWhat) {
905 case PURGE_DISK_DATA_ONLY:
906 what = CacheEntry::PURGE_DATA_ONLY_DISK_BACKED;
907 break;
909 case PURGE_DISK_ALL:
910 what = CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED;
911 break;
913 case PURGE_EVERYTHING:
914 what = CacheEntry::PURGE_WHOLE;
915 break;
917 default:
918 return NS_ERROR_INVALID_ARG;
921 nsCOMPtr<nsIRunnable> event = new PurgeFromMemoryRunnable(this, what);
923 return Dispatch(event);
926 NS_IMETHODIMP CacheStorageService::PurgeFromMemoryRunnable::Run() {
927 if (NS_IsMainThread()) {
928 nsCOMPtr<nsIObserverService> observerService =
929 mozilla::services::GetObserverService();
930 if (observerService) {
931 observerService->NotifyObservers(
932 nullptr, "cacheservice:purge-memory-pools", nullptr);
935 return NS_OK;
938 if (mService) {
939 // Note that we seem to come here only in the case of "memory-pressure"
940 // being notified (or in case of tests), so we start from purging in-memory
941 // entries first and ignore minprogress for disk entries.
942 // TODO not all flags apply to both pools.
943 mService->Pool(MemoryPool::EType::MEMORY)
944 .PurgeAll(mWhat, StaticPrefs::network_cache_purge_minprogress_memory());
945 mService->Pool(MemoryPool::EType::DISK).PurgeAll(mWhat, 0);
946 mService = nullptr;
949 NS_DispatchToMainThread(this);
950 return NS_OK;
953 NS_IMETHODIMP CacheStorageService::AsyncGetDiskConsumption(
954 nsICacheStorageConsumptionObserver* aObserver) {
955 NS_ENSURE_ARG(aObserver);
957 nsresult rv;
959 rv = CacheIndex::AsyncGetDiskConsumption(aObserver);
960 NS_ENSURE_SUCCESS(rv, rv);
962 return NS_OK;
965 NS_IMETHODIMP CacheStorageService::GetIoTarget(nsIEventTarget** aEventTarget) {
966 NS_ENSURE_ARG(aEventTarget);
968 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
969 ioTarget.forget(aEventTarget);
971 return NS_OK;
974 NS_IMETHODIMP CacheStorageService::AsyncVisitAllStorages(
975 nsICacheStorageVisitor* aVisitor, bool aVisitEntries) {
976 LOG(("CacheStorageService::AsyncVisitAllStorages [cb=%p]", aVisitor));
977 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
979 // Walking the disk cache also walks the memory cache.
980 RefPtr<WalkDiskCacheRunnable> event =
981 new WalkDiskCacheRunnable(nullptr, aVisitEntries, aVisitor);
982 return event->Walk();
985 // Methods used by CacheEntry for management of in-memory structures.
987 void CacheStorageService::RegisterEntry(CacheEntry* aEntry) {
988 MOZ_ASSERT(IsOnManagementThread());
990 if (mShutdown || !aEntry->CanRegister()) return;
992 TelemetryRecordEntryCreation(aEntry);
994 LOG(("CacheStorageService::RegisterEntry [entry=%p]", aEntry));
996 MemoryPool& pool = Pool(aEntry->IsUsingDisk());
997 pool.mManagedEntries.insertBack(aEntry);
999 aEntry->SetRegistered(true);
1002 void CacheStorageService::UnregisterEntry(CacheEntry* aEntry) {
1003 MOZ_ASSERT(IsOnManagementThread());
1005 if (!aEntry->IsRegistered()) return;
1007 TelemetryRecordEntryRemoval(aEntry);
1009 LOG(("CacheStorageService::UnregisterEntry [entry=%p]", aEntry));
1011 MemoryPool& pool = Pool(aEntry->IsUsingDisk());
1012 aEntry->removeFrom(pool.mManagedEntries);
1014 // Note: aEntry->CanRegister() since now returns false
1015 aEntry->SetRegistered(false);
1018 static bool AddExactEntry(CacheEntryTable* aEntries, nsACString const& aKey,
1019 CacheEntry* aEntry, bool aOverwrite) {
1020 RefPtr<CacheEntry> existingEntry;
1021 if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
1022 bool equals = existingEntry == aEntry;
1023 LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals));
1024 return equals; // Already there...
1027 LOG(("AddExactEntry [entry=%p put]", aEntry));
1028 aEntries->InsertOrUpdate(aKey, RefPtr{aEntry});
1029 return true;
1032 bool CacheStorageService::RemoveEntry(CacheEntry* aEntry,
1033 bool aOnlyUnreferenced) {
1034 LOG(("CacheStorageService::RemoveEntry [entry=%p]", aEntry));
1036 nsAutoCString entryKey;
1037 nsresult rv = aEntry->HashingKey(entryKey);
1038 if (NS_FAILED(rv)) {
1039 NS_ERROR("aEntry->HashingKey() failed?");
1040 return false;
1043 mozilla::MutexAutoLock lock(mLock);
1045 if (mShutdown) {
1046 LOG((" after shutdown"));
1047 return false;
1050 if (aOnlyUnreferenced) {
1051 if (aEntry->IsReferenced()) {
1052 LOG((" still referenced, not removing"));
1053 return false;
1056 if (!aEntry->IsUsingDisk() &&
1057 IsForcedValidEntry(aEntry->GetStorageID(), entryKey)) {
1058 LOG((" forced valid, not removing"));
1059 return false;
1063 CacheEntryTable* entries;
1064 if (sGlobalEntryTables->Get(aEntry->GetStorageID(), &entries)) {
1065 RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
1068 nsAutoCString memoryStorageID(aEntry->GetStorageID());
1069 AppendMemoryStorageTag(memoryStorageID);
1071 if (sGlobalEntryTables->Get(memoryStorageID, &entries)) {
1072 RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
1075 return true;
1078 void CacheStorageService::RecordMemoryOnlyEntry(CacheEntry* aEntry,
1079 bool aOnlyInMemory,
1080 bool aOverwrite) {
1081 LOG(
1082 ("CacheStorageService::RecordMemoryOnlyEntry [entry=%p, memory=%d, "
1083 "overwrite=%d]",
1084 aEntry, aOnlyInMemory, aOverwrite));
1085 // This method is responsible to put this entry to a special record hashtable
1086 // that contains only entries that are stored in memory.
1087 // Keep in mind that every entry, regardless of whether is in-memory-only or
1088 // not is always recorded in the storage master hash table, the one identified
1089 // by CacheEntry.StorageID().
1091 mLock.AssertCurrentThreadOwns();
1093 if (mShutdown) {
1094 LOG((" after shutdown"));
1095 return;
1098 nsresult rv;
1100 nsAutoCString entryKey;
1101 rv = aEntry->HashingKey(entryKey);
1102 if (NS_FAILED(rv)) {
1103 NS_ERROR("aEntry->HashingKey() failed?");
1104 return;
1107 CacheEntryTable* entries = nullptr;
1108 nsAutoCString memoryStorageID(aEntry->GetStorageID());
1109 AppendMemoryStorageTag(memoryStorageID);
1111 if (!sGlobalEntryTables->Get(memoryStorageID, &entries)) {
1112 if (!aOnlyInMemory) {
1113 LOG((" not recorded as memory only"));
1114 return;
1117 entries = sGlobalEntryTables
1118 ->InsertOrUpdate(
1119 memoryStorageID,
1120 MakeUnique<CacheEntryTable>(CacheEntryTable::MEMORY_ONLY))
1121 .get();
1122 LOG((" new memory-only storage table for %s", memoryStorageID.get()));
1125 if (aOnlyInMemory) {
1126 AddExactEntry(entries, entryKey, aEntry, aOverwrite);
1127 } else {
1128 RemoveExactEntry(entries, entryKey, aEntry, aOverwrite);
1132 // Checks if a cache entry is forced valid (will be loaded directly from cache
1133 // without further validation) - see nsICacheEntry.idl for further details
1134 bool CacheStorageService::IsForcedValidEntry(nsACString const& aContextKey,
1135 nsACString const& aEntryKey) {
1136 return IsForcedValidEntry(aContextKey + aEntryKey);
1139 bool CacheStorageService::IsForcedValidEntry(
1140 nsACString const& aContextEntryKey) {
1141 mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1143 ForcedValidData data;
1145 if (!mForcedValidEntries.Get(aContextEntryKey, &data)) {
1146 return false;
1149 if (data.validUntil.IsNull()) {
1150 MOZ_ASSERT_UNREACHABLE("the timeStamp should never be null");
1151 return false;
1154 // Entry timeout not reached yet
1155 if (TimeStamp::NowLoRes() <= data.validUntil) {
1156 return true;
1159 // Entry timeout has been reached
1160 mForcedValidEntries.Remove(aContextEntryKey);
1162 if (!data.viewed) {
1163 glean::predictor::prefetch_use_status
1164 .EnumGet(glean::predictor::PrefetchUseStatusLabel::eWaitedtoolong)
1165 .Add();
1167 return false;
1170 void CacheStorageService::MarkForcedValidEntryUse(nsACString const& aContextKey,
1171 nsACString const& aEntryKey) {
1172 mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1174 ForcedValidData data;
1176 if (!mForcedValidEntries.Get(aContextKey + aEntryKey, &data)) {
1177 return;
1180 data.viewed = true;
1181 mForcedValidEntries.InsertOrUpdate(aContextKey + aEntryKey, data);
1184 // Allows a cache entry to be loaded directly from cache without further
1185 // validation - see nsICacheEntry.idl for further details
1186 void CacheStorageService::ForceEntryValidFor(nsACString const& aContextKey,
1187 nsACString const& aEntryKey,
1188 uint32_t aSecondsToTheFuture) {
1189 mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1191 TimeStamp now = TimeStamp::NowLoRes();
1192 ForcedValidEntriesPrune(now);
1194 ForcedValidData data;
1195 data.validUntil = now + TimeDuration::FromSeconds(aSecondsToTheFuture);
1196 data.viewed = false;
1198 mForcedValidEntries.InsertOrUpdate(aContextKey + aEntryKey, data);
1201 void CacheStorageService::RemoveEntryForceValid(nsACString const& aContextKey,
1202 nsACString const& aEntryKey) {
1203 mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1205 LOG(("CacheStorageService::RemoveEntryForceValid context='%s' entryKey=%s",
1206 aContextKey.BeginReading(), aEntryKey.BeginReading()));
1207 ForcedValidData data;
1208 bool ok = mForcedValidEntries.Get(aContextKey + aEntryKey, &data);
1209 if (ok && !data.viewed) {
1210 glean::predictor::prefetch_use_status
1211 .EnumGet(glean::predictor::PrefetchUseStatusLabel::eWaitedtoolong)
1212 .Add();
1214 mForcedValidEntries.Remove(aContextKey + aEntryKey);
1217 // Cleans out the old entries in mForcedValidEntries
1218 void CacheStorageService::ForcedValidEntriesPrune(TimeStamp& now) {
1219 static TimeDuration const oneMinute = TimeDuration::FromSeconds(60);
1220 static TimeStamp dontPruneUntil = now + oneMinute;
1221 if (now < dontPruneUntil) return;
1223 for (auto iter = mForcedValidEntries.Iter(); !iter.Done(); iter.Next()) {
1224 if (iter.Data().validUntil < now) {
1225 if (!iter.Data().viewed) {
1226 glean::predictor::prefetch_use_status
1227 .EnumGet(glean::predictor::PrefetchUseStatusLabel::eWaitedtoolong)
1228 .Add();
1230 iter.Remove();
1233 dontPruneUntil = now + oneMinute;
1236 void CacheStorageService::OnMemoryConsumptionChange(
1237 CacheMemoryConsumer* aConsumer, uint32_t aCurrentMemoryConsumption) {
1238 LOG(("CacheStorageService::OnMemoryConsumptionChange [consumer=%p, size=%u]",
1239 aConsumer, aCurrentMemoryConsumption));
1241 uint32_t savedMemorySize = aConsumer->LoadReportedMemoryConsumption();
1242 if (savedMemorySize == aCurrentMemoryConsumption) return;
1244 // Exchange saved size with current one.
1245 aConsumer->StoreReportedMemoryConsumption(aCurrentMemoryConsumption);
1247 bool usingDisk = !(aConsumer->LoadFlags() & CacheMemoryConsumer::MEMORY_ONLY);
1248 bool overLimit = Pool(usingDisk).OnMemoryConsumptionChange(
1249 savedMemorySize, aCurrentMemoryConsumption);
1251 if (!overLimit) return;
1253 // It's likely the timer has already been set when we get here,
1254 // check outside the lock to save resources.
1255 #ifdef MOZ_TSAN
1256 if (mPurgeTimerActive) {
1257 #else
1258 if (mPurgeTimer) {
1259 #endif
1260 return;
1263 // We don't know if this is called under the service lock or not,
1264 // hence rather dispatch.
1265 RefPtr<nsIEventTarget> cacheIOTarget = Thread();
1266 if (!cacheIOTarget) return;
1268 // Dispatch as a priority task, we want to set the purge timer
1269 // ASAP to prevent vain redispatch of this event.
1270 nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
1271 "net::CacheStorageService::SchedulePurgeOverMemoryLimit", this,
1272 &CacheStorageService::SchedulePurgeOverMemoryLimit);
1273 cacheIOTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1276 bool CacheStorageService::MemoryPool::OnMemoryConsumptionChange(
1277 uint32_t aSavedMemorySize, uint32_t aCurrentMemoryConsumption) {
1278 mMemorySize -= aSavedMemorySize;
1279 mMemorySize += aCurrentMemoryConsumption;
1281 LOG((" mMemorySize=%u (+%u,-%u)", uint32_t(mMemorySize),
1282 aCurrentMemoryConsumption, aSavedMemorySize));
1284 // Bypass purging when memory has not grew up significantly
1285 if (aCurrentMemoryConsumption <= aSavedMemorySize) return false;
1287 return mMemorySize > Limit();
1290 void CacheStorageService::SchedulePurgeOverMemoryLimit() {
1291 LOG(("CacheStorageService::SchedulePurgeOverMemoryLimit"));
1293 mozilla::MutexAutoLock lock(mLock);
1295 if (mShutdown) {
1296 LOG((" past shutdown"));
1297 return;
1300 if (mPurgeTimer) {
1301 LOG((" timer already up"));
1302 return;
1305 mPurgeTimer = NS_NewTimer();
1306 if (mPurgeTimer) {
1307 #ifdef MOZ_TSAN
1308 mPurgeTimerActive = true;
1309 #endif
1310 nsresult rv;
1311 rv = mPurgeTimer->InitWithCallback(this, 1000, nsITimer::TYPE_ONE_SHOT);
1312 LOG((" timer init rv=0x%08" PRIx32, static_cast<uint32_t>(rv)));
1316 NS_IMETHODIMP
1317 CacheStorageService::Notify(nsITimer* aTimer) {
1318 LOG(("CacheStorageService::Notify"));
1320 mozilla::MutexAutoLock lock(mLock);
1322 if (aTimer == mPurgeTimer) {
1323 #ifdef MOZ_TSAN
1324 mPurgeTimerActive = false;
1325 #endif
1326 mPurgeTimer = nullptr;
1328 if (!mShutdown) {
1329 nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
1330 "net::CacheStorageService::PurgeExpiredOrOverMemoryLimit", this,
1331 &CacheStorageService::PurgeExpiredOrOverMemoryLimit);
1332 Dispatch(event);
1336 return NS_OK;
1339 NS_IMETHODIMP
1340 CacheStorageService::GetName(nsACString& aName) {
1341 aName.AssignLiteral("CacheStorageService");
1342 return NS_OK;
1345 void CacheStorageService::PurgeExpiredOrOverMemoryLimit() {
1346 MOZ_ASSERT(IsOnManagementThread());
1348 LOG(("CacheStorageService::PurgeExpiredOrOverMemoryLimit"));
1350 if (mShutdown) return;
1352 static TimeDuration const kFourSeconds = TimeDuration::FromSeconds(4);
1353 TimeStamp now = TimeStamp::NowLoRes();
1355 if (!mLastPurgeTime.IsNull() && now - mLastPurgeTime < kFourSeconds) {
1356 LOG((" bypassed, too soon"));
1357 return;
1360 mLastPurgeTime = now;
1362 // We start purging memory entries first as we care more about RAM over
1363 // disk space beeing freed in case we are interrupted.
1364 Pool(MemoryPool::EType::MEMORY).PurgeExpiredOrOverMemoryLimit();
1365 Pool(MemoryPool::EType::DISK).PurgeExpiredOrOverMemoryLimit();
1368 void CacheStorageService::MemoryPool::PurgeExpiredOrOverMemoryLimit() {
1369 TimeStamp start(TimeStamp::Now());
1371 uint32_t const memoryLimit = Limit();
1372 size_t minprogress =
1373 (mType == EType::DISK)
1374 ? StaticPrefs::network_cache_purge_minprogress_disk()
1375 : StaticPrefs::network_cache_purge_minprogress_memory();
1377 // We always purge expired entries, even if under our limit.
1378 size_t numExpired = PurgeExpired(minprogress);
1379 if (numExpired > 0) {
1380 LOG((" found and purged %zu expired entries", numExpired));
1382 minprogress = (minprogress > numExpired) ? minprogress - numExpired : 0;
1384 // If we are still under pressure, purge LFU entries until we aren't.
1385 if (mMemorySize > memoryLimit) {
1386 // Do not enter PurgeByFrecency if we reached the minimum and are asked to
1387 // deliver entries.
1388 if (minprogress == 0 && CacheIOThread::YieldAndRerun()) {
1389 return;
1392 if (mType == EType::DISK) {
1393 mozilla::glean::networking::cache_purge_due_to_memory_limit
1394 .Get("meta_data_file_size_limit"_ns)
1395 .Add(1);
1396 } else if (mType == EType::MEMORY) {
1397 mozilla::glean::networking::cache_purge_due_to_memory_limit
1398 .Get("cache_memory_limit"_ns)
1399 .Add(1);
1402 auto r = PurgeByFrecency(minprogress);
1403 if (MOZ_LIKELY(r.isOk())) {
1404 size_t numPurged = r.unwrap();
1405 LOG((
1406 " memory data consumption over the limit, abandoned %zu LFU entries",
1407 numPurged));
1408 } else {
1409 // If we hit an error (OOM), do an emergency PurgeAll.
1410 size_t numPurged = PurgeAll(CacheEntry::PURGE_WHOLE, minprogress);
1411 LOG(
1412 (" memory data consumption over the limit, emergency purged all %zu "
1413 "entries",
1414 numPurged));
1418 LOG((" purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds()));
1421 // This function purges ALL expired entries.
1422 size_t CacheStorageService::MemoryPool::PurgeExpired(size_t minprogress) {
1423 MOZ_ASSERT(IsOnManagementThread());
1425 uint32_t now = NowInSeconds();
1427 size_t numPurged = 0;
1428 // Scan for items to purge. mManagedEntries is not sorted but comparing just
1429 // one integer should be faster than anything else, so go scan.
1430 RefPtr<CacheEntry> entry = mManagedEntries.getFirst();
1431 while (entry) {
1432 // Get the next entry before we may be removed from our list.
1433 RefPtr<CacheEntry> nextEntry = entry->getNext();
1435 if (entry->GetExpirationTime() <= now) {
1436 // Purge will modify our mManagedEntries list but we are prepared for it.
1437 if (entry->Purge(CacheEntry::PURGE_WHOLE)) {
1438 numPurged++;
1439 LOG((" purged expired, entry=%p, exptime=%u (now=%u)", entry.get(),
1440 entry->GetExpirationTime(), now));
1444 entry = std::move(nextEntry);
1446 // To have some progress even under load, we do the check only after
1447 // purging at least minprogress items if under pressure.
1448 if ((numPurged >= minprogress || mMemorySize <= Limit()) &&
1449 CacheIOThread::YieldAndRerun()) {
1450 break;
1454 return numPurged;
1457 Result<size_t, nsresult> CacheStorageService::MemoryPool::PurgeByFrecency(
1458 size_t minprogress) {
1459 MOZ_ASSERT(IsOnManagementThread());
1461 // Pretend the limit is 10% lower so that we get rid of more entries at one
1462 // shot and save the sorting below.
1463 uint32_t const memoryLimit = (uint32_t)(Limit() * 0.9);
1464 if (mMemorySize <= memoryLimit) {
1465 return 0;
1468 LOG(("MemoryPool::PurgeByFrecency, len=%zu", mManagedEntries.length()));
1470 // We want to have an array snapshot for sorting and iterating.
1471 struct mayPurgeEntry {
1472 RefPtr<CacheEntry> mEntry;
1473 double mFrecency;
1475 explicit mayPurgeEntry(CacheEntry* aEntry) {
1476 mEntry = aEntry;
1477 mFrecency = aEntry->GetFrecency();
1480 bool operator<(const mayPurgeEntry& aOther) const {
1481 return mFrecency < aOther.mFrecency;
1485 nsTArray<mayPurgeEntry> mayPurgeSorted;
1486 if (!mayPurgeSorted.SetCapacity(mManagedEntries.length(),
1487 mozilla::fallible)) {
1488 return Err(NS_ERROR_OUT_OF_MEMORY);
1491 mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
1493 for (const auto& entry : mManagedEntries) {
1494 // Referenced items cannot be purged and we deliberately want to not look
1495 // at '0' frecency entries, these are new entries and can be ignored.
1496 if (!entry->IsReferenced() && entry->GetFrecency() > 0.0) {
1497 mayPurgeEntry copy(entry);
1498 mayPurgeSorted.AppendElement(std::move(copy));
1502 if (mayPurgeSorted.Length() == 0) {
1503 return 0;
1505 mayPurgeSorted.Sort();
1507 size_t numPurged = 0;
1509 for (auto& checkPurge : mayPurgeSorted) {
1510 if (mMemorySize <= memoryLimit) {
1511 break;
1514 RefPtr<CacheEntry> entry = checkPurge.mEntry;
1516 if (entry->Purge(CacheEntry::PURGE_WHOLE)) {
1517 numPurged++;
1518 LOG((" abandoned (%d), entry=%p, frecency=%1.10f",
1519 CacheEntry::PURGE_WHOLE, entry.get(), entry->GetFrecency()));
1522 if (numPurged >= minprogress && CacheIOThread::YieldAndRerun()) {
1523 LOG(("MemoryPool::PurgeByFrecency interrupted"));
1524 return numPurged;
1528 LOG(("MemoryPool::PurgeByFrecency done"));
1530 return numPurged;
1533 size_t CacheStorageService::MemoryPool::PurgeAll(uint32_t aWhat,
1534 size_t minprogress) {
1535 LOG(("CacheStorageService::MemoryPool::PurgeAll aWhat=%d", aWhat));
1536 MOZ_ASSERT(IsOnManagementThread());
1538 size_t numPurged = 0;
1540 RefPtr<CacheEntry> entry = mManagedEntries.getFirst();
1541 while (entry) {
1542 if (numPurged >= minprogress && CacheIOThread::YieldAndRerun()) break;
1544 // Get the next entry before we may be removed from our list.
1545 RefPtr<CacheEntry> nextEntry = entry->getNext();
1547 if (entry->Purge(aWhat)) {
1548 numPurged++;
1549 LOG((" abandoned entry=%p", entry.get()));
1552 entry = std::move(nextEntry);
1555 return numPurged;
1558 // Methods exposed to and used by CacheStorage.
1560 nsresult CacheStorageService::AddStorageEntry(CacheStorage const* aStorage,
1561 const nsACString& aURI,
1562 const nsACString& aIdExtension,
1563 uint32_t aFlags,
1564 CacheEntryHandle** aResult) {
1565 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1567 NS_ENSURE_ARG(aStorage);
1569 nsAutoCString contextKey;
1570 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1572 return AddStorageEntry(contextKey, aURI, aIdExtension,
1573 aStorage->WriteToDisk(), aStorage->SkipSizeCheck(),
1574 aStorage->Pinning(), aFlags, aResult);
1577 nsresult CacheStorageService::AddStorageEntry(
1578 const nsACString& aContextKey, const nsACString& aURI,
1579 const nsACString& aIdExtension, bool aWriteToDisk, bool aSkipSizeCheck,
1580 bool aPin, uint32_t aFlags, CacheEntryHandle** aResult) {
1581 nsresult rv;
1583 nsAutoCString entryKey;
1584 rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
1585 NS_ENSURE_SUCCESS(rv, rv);
1587 LOG(("CacheStorageService::AddStorageEntry [entryKey=%s, contextKey=%s]",
1588 entryKey.get(), aContextKey.BeginReading()));
1590 RefPtr<CacheEntry> entry;
1591 RefPtr<CacheEntryHandle> handle;
1594 mozilla::MutexAutoLock lock(mLock);
1596 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1598 // Ensure storage table
1599 CacheEntryTable* const entries =
1600 sGlobalEntryTables
1601 ->LookupOrInsertWith(
1602 aContextKey,
1603 [&aContextKey] {
1604 LOG((" new storage entries table for context '%s'",
1605 aContextKey.BeginReading()));
1606 return MakeUnique<CacheEntryTable>(
1607 CacheEntryTable::ALL_ENTRIES);
1609 .get();
1611 bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
1612 if (!entryExists && (aFlags & nsICacheStorage::OPEN_READONLY) &&
1613 (aFlags & nsICacheStorage::OPEN_SECRETLY) &&
1614 StaticPrefs::network_cache_bug1708673()) {
1615 return NS_ERROR_CACHE_KEY_NOT_FOUND;
1618 bool replace = aFlags & nsICacheStorage::OPEN_TRUNCATE;
1620 if (entryExists && !replace) {
1621 // check whether we want to turn this entry to a memory-only.
1622 if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) {
1623 LOG((" entry is persistent but we want mem-only, replacing it"));
1624 replace = true;
1628 // If truncate is demanded, delete and doom the current entry
1629 if (entryExists && replace) {
1630 entries->Remove(entryKey);
1632 LOG((" dooming entry %p for %s because of OPEN_TRUNCATE", entry.get(),
1633 entryKey.get()));
1634 // On purpose called under the lock to prevent races of doom and open on
1635 // I/O thread No need to remove from both memory-only and all-entries
1636 // tables. The new entry will overwrite the shadow entry in its ctor.
1637 entry->DoomAlreadyRemoved();
1639 entry = nullptr;
1640 entryExists = false;
1642 // Would only lead to deleting force-valid timestamp again. We don't need
1643 // the replace information anymore after this point anyway.
1644 replace = false;
1647 // Ensure entry for the particular URL
1648 if (!entryExists) {
1649 // When replacing with a new entry, always remove the current force-valid
1650 // timestamp, this is the only place to do it.
1651 if (replace) {
1652 RemoveEntryForceValid(aContextKey, entryKey);
1655 // Entry is not in the hashtable or has just been truncated...
1656 entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk,
1657 aSkipSizeCheck, aPin);
1658 entries->InsertOrUpdate(entryKey, RefPtr{entry});
1659 LOG((" new entry %p for %s", entry.get(), entryKey.get()));
1662 if (entry) {
1663 // Here, if this entry was not for a long time referenced by any consumer,
1664 // gets again first 'handles count' reference.
1665 handle = entry->NewHandle();
1669 handle.forget(aResult);
1670 return NS_OK;
1673 nsresult CacheStorageService::CheckStorageEntry(CacheStorage const* aStorage,
1674 const nsACString& aURI,
1675 const nsACString& aIdExtension,
1676 bool* aResult) {
1677 nsresult rv;
1679 nsAutoCString contextKey;
1680 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1682 if (!aStorage->WriteToDisk()) {
1683 AppendMemoryStorageTag(contextKey);
1686 LOG(("CacheStorageService::CheckStorageEntry [uri=%s, eid=%s, contextKey=%s]",
1687 aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get()));
1690 mozilla::MutexAutoLock lock(mLock);
1692 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1694 nsAutoCString entryKey;
1695 rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
1696 NS_ENSURE_SUCCESS(rv, rv);
1698 CacheEntryTable* entries;
1699 if ((*aResult = sGlobalEntryTables->Get(contextKey, &entries)) &&
1700 entries->GetWeak(entryKey, aResult)) {
1701 LOG((" found in hash tables"));
1702 return NS_OK;
1706 if (!aStorage->WriteToDisk()) {
1707 // Memory entry, nothing more to do.
1708 LOG((" not found in hash tables"));
1709 return NS_OK;
1712 // Disk entry, not found in the hashtable, check the index.
1713 nsAutoCString fileKey;
1714 rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey);
1716 CacheIndex::EntryStatus status;
1717 rv = CacheIndex::HasEntry(fileKey, &status);
1718 if (NS_FAILED(rv) || status == CacheIndex::DO_NOT_KNOW) {
1719 LOG((" index doesn't know, rv=0x%08" PRIx32, static_cast<uint32_t>(rv)));
1720 return NS_ERROR_NOT_AVAILABLE;
1723 *aResult = status == CacheIndex::EXISTS;
1724 LOG((" %sfound in index", *aResult ? "" : "not "));
1725 return NS_OK;
1728 nsresult CacheStorageService::GetCacheIndexEntryAttrs(
1729 CacheStorage const* aStorage, const nsACString& aURI,
1730 const nsACString& aIdExtension, bool* aHasAltData, uint32_t* aFileSizeKb) {
1731 nsresult rv;
1733 nsAutoCString contextKey;
1734 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1736 LOG(
1737 ("CacheStorageService::GetCacheIndexEntryAttrs [uri=%s, eid=%s, "
1738 "contextKey=%s]",
1739 aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get()));
1741 nsAutoCString fileKey;
1742 rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey);
1743 if (NS_FAILED(rv)) {
1744 return rv;
1747 *aHasAltData = false;
1748 *aFileSizeKb = 0;
1749 auto closure = [&aHasAltData, &aFileSizeKb](const CacheIndexEntry* entry) {
1750 *aHasAltData = entry->GetHasAltData();
1751 *aFileSizeKb = entry->GetFileSize();
1754 CacheIndex::EntryStatus status;
1755 rv = CacheIndex::HasEntry(fileKey, &status, closure);
1756 if (NS_FAILED(rv)) {
1757 return rv;
1760 if (status != CacheIndex::EXISTS) {
1761 return NS_ERROR_CACHE_KEY_NOT_FOUND;
1764 return NS_OK;
1767 namespace {
1769 class CacheEntryDoomByKeyCallback : public CacheFileIOListener,
1770 public nsIRunnable {
1771 public:
1772 NS_DECL_THREADSAFE_ISUPPORTS
1773 NS_DECL_NSIRUNNABLE
1775 explicit CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback* aCallback)
1776 : mCallback(aCallback), mResult(NS_ERROR_NOT_INITIALIZED) {}
1778 private:
1779 virtual ~CacheEntryDoomByKeyCallback();
1781 NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override {
1782 return NS_OK;
1784 NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
1785 nsresult aResult) override {
1786 return NS_OK;
1788 NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
1789 nsresult aResult) override {
1790 return NS_OK;
1792 NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override;
1793 NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override {
1794 return NS_OK;
1796 NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle,
1797 nsresult aResult) override {
1798 return NS_OK;
1801 nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
1802 nsresult mResult;
1805 CacheEntryDoomByKeyCallback::~CacheEntryDoomByKeyCallback() {
1806 if (mCallback) {
1807 ProxyReleaseMainThread("CacheEntryDoomByKeyCallback::mCallback", mCallback);
1811 NS_IMETHODIMP CacheEntryDoomByKeyCallback::OnFileDoomed(
1812 CacheFileHandle* aHandle, nsresult aResult) {
1813 if (!mCallback) return NS_OK;
1815 mResult = aResult;
1816 if (NS_IsMainThread()) {
1817 Run();
1818 } else {
1819 NS_DispatchToMainThread(this);
1822 return NS_OK;
1825 NS_IMETHODIMP CacheEntryDoomByKeyCallback::Run() {
1826 mCallback->OnCacheEntryDoomed(mResult);
1827 return NS_OK;
1830 NS_IMPL_ISUPPORTS(CacheEntryDoomByKeyCallback, CacheFileIOListener,
1831 nsIRunnable);
1833 } // namespace
1835 nsresult CacheStorageService::DoomStorageEntry(
1836 CacheStorage const* aStorage, const nsACString& aURI,
1837 const nsACString& aIdExtension, nsICacheEntryDoomCallback* aCallback) {
1838 LOG(("CacheStorageService::DoomStorageEntry"));
1840 NS_ENSURE_ARG(aStorage);
1842 nsAutoCString contextKey;
1843 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1845 nsAutoCString entryKey;
1846 nsresult rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
1847 NS_ENSURE_SUCCESS(rv, rv);
1849 RefPtr<CacheEntry> entry;
1851 mozilla::MutexAutoLock lock(mLock);
1853 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1855 CacheEntryTable* entries;
1856 if (sGlobalEntryTables->Get(contextKey, &entries)) {
1857 if (entries->Get(entryKey, getter_AddRefs(entry))) {
1858 if (aStorage->WriteToDisk() || !entry->IsUsingDisk()) {
1859 // When evicting from disk storage, purge
1860 // When evicting from memory storage and the entry is memory-only,
1861 // purge
1862 LOG(
1863 (" purging entry %p for %s [storage use disk=%d, entry use "
1864 "disk=%d]",
1865 entry.get(), entryKey.get(), aStorage->WriteToDisk(),
1866 entry->IsUsingDisk()));
1867 entries->Remove(entryKey);
1868 } else {
1869 // Otherwise, leave it
1870 LOG(
1871 (" leaving entry %p for %s [storage use disk=%d, entry use "
1872 "disk=%d]",
1873 entry.get(), entryKey.get(), aStorage->WriteToDisk(),
1874 entry->IsUsingDisk()));
1875 entry = nullptr;
1880 if (!entry) {
1881 RemoveEntryForceValid(contextKey, entryKey);
1885 if (entry) {
1886 LOG((" dooming entry %p for %s", entry.get(), entryKey.get()));
1887 return entry->AsyncDoom(aCallback);
1890 LOG((" no entry loaded for %s", entryKey.get()));
1892 if (aStorage->WriteToDisk()) {
1893 nsAutoCString contextKey;
1894 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1896 rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, entryKey);
1897 NS_ENSURE_SUCCESS(rv, rv);
1899 LOG((" dooming file only for %s", entryKey.get()));
1901 RefPtr<CacheEntryDoomByKeyCallback> callback(
1902 new CacheEntryDoomByKeyCallback(aCallback));
1903 rv = CacheFileIOManager::DoomFileByKey(entryKey, callback);
1904 NS_ENSURE_SUCCESS(rv, rv);
1906 return NS_OK;
1909 class Callback : public Runnable {
1910 public:
1911 explicit Callback(nsICacheEntryDoomCallback* aCallback)
1912 : mozilla::Runnable("Callback"), mCallback(aCallback) {}
1913 NS_IMETHOD Run() override {
1914 mCallback->OnCacheEntryDoomed(NS_ERROR_NOT_AVAILABLE);
1915 return NS_OK;
1917 nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
1920 if (aCallback) {
1921 RefPtr<Runnable> callback = new Callback(aCallback);
1922 return NS_DispatchToMainThread(callback);
1925 return NS_OK;
1928 nsresult CacheStorageService::DoomStorageEntries(
1929 CacheStorage const* aStorage, nsICacheEntryDoomCallback* aCallback) {
1930 LOG(("CacheStorageService::DoomStorageEntries"));
1932 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1933 NS_ENSURE_ARG(aStorage);
1935 nsAutoCString contextKey;
1936 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1938 mozilla::MutexAutoLock lock(mLock);
1940 return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
1941 aStorage->WriteToDisk(), aStorage->Pinning(),
1942 aCallback);
1945 nsresult CacheStorageService::DoomStorageEntries(
1946 const nsACString& aContextKey, nsILoadContextInfo* aContext,
1947 bool aDiskStorage, bool aPinned, nsICacheEntryDoomCallback* aCallback) {
1948 LOG(("CacheStorageService::DoomStorageEntries [context=%s]",
1949 aContextKey.BeginReading()));
1951 mLock.AssertCurrentThreadOwns();
1953 NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
1955 nsAutoCString memoryStorageID(aContextKey);
1956 AppendMemoryStorageTag(memoryStorageID);
1958 if (aDiskStorage) {
1959 LOG((" dooming disk+memory storage of %s", aContextKey.BeginReading()));
1961 // Walk one by one and remove entries according their pin status
1962 CacheEntryTable *diskEntries, *memoryEntries;
1963 if (sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
1964 sGlobalEntryTables->Get(memoryStorageID, &memoryEntries);
1966 for (auto iter = diskEntries->Iter(); !iter.Done(); iter.Next()) {
1967 auto entry = iter.Data();
1968 if (entry->DeferOrBypassRemovalOnPinStatus(aPinned)) {
1969 continue;
1972 if (memoryEntries) {
1973 RemoveExactEntry(memoryEntries, iter.Key(), entry, false);
1975 iter.Remove();
1979 if (aContext && !aContext->IsPrivate()) {
1980 LOG((" dooming disk entries"));
1981 CacheFileIOManager::EvictByContext(aContext, aPinned, u""_ns);
1983 } else {
1984 LOG((" dooming memory-only storage of %s", aContextKey.BeginReading()));
1986 // Remove the memory entries table from the global tables.
1987 // Since we store memory entries also in the disk entries table
1988 // we need to remove the memory entries from the disk table one
1989 // by one manually.
1990 mozilla::UniquePtr<CacheEntryTable> memoryEntries;
1991 sGlobalEntryTables->Remove(memoryStorageID, &memoryEntries);
1993 CacheEntryTable* diskEntries;
1994 if (memoryEntries && sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
1995 for (const auto& memoryEntry : *memoryEntries) {
1996 const auto& entry = memoryEntry.GetData();
1997 RemoveExactEntry(diskEntries, memoryEntry.GetKey(), entry, false);
2003 mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
2005 if (aContext) {
2006 for (auto iter = mForcedValidEntries.Iter(); !iter.Done(); iter.Next()) {
2007 bool matches;
2008 DebugOnly<nsresult> rv = CacheFileUtils::KeyMatchesLoadContextInfo(
2009 iter.Key(), aContext, &matches);
2010 MOZ_ASSERT(NS_SUCCEEDED(rv));
2012 if (matches) {
2013 iter.Remove();
2016 } else {
2017 mForcedValidEntries.Clear();
2021 // An artificial callback. This is a candidate for removal tho. In the new
2022 // cache any 'doom' or 'evict' function ensures that the entry or entries
2023 // being doomed is/are not accessible after the function returns. So there is
2024 // probably no need for a callback - has no meaning. But for compatibility
2025 // with the old cache that is still in the tree we keep the API similar to be
2026 // able to make tests as well as other consumers work for now.
2027 class Callback : public Runnable {
2028 public:
2029 explicit Callback(nsICacheEntryDoomCallback* aCallback)
2030 : mozilla::Runnable("Callback"), mCallback(aCallback) {}
2031 NS_IMETHOD Run() override {
2032 mCallback->OnCacheEntryDoomed(NS_OK);
2033 return NS_OK;
2035 nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
2038 if (aCallback) {
2039 RefPtr<Runnable> callback = new Callback(aCallback);
2040 return NS_DispatchToMainThread(callback);
2043 return NS_OK;
2046 nsresult CacheStorageService::WalkStorageEntries(
2047 CacheStorage const* aStorage, bool aVisitEntries,
2048 nsICacheStorageVisitor* aVisitor) {
2049 LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]",
2050 aVisitor, aVisitEntries));
2051 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
2053 NS_ENSURE_ARG(aStorage);
2055 if (aStorage->WriteToDisk()) {
2056 RefPtr<WalkDiskCacheRunnable> event = new WalkDiskCacheRunnable(
2057 aStorage->LoadInfo(), aVisitEntries, aVisitor);
2058 return event->Walk();
2061 RefPtr<WalkMemoryCacheRunnable> event = new WalkMemoryCacheRunnable(
2062 aStorage->LoadInfo(), aVisitEntries, aVisitor);
2063 return event->Walk();
2066 void CacheStorageService::CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
2067 const nsACString& aIdExtension,
2068 const nsACString& aURISpec) {
2069 nsAutoCString contextKey;
2070 CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
2072 nsAutoCString entryKey;
2073 CacheEntry::HashingKey(""_ns, aIdExtension, aURISpec, entryKey);
2075 mozilla::MutexAutoLock lock(mLock);
2077 if (mShutdown) {
2078 return;
2081 CacheEntryTable* entries;
2082 RefPtr<CacheEntry> entry;
2084 if (sGlobalEntryTables->Get(contextKey, &entries) &&
2085 entries->Get(entryKey, getter_AddRefs(entry))) {
2086 if (entry->IsFileDoomed()) {
2087 // Need to remove under the lock to avoid possible race leading
2088 // to duplication of the entry per its key.
2089 RemoveExactEntry(entries, entryKey, entry, false);
2090 entry->DoomAlreadyRemoved();
2093 // Entry found, but it's not the entry that has been found doomed
2094 // by the lower eviction layer. Just leave everything unchanged.
2095 return;
2098 RemoveEntryForceValid(contextKey, entryKey);
2101 bool CacheStorageService::GetCacheEntryInfo(
2102 nsILoadContextInfo* aLoadContextInfo, const nsACString& aIdExtension,
2103 const nsACString& aURISpec, EntryInfoCallback* aCallback) {
2104 nsAutoCString contextKey;
2105 CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
2107 nsAutoCString entryKey;
2108 CacheEntry::HashingKey(""_ns, aIdExtension, aURISpec, entryKey);
2110 RefPtr<CacheEntry> entry;
2112 mozilla::MutexAutoLock lock(mLock);
2114 if (mShutdown) {
2115 return false;
2118 CacheEntryTable* entries;
2119 if (!sGlobalEntryTables->Get(contextKey, &entries)) {
2120 return false;
2123 if (!entries->Get(entryKey, getter_AddRefs(entry))) {
2124 return false;
2128 GetCacheEntryInfo(entry, aCallback);
2129 return true;
2132 // static
2133 void CacheStorageService::GetCacheEntryInfo(CacheEntry* aEntry,
2134 EntryInfoCallback* aCallback) {
2135 nsCString const uriSpec = aEntry->GetURI();
2136 nsCString const enhanceId = aEntry->GetEnhanceID();
2138 nsAutoCString entryKey;
2139 aEntry->HashingKeyWithStorage(entryKey);
2141 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(entryKey);
2143 uint32_t dataSize;
2144 if (NS_FAILED(aEntry->GetStorageDataSize(&dataSize))) {
2145 dataSize = 0;
2147 int64_t altDataSize;
2148 if (NS_FAILED(aEntry->GetAltDataSize(&altDataSize))) {
2149 altDataSize = 0;
2151 uint32_t fetchCount;
2152 if (NS_FAILED(aEntry->GetFetchCount(&fetchCount))) {
2153 fetchCount = 0;
2155 uint32_t lastModified;
2156 if (NS_FAILED(aEntry->GetLastModified(&lastModified))) {
2157 lastModified = 0;
2159 uint32_t expirationTime;
2160 if (NS_FAILED(aEntry->GetExpirationTime(&expirationTime))) {
2161 expirationTime = 0;
2164 aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, altDataSize, fetchCount,
2165 lastModified, expirationTime, aEntry->IsPinned(),
2166 info);
2169 // static
2170 uint32_t CacheStorageService::CacheQueueSize(bool highPriority) {
2171 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
2172 // The thread will be null at shutdown.
2173 if (!thread) {
2174 return 0;
2176 return thread->QueueSize(highPriority);
2179 // Telemetry collection
2181 namespace {
2183 bool TelemetryEntryKey(CacheEntry const* entry, nsAutoCString& key) {
2184 nsAutoCString entryKey;
2185 nsresult rv = entry->HashingKey(entryKey);
2186 if (NS_FAILED(rv)) return false;
2188 if (entry->GetStorageID().IsEmpty()) {
2189 // Hopefully this will be const-copied, saves some memory
2190 key = entryKey;
2191 } else {
2192 key.Assign(entry->GetStorageID());
2193 key.Append(':');
2194 key.Append(entryKey);
2197 return true;
2200 } // namespace
2202 void CacheStorageService::TelemetryPrune(TimeStamp& now) {
2203 static TimeDuration const oneMinute = TimeDuration::FromSeconds(60);
2204 static TimeStamp dontPruneUntil = now + oneMinute;
2205 if (now < dontPruneUntil) return;
2207 static TimeDuration const fifteenMinutes = TimeDuration::FromSeconds(900);
2208 for (auto iter = mPurgeTimeStamps.Iter(); !iter.Done(); iter.Next()) {
2209 if (now - iter.Data() > fifteenMinutes) {
2210 // We are not interested in resurrection of entries after 15 minutes
2211 // of time. This is also the limit for the telemetry.
2212 iter.Remove();
2215 dontPruneUntil = now + oneMinute;
2218 void CacheStorageService::TelemetryRecordEntryCreation(
2219 CacheEntry const* entry) {
2220 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
2222 nsAutoCString key;
2223 if (!TelemetryEntryKey(entry, key)) return;
2225 TimeStamp now = TimeStamp::NowLoRes();
2226 TelemetryPrune(now);
2228 // When an entry is craeted (registered actually) we check if there is
2229 // a timestamp marked when this very same cache entry has been removed
2230 // (deregistered) because of over-memory-limit purging. If there is such
2231 // a timestamp found accumulate telemetry on how long the entry was away.
2232 TimeStamp timeStamp;
2233 if (!mPurgeTimeStamps.Get(key, &timeStamp)) return;
2235 mPurgeTimeStamps.Remove(key);
2237 Telemetry::AccumulateTimeDelta(Telemetry::HTTP_CACHE_ENTRY_RELOAD_TIME,
2238 timeStamp, TimeStamp::NowLoRes());
2241 void CacheStorageService::TelemetryRecordEntryRemoval(CacheEntry* entry) {
2242 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
2244 // Doomed entries must not be considered, we are only interested in purged
2245 // entries. Note that the mIsDoomed flag is always set before deregistration
2246 // happens.
2247 if (entry->IsDoomed()) return;
2249 nsAutoCString key;
2250 if (!TelemetryEntryKey(entry, key)) return;
2252 // When an entry is removed (deregistered actually) we put a timestamp for
2253 // this entry to the hashtable so that when the entry is created (registered)
2254 // again we know how long it was away. Also accumulate number of AsyncOpen
2255 // calls on the entry, this tells us how efficiently the pool actually works.
2257 TimeStamp now = TimeStamp::NowLoRes();
2258 TelemetryPrune(now);
2259 mPurgeTimeStamps.InsertOrUpdate(key, now);
2261 Telemetry::Accumulate(Telemetry::HTTP_CACHE_ENTRY_REUSE_COUNT,
2262 entry->UseCount());
2263 Telemetry::AccumulateTimeDelta(Telemetry::HTTP_CACHE_ENTRY_ALIVE_TIME,
2264 entry->LoadStart(), TimeStamp::NowLoRes());
2267 // nsIMemoryReporter
2269 size_t CacheStorageService::SizeOfExcludingThis(
2270 mozilla::MallocSizeOf mallocSizeOf) const {
2271 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
2273 size_t n = 0;
2274 // The elemets are referenced by sGlobalEntryTables and are reported from
2275 // there.
2277 // Entries reported manually in CacheStorageService::CollectReports callback
2278 if (sGlobalEntryTables) {
2279 n += sGlobalEntryTables->ShallowSizeOfIncludingThis(mallocSizeOf);
2281 n += mPurgeTimeStamps.SizeOfExcludingThis(mallocSizeOf);
2283 return n;
2286 size_t CacheStorageService::SizeOfIncludingThis(
2287 mozilla::MallocSizeOf mallocSizeOf) const {
2288 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
2291 NS_IMETHODIMP
2292 CacheStorageService::CollectReports(nsIHandleReportCallback* aHandleReport,
2293 nsISupports* aData, bool aAnonymize) {
2294 MutexAutoLock lock(mLock);
2295 MOZ_COLLECT_REPORT("explicit/network/cache2/io", KIND_HEAP, UNITS_BYTES,
2296 CacheFileIOManager::SizeOfIncludingThis(MallocSizeOf),
2297 "Memory used by the cache IO manager.");
2299 MOZ_COLLECT_REPORT("explicit/network/cache2/index", KIND_HEAP, UNITS_BYTES,
2300 CacheIndex::SizeOfIncludingThis(MallocSizeOf),
2301 "Memory used by the cache index.");
2303 // Report the service instance, this doesn't report entries, done lower
2304 MOZ_COLLECT_REPORT("explicit/network/cache2/service", KIND_HEAP, UNITS_BYTES,
2305 SizeOfIncludingThis(MallocSizeOf),
2306 "Memory used by the cache storage service.");
2308 // Report all entries, each storage separately (by the context key)
2310 // References are:
2311 // sGlobalEntryTables to N CacheEntryTable
2312 // CacheEntryTable to N CacheEntry
2313 // CacheEntry to 1 CacheFile
2314 // CacheFile to
2315 // N CacheFileChunk (keeping the actual data)
2316 // 1 CacheFileMetadata (keeping http headers etc.)
2317 // 1 CacheFileOutputStream
2318 // N CacheFileInputStream
2319 if (sGlobalEntryTables) {
2320 for (const auto& globalEntry : *sGlobalEntryTables) {
2321 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
2323 CacheEntryTable* table = globalEntry.GetWeak();
2325 size_t size = 0;
2326 mozilla::MallocSizeOf mallocSizeOf = CacheStorageService::MallocSizeOf;
2328 size += table->ShallowSizeOfIncludingThis(mallocSizeOf);
2329 for (const auto& tableEntry : *table) {
2330 size += tableEntry.GetKey().SizeOfExcludingThisIfUnshared(mallocSizeOf);
2332 // Bypass memory-only entries, those will be reported when iterating the
2333 // memory only table. Memory-only entries are stored in both ALL_ENTRIES
2334 // and MEMORY_ONLY hashtables.
2335 RefPtr<mozilla::net::CacheEntry> const& entry = tableEntry.GetData();
2336 if (table->Type() == CacheEntryTable::MEMORY_ONLY ||
2337 entry->IsUsingDisk()) {
2338 size += entry->SizeOfIncludingThis(mallocSizeOf);
2342 aHandleReport->Callback(
2343 ""_ns,
2344 nsPrintfCString(
2345 "explicit/network/cache2/%s-storage(%s)",
2346 table->Type() == CacheEntryTable::MEMORY_ONLY ? "memory" : "disk",
2347 aAnonymize ? "<anonymized>"
2348 : globalEntry.GetKey().BeginReading()),
2349 nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, size,
2350 "Memory used by the cache storage."_ns, aData);
2354 return NS_OK;
2357 // nsICacheTesting
2359 NS_IMETHODIMP
2360 CacheStorageService::IOThreadSuspender::Run() {
2361 MonitorAutoLock mon(mMon);
2362 while (!mSignaled) {
2363 mon.Wait();
2365 return NS_OK;
2368 void CacheStorageService::IOThreadSuspender::Notify() {
2369 MonitorAutoLock mon(mMon);
2370 mSignaled = true;
2371 mon.Notify();
2374 NS_IMETHODIMP
2375 CacheStorageService::SuspendCacheIOThread(uint32_t aLevel) {
2376 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
2377 if (!thread) {
2378 return NS_ERROR_NOT_AVAILABLE;
2381 MOZ_ASSERT(!mActiveIOSuspender);
2382 mActiveIOSuspender = new IOThreadSuspender();
2383 return thread->Dispatch(mActiveIOSuspender, aLevel);
2386 NS_IMETHODIMP
2387 CacheStorageService::ResumeCacheIOThread() {
2388 MOZ_ASSERT(mActiveIOSuspender);
2390 RefPtr<IOThreadSuspender> suspender;
2391 suspender.swap(mActiveIOSuspender);
2392 suspender->Notify();
2393 return NS_OK;
2396 NS_IMETHODIMP
2397 CacheStorageService::Flush(nsIObserver* aObserver) {
2398 RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
2399 if (!thread) {
2400 return NS_ERROR_NOT_AVAILABLE;
2403 nsCOMPtr<nsIObserverService> observerService =
2404 mozilla::services::GetObserverService();
2405 if (!observerService) {
2406 return NS_ERROR_NOT_AVAILABLE;
2409 // Adding as weak, the consumer is responsible to keep the reference
2410 // until notified.
2411 observerService->AddObserver(aObserver, "cacheservice:purge-memory-pools",
2412 false);
2414 // This runnable will do the purging and when done, notifies the above
2415 // observer. We dispatch it to the CLOSE level, so all data writes scheduled
2416 // up to this time will be done before this purging happens.
2417 RefPtr<CacheStorageService::PurgeFromMemoryRunnable> r =
2418 new CacheStorageService::PurgeFromMemoryRunnable(this,
2419 CacheEntry::PURGE_WHOLE);
2421 return thread->Dispatch(r, CacheIOThread::WRITE);
2424 } // namespace mozilla::net