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