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/. */
8 #include "CacheStorageService.h"
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"
23 #include "nsINetworkPredictor.h"
25 #include "nsContentUtils.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
{
44 void AppendMemoryStorageTag(nsAutoCString
& key
) {
45 // Using DEL as the very last ascii-7 character we can use in the list of
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
>;
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
64 * Thread-safe to access, protected by the service mutex.
66 static GlobalEntryTables
* sGlobalEntryTables
;
68 CacheMemoryConsumer::CacheMemoryConsumer(uint32_t 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) {
83 "Network cache reported memory consumption is not at 0, probably "
88 uint32_t CacheStorageService::MemoryPool::Limit() const {
93 limit
= CacheObserver::MetadataMemoryLimit();
96 limit
= CacheObserver::MemoryCacheCapacity();
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
,
112 NS_IMPL_ISUPPORTS(CacheStorageService
, nsICacheStorageService
,
113 nsIMemoryReporter
, nsITimerCallback
, nsICacheTesting
,
116 CacheStorageService
* CacheStorageService::sSelf
= nullptr;
118 CacheStorageService::CacheStorageService() {
119 CacheFileIOManager::Init();
121 MOZ_ASSERT(XRE_IsParentProcess());
125 sGlobalEntryTables
= new GlobalEntryTables();
127 RegisterStrongMemoryReporter(this);
130 CacheStorageService::~CacheStorageService() {
131 LOG(("CacheStorageService::~CacheStorageService"));
135 void CacheStorageService::Shutdown() {
136 mozilla::MutexAutoLock
lock(mLock
);
138 if (mShutdown
) return;
140 LOG(("CacheStorageService::Shutdown - start"));
144 nsCOMPtr
<nsIRunnable
> event
=
145 NewRunnableMethod("net::CacheStorageService::ShutdownBackground", this,
146 &CacheStorageService::ShutdownBackground
);
149 #ifdef NS_FREE_PERMANENT_DATA
150 sGlobalEntryTables
->Clear();
151 delete sGlobalEntryTables
;
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.
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();
178 LOG(("CacheStorageService::ShutdownBackground - done"));
181 // Internal management methods
186 // Base class for particular storage entries visiting
187 class WalkCacheRunnable
: public Runnable
,
188 public CacheStorageService::EntryInfoCallback
{
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() {
201 ProxyReleaseMainThread("WalkCacheRunnable::mCallback", mCallback
);
205 RefPtr
<CacheStorageService
> mService
;
206 nsCOMPtr
<nsICacheStorageVisitor
> mCallback
;
211 MOZ_ATOMIC_BITFIELDS(mAtomicBitfields
, 8, (
212 (bool, NotifyStorage
, 1),
213 (bool, VisitEntries
, 1)
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
{
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); }
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
) {
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
) {
260 for (CacheEntry
* entry
: entries
->Values()) {
261 MOZ_ASSERT(!entry
->IsUsingDisk());
263 mSize
+= entry
->GetMetadataMemoryConsumption();
266 if (NS_SUCCEEDED(entry
->GetDataSize(&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()) {
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
,
286 if (!LoadVisitEntries()) return NS_OK
; // done
288 StoreNotifyStorage(false);
291 LOG((" entry [left=%zu, canceled=%d]", mEntryArray
.Length(),
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);
308 MOZ_CRASH("Bad thread");
309 return NS_ERROR_FAILURE
;
312 NS_DispatchToMainThread(this);
316 virtual ~WalkMemoryCacheRunnable() {
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
{
329 nsCOMPtr
<nsIURI
> uri
;
330 rv
= NS_NewURI(getter_AddRefs(uri
), aURISpec
);
335 rv
= mCallback
->OnCacheEntryInfo(uri
, aIdEnhance
, aDataSize
, aAltDataSize
,
336 aFetchCount
, aLastModifiedTime
,
337 aExpirationTime
, aPinned
, aInfo
);
339 LOG((" callback failed, canceling the walk"));
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
{
354 WalkDiskCacheRunnable(nsILoadContextInfo
* aLoadInfo
, bool aVisitEntries
,
355 nsICacheStorageVisitor
* aVisitor
)
356 : WalkCacheRunnable(aVisitor
, aVisitEntries
),
357 mLoadInfo(aLoadInfo
),
358 mPass(COLLECT_STATS
),
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
);
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
{
381 explicit OnCacheEntryInfoRunnable(WalkDiskCacheRunnable
* aWalker
)
382 : Runnable("net::WalkDiskCacheRunnable::OnCacheEntryInfoRunnable"),
385 NS_IMETHOD
Run() override
{
386 MOZ_ASSERT(NS_IsMainThread());
390 nsCOMPtr
<nsIURI
> uri
;
391 rv
= NS_NewURI(getter_AddRefs(uri
), mURISpec
);
396 rv
= mWalker
->mCallback
->OnCacheEntryInfo(
397 uri
, mIdEnhance
, mDataSize
, mAltDataSize
, mFetchCount
,
398 mLastModifiedTime
, mExpirationTime
, mPinned
, mInfo
);
400 mWalker
->mCancel
= true;
406 RefPtr
<WalkDiskCacheRunnable
> mWalker
;
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};
416 nsCOMPtr
<nsILoadContextInfo
> mInfo
;
419 NS_IMETHOD
Run() override
{
423 if (CacheStorageService::IsOnManagementThread()) {
426 // Get quickly the cache stats.
428 rv
= CacheIndex::GetCacheStats(mLoadInfo
, &size
, &mCount
);
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
;
449 case ITERATE_METADATA
:
450 // Now grab the context iterator.
453 CacheIndex::GetIterator(mLoadInfo
, true, getter_AddRefs(mIter
));
455 // Invoke onCacheEntryVisitCompleted now
456 return NS_DispatchToMainThread(this);
460 while (!mCancel
&& !CacheObserver::ShuttingDown()) {
461 if (CacheIOThread::YieldAndRerun()) return NS_OK
;
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);
484 mCallback
->OnCacheEntryVisitCompleted();
487 MOZ_CRASH("Bad thread");
488 return NS_ERROR_FAILURE
;
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
;
513 NS_DispatchToMainThread(info
);
516 RefPtr
<nsILoadContextInfo
> mLoadInfo
;
518 // First, we collect stats for the load context.
521 // Second, if demanded, we iterate over the entries gethered
522 // from the iterator and call CacheFileIOManager::GetEntryInfo
523 // for each found entry.
527 RefPtr
<CacheIndexIterator
> mIter
;
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);
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;
562 nsresult rv
= target
->IsOnCurrentThread(¤tThread
);
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
) {
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
);
605 nsresult
Run(OriginAttributes
& aOa
) {
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
);
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
);
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
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
);
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
);
668 NS_IMETHODIMP
CacheStorageService::Clear() {
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
);
698 NS_IMETHODIMP
CacheStorageService::ClearOrigin(nsIPrincipal
* aPrincipal
) {
701 if (NS_WARN_IF(!aPrincipal
)) {
702 return NS_ERROR_FAILURE
;
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
);
718 NS_IMETHODIMP
CacheStorageService::ClearOriginAttributes(
719 const nsAString
& aOriginAttributes
) {
722 if (NS_WARN_IF(aOriginAttributes
.IsEmpty())) {
723 return NS_ERROR_FAILURE
;
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
);
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
);
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());
776 StoragePrincipalHelper::PartitionKeyHasBaseDomain(
777 info
->OriginAttributesPtr()->mPartitionKey
, aBaseDomain
)) {
778 keys
.AppendElement(key
);
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();
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
))) {
797 rv
= uri
->GetHost(host
);
798 // Some entries may not have valid hosts. We can skip them.
799 if (NS_FAILED(rv
) || host
.IsEmpty()) {
803 bool hasRootDomain
= false;
804 rv
= HasRootDomain(host
, cBaseDomain
, &hasRootDomain
);
805 if (NS_WARN_IF(NS_FAILED(rv
))) {
809 entriesToDelete
.AppendElement(entry
);
813 // Clear individual matched entries.
814 for (RefPtr
<CacheEntry
>& entry
: entriesToDelete
) {
815 nsAutoCString entryKey
;
816 nsresult rv
= entry
->HashingKey(entryKey
);
818 NS_ERROR("aEntry->HashingKey() failed?");
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
,
836 nsresult
CacheStorageService::ClearOriginInternal(
837 const nsAString
& aOrigin
, const OriginAttributes
& aOriginAttributes
,
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
,
854 NS_ENSURE_SUCCESS(rv
, rv
);
859 CacheEntryTable
* table
= globalEntry
.GetWeak();
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
);
870 rv
= nsContentUtils::GetWebExposedOriginSerialization(uri
, origin
);
871 NS_ENSURE_SUCCESS(rv
, rv
);
873 if (origin
!= aOrigin
) {
877 entriesToDelete
.AppendElement(entry
);
880 for (RefPtr
<CacheEntry
>& entry
: entriesToDelete
) {
881 nsAutoCString entryKey
;
882 rv
= entry
->HashingKey(entryKey
);
884 NS_ERROR("aEntry->HashingKey() failed?");
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
);
901 NS_IMETHODIMP
CacheStorageService::PurgeFromMemory(uint32_t aWhat
) {
905 case PURGE_DISK_DATA_ONLY
:
906 what
= CacheEntry::PURGE_DATA_ONLY_DISK_BACKED
;
910 what
= CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED
;
913 case PURGE_EVERYTHING
:
914 what
= CacheEntry::PURGE_WHOLE
;
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);
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);
949 NS_DispatchToMainThread(this);
953 NS_IMETHODIMP
CacheStorageService::AsyncGetDiskConsumption(
954 nsICacheStorageConsumptionObserver
* aObserver
) {
955 NS_ENSURE_ARG(aObserver
);
959 rv
= CacheIndex::AsyncGetDiskConsumption(aObserver
);
960 NS_ENSURE_SUCCESS(rv
, rv
);
965 NS_IMETHODIMP
CacheStorageService::GetIoTarget(nsIEventTarget
** aEventTarget
) {
966 NS_ENSURE_ARG(aEventTarget
);
968 nsCOMPtr
<nsIEventTarget
> ioTarget
= CacheFileIOManager::IOTarget();
969 ioTarget
.forget(aEventTarget
);
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
});
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?");
1043 mozilla::MutexAutoLock
lock(mLock
);
1046 LOG((" after shutdown"));
1050 if (aOnlyUnreferenced
) {
1051 if (aEntry
->IsReferenced()) {
1052 LOG((" still referenced, not removing"));
1056 if (!aEntry
->IsUsingDisk() &&
1057 IsForcedValidEntry(aEntry
->GetStorageID(), entryKey
)) {
1058 LOG((" forced valid, not removing"));
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 */);
1078 void CacheStorageService::RecordMemoryOnlyEntry(CacheEntry
* aEntry
,
1082 ("CacheStorageService::RecordMemoryOnlyEntry [entry=%p, memory=%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();
1094 LOG((" after shutdown"));
1100 nsAutoCString entryKey
;
1101 rv
= aEntry
->HashingKey(entryKey
);
1102 if (NS_FAILED(rv
)) {
1103 NS_ERROR("aEntry->HashingKey() failed?");
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"));
1117 entries
= sGlobalEntryTables
1120 MakeUnique
<CacheEntryTable
>(CacheEntryTable::MEMORY_ONLY
))
1122 LOG((" new memory-only storage table for %s", memoryStorageID
.get()));
1125 if (aOnlyInMemory
) {
1126 AddExactEntry(entries
, entryKey
, aEntry
, aOverwrite
);
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
)) {
1149 if (data
.validUntil
.IsNull()) {
1150 MOZ_ASSERT_UNREACHABLE("the timeStamp should never be null");
1154 // Entry timeout not reached yet
1155 if (TimeStamp::NowLoRes() <= data
.validUntil
) {
1159 // Entry timeout has been reached
1160 mForcedValidEntries
.Remove(aContextEntryKey
);
1163 glean::predictor::prefetch_use_status
1164 .EnumGet(glean::predictor::PrefetchUseStatusLabel::eWaitedtoolong
)
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
)) {
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
)
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
)
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.
1256 if (mPurgeTimerActive
) {
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
);
1296 LOG((" past shutdown"));
1301 LOG((" timer already up"));
1305 mPurgeTimer
= NS_NewTimer();
1308 mPurgeTimerActive
= true;
1311 rv
= mPurgeTimer
->InitWithCallback(this, 1000, nsITimer::TYPE_ONE_SHOT
);
1312 LOG((" timer init rv=0x%08" PRIx32
, static_cast<uint32_t>(rv
)));
1317 CacheStorageService::Notify(nsITimer
* aTimer
) {
1318 LOG(("CacheStorageService::Notify"));
1320 mozilla::MutexAutoLock
lock(mLock
);
1322 if (aTimer
== mPurgeTimer
) {
1324 mPurgeTimerActive
= false;
1326 mPurgeTimer
= nullptr;
1329 nsCOMPtr
<nsIRunnable
> event
= NewRunnableMethod(
1330 "net::CacheStorageService::PurgeExpiredOrOverMemoryLimit", this,
1331 &CacheStorageService::PurgeExpiredOrOverMemoryLimit
);
1340 CacheStorageService::GetName(nsACString
& aName
) {
1341 aName
.AssignLiteral("CacheStorageService");
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"));
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
1388 if (minprogress
== 0 && CacheIOThread::YieldAndRerun()) {
1392 if (mType
== EType::DISK
) {
1393 mozilla::glean::networking::cache_purge_due_to_memory_limit
1394 .Get("meta_data_file_size_limit"_ns
)
1396 } else if (mType
== EType::MEMORY
) {
1397 mozilla::glean::networking::cache_purge_due_to_memory_limit
1398 .Get("cache_memory_limit"_ns
)
1402 auto r
= PurgeByFrecency(minprogress
);
1403 if (MOZ_LIKELY(r
.isOk())) {
1404 size_t numPurged
= r
.unwrap();
1406 " memory data consumption over the limit, abandoned %zu LFU entries",
1409 // If we hit an error (OOM), do an emergency PurgeAll.
1410 size_t numPurged
= PurgeAll(CacheEntry::PURGE_WHOLE
, minprogress
);
1412 (" memory data consumption over the limit, emergency purged all %zu "
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();
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
)) {
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()) {
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
) {
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
;
1475 explicit mayPurgeEntry(CacheEntry
* 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) {
1505 mayPurgeSorted
.Sort();
1507 size_t numPurged
= 0;
1509 for (auto& checkPurge
: mayPurgeSorted
) {
1510 if (mMemorySize
<= memoryLimit
) {
1514 RefPtr
<CacheEntry
> entry
= checkPurge
.mEntry
;
1516 if (entry
->Purge(CacheEntry::PURGE_WHOLE
)) {
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"));
1528 LOG(("MemoryPool::PurgeByFrecency done"));
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();
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
)) {
1549 LOG((" abandoned entry=%p", entry
.get()));
1552 entry
= std::move(nextEntry
);
1558 // Methods exposed to and used by CacheStorage.
1560 nsresult
CacheStorageService::AddStorageEntry(CacheStorage
const* aStorage
,
1561 const nsACString
& aURI
,
1562 const nsACString
& aIdExtension
,
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
) {
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
=
1601 ->LookupOrInsertWith(
1604 LOG((" new storage entries table for context '%s'",
1605 aContextKey
.BeginReading()));
1606 return MakeUnique
<CacheEntryTable
>(
1607 CacheEntryTable::ALL_ENTRIES
);
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"));
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(),
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();
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.
1647 // Ensure entry for the particular URL
1649 // When replacing with a new entry, always remove the current force-valid
1650 // timestamp, this is the only place to do it.
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()));
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
);
1673 nsresult
CacheStorageService::CheckStorageEntry(CacheStorage
const* aStorage
,
1674 const nsACString
& aURI
,
1675 const nsACString
& aIdExtension
,
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"));
1706 if (!aStorage
->WriteToDisk()) {
1707 // Memory entry, nothing more to do.
1708 LOG((" not found in hash tables"));
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 "));
1728 nsresult
CacheStorageService::GetCacheIndexEntryAttrs(
1729 CacheStorage
const* aStorage
, const nsACString
& aURI
,
1730 const nsACString
& aIdExtension
, bool* aHasAltData
, uint32_t* aFileSizeKb
) {
1733 nsAutoCString contextKey
;
1734 CacheFileUtils::AppendKeyPrefix(aStorage
->LoadInfo(), contextKey
);
1737 ("CacheStorageService::GetCacheIndexEntryAttrs [uri=%s, eid=%s, "
1739 aURI
.BeginReading(), aIdExtension
.BeginReading(), contextKey
.get()));
1741 nsAutoCString fileKey
;
1742 rv
= CacheEntry::HashingKey(contextKey
, aIdExtension
, aURI
, fileKey
);
1743 if (NS_FAILED(rv
)) {
1747 *aHasAltData
= false;
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
)) {
1760 if (status
!= CacheIndex::EXISTS
) {
1761 return NS_ERROR_CACHE_KEY_NOT_FOUND
;
1769 class CacheEntryDoomByKeyCallback
: public CacheFileIOListener
,
1770 public nsIRunnable
{
1772 NS_DECL_THREADSAFE_ISUPPORTS
1775 explicit CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback
* aCallback
)
1776 : mCallback(aCallback
), mResult(NS_ERROR_NOT_INITIALIZED
) {}
1779 virtual ~CacheEntryDoomByKeyCallback();
1781 NS_IMETHOD
OnFileOpened(CacheFileHandle
* aHandle
, nsresult aResult
) override
{
1784 NS_IMETHOD
OnDataWritten(CacheFileHandle
* aHandle
, const char* aBuf
,
1785 nsresult aResult
) override
{
1788 NS_IMETHOD
OnDataRead(CacheFileHandle
* aHandle
, char* aBuf
,
1789 nsresult aResult
) override
{
1792 NS_IMETHOD
OnFileDoomed(CacheFileHandle
* aHandle
, nsresult aResult
) override
;
1793 NS_IMETHOD
OnEOFSet(CacheFileHandle
* aHandle
, nsresult aResult
) override
{
1796 NS_IMETHOD
OnFileRenamed(CacheFileHandle
* aHandle
,
1797 nsresult aResult
) override
{
1801 nsCOMPtr
<nsICacheEntryDoomCallback
> mCallback
;
1805 CacheEntryDoomByKeyCallback::~CacheEntryDoomByKeyCallback() {
1807 ProxyReleaseMainThread("CacheEntryDoomByKeyCallback::mCallback", mCallback
);
1811 NS_IMETHODIMP
CacheEntryDoomByKeyCallback::OnFileDoomed(
1812 CacheFileHandle
* aHandle
, nsresult aResult
) {
1813 if (!mCallback
) return NS_OK
;
1816 if (NS_IsMainThread()) {
1819 NS_DispatchToMainThread(this);
1825 NS_IMETHODIMP
CacheEntryDoomByKeyCallback::Run() {
1826 mCallback
->OnCacheEntryDoomed(mResult
);
1830 NS_IMPL_ISUPPORTS(CacheEntryDoomByKeyCallback
, CacheFileIOListener
,
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,
1863 (" purging entry %p for %s [storage use disk=%d, entry use "
1865 entry
.get(), entryKey
.get(), aStorage
->WriteToDisk(),
1866 entry
->IsUsingDisk()));
1867 entries
->Remove(entryKey
);
1869 // Otherwise, leave it
1871 (" leaving entry %p for %s [storage use disk=%d, entry use "
1873 entry
.get(), entryKey
.get(), aStorage
->WriteToDisk(),
1874 entry
->IsUsingDisk()));
1881 RemoveEntryForceValid(contextKey
, entryKey
);
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
);
1909 class Callback
: public Runnable
{
1911 explicit Callback(nsICacheEntryDoomCallback
* aCallback
)
1912 : mozilla::Runnable("Callback"), mCallback(aCallback
) {}
1913 NS_IMETHOD
Run() override
{
1914 mCallback
->OnCacheEntryDoomed(NS_ERROR_NOT_AVAILABLE
);
1917 nsCOMPtr
<nsICacheEntryDoomCallback
> mCallback
;
1921 RefPtr
<Runnable
> callback
= new Callback(aCallback
);
1922 return NS_DispatchToMainThread(callback
);
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(),
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
);
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
)) {
1972 if (memoryEntries
) {
1973 RemoveExactEntry(memoryEntries
, iter
.Key(), entry
, false);
1979 if (aContext
&& !aContext
->IsPrivate()) {
1980 LOG((" dooming disk entries"));
1981 CacheFileIOManager::EvictByContext(aContext
, aPinned
, u
""_ns
);
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
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
);
2006 for (auto iter
= mForcedValidEntries
.Iter(); !iter
.Done(); iter
.Next()) {
2008 DebugOnly
<nsresult
> rv
= CacheFileUtils::KeyMatchesLoadContextInfo(
2009 iter
.Key(), aContext
, &matches
);
2010 MOZ_ASSERT(NS_SUCCEEDED(rv
));
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
{
2029 explicit Callback(nsICacheEntryDoomCallback
* aCallback
)
2030 : mozilla::Runnable("Callback"), mCallback(aCallback
) {}
2031 NS_IMETHOD
Run() override
{
2032 mCallback
->OnCacheEntryDoomed(NS_OK
);
2035 nsCOMPtr
<nsICacheEntryDoomCallback
> mCallback
;
2039 RefPtr
<Runnable
> callback
= new Callback(aCallback
);
2040 return NS_DispatchToMainThread(callback
);
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
);
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.
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
);
2118 CacheEntryTable
* entries
;
2119 if (!sGlobalEntryTables
->Get(contextKey
, &entries
)) {
2123 if (!entries
->Get(entryKey
, getter_AddRefs(entry
))) {
2128 GetCacheEntryInfo(entry
, aCallback
);
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
);
2144 if (NS_FAILED(aEntry
->GetStorageDataSize(&dataSize
))) {
2147 int64_t altDataSize
;
2148 if (NS_FAILED(aEntry
->GetAltDataSize(&altDataSize
))) {
2151 uint32_t fetchCount
;
2152 if (NS_FAILED(aEntry
->GetFetchCount(&fetchCount
))) {
2155 uint32_t lastModified
;
2156 if (NS_FAILED(aEntry
->GetLastModified(&lastModified
))) {
2159 uint32_t expirationTime
;
2160 if (NS_FAILED(aEntry
->GetExpirationTime(&expirationTime
))) {
2164 aCallback
->OnEntryInfo(uriSpec
, enhanceId
, dataSize
, altDataSize
, fetchCount
,
2165 lastModified
, expirationTime
, aEntry
->IsPinned(),
2170 uint32_t CacheStorageService::CacheQueueSize(bool highPriority
) {
2171 RefPtr
<CacheIOThread
> thread
= CacheFileIOManager::IOThread();
2172 // The thread will be null at shutdown.
2176 return thread
->QueueSize(highPriority
);
2179 // Telemetry collection
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
2192 key
.Assign(entry
->GetStorageID());
2194 key
.Append(entryKey
);
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.
2215 dontPruneUntil
= now
+ oneMinute
;
2218 void CacheStorageService::TelemetryRecordEntryCreation(
2219 CacheEntry
const* entry
) {
2220 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
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
2247 if (entry
->IsDoomed()) return;
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
,
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();
2274 // The elemets are referenced by sGlobalEntryTables and are reported from
2277 // Entries reported manually in CacheStorageService::CollectReports callback
2278 if (sGlobalEntryTables
) {
2279 n
+= sGlobalEntryTables
->ShallowSizeOfIncludingThis(mallocSizeOf
);
2281 n
+= mPurgeTimeStamps
.SizeOfExcludingThis(mallocSizeOf
);
2286 size_t CacheStorageService::SizeOfIncludingThis(
2287 mozilla::MallocSizeOf mallocSizeOf
) const {
2288 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf
);
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)
2311 // sGlobalEntryTables to N CacheEntryTable
2312 // CacheEntryTable to N CacheEntry
2313 // CacheEntry to 1 CacheFile
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();
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(
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
);
2360 CacheStorageService::IOThreadSuspender::Run() {
2361 MonitorAutoLock
mon(mMon
);
2362 while (!mSignaled
) {
2368 void CacheStorageService::IOThreadSuspender::Notify() {
2369 MonitorAutoLock
mon(mMon
);
2375 CacheStorageService::SuspendCacheIOThread(uint32_t aLevel
) {
2376 RefPtr
<CacheIOThread
> thread
= CacheFileIOManager::IOThread();
2378 return NS_ERROR_NOT_AVAILABLE
;
2381 MOZ_ASSERT(!mActiveIOSuspender
);
2382 mActiveIOSuspender
= new IOThreadSuspender();
2383 return thread
->Dispatch(mActiveIOSuspender
, aLevel
);
2387 CacheStorageService::ResumeCacheIOThread() {
2388 MOZ_ASSERT(mActiveIOSuspender
);
2390 RefPtr
<IOThreadSuspender
> suspender
;
2391 suspender
.swap(mActiveIOSuspender
);
2392 suspender
->Notify();
2397 CacheStorageService::Flush(nsIObserver
* aObserver
) {
2398 RefPtr
<CacheIOThread
> thread
= CacheFileIOManager::IOThread();
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
2411 observerService
->AddObserver(aObserver
, "cacheservice:purge-memory-pools",
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