1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "CacheFileContextEvictor.h"
7 #include "CacheFileIOManager.h"
8 #include "CacheFileMetadata.h"
9 #include "CacheIndex.h"
10 #include "CacheIndexIterator.h"
11 #include "CacheFileUtils.h"
12 #include "CacheObserver.h"
14 #include "LoadContextInfo.h"
15 #include "nsThreadUtils.h"
17 #include "nsIDirectoryEnumerator.h"
18 #include "mozilla/Base64.h"
19 #include "mozilla/IntegerPrintfMacros.h"
20 #include "nsContentUtils.h"
21 #include "nsNetUtil.h"
23 namespace mozilla::net
{
25 #define CONTEXT_EVICTION_PREFIX "ce_"
26 const uint32_t kContextEvictionPrefixLength
=
27 sizeof(CONTEXT_EVICTION_PREFIX
) - 1;
29 bool CacheFileContextEvictor::sDiskAlreadySearched
= false;
31 CacheFileContextEvictor::CacheFileContextEvictor() {
32 LOG(("CacheFileContextEvictor::CacheFileContextEvictor() [this=%p]", this));
35 CacheFileContextEvictor::~CacheFileContextEvictor() {
36 LOG(("CacheFileContextEvictor::~CacheFileContextEvictor() [this=%p]", this));
39 nsresult
CacheFileContextEvictor::Init(nsIFile
* aCacheDirectory
) {
40 LOG(("CacheFileContextEvictor::Init()"));
44 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
46 CacheIndex::IsUpToDate(&mIndexIsUpToDate
);
48 mCacheDirectory
= aCacheDirectory
;
50 rv
= aCacheDirectory
->Clone(getter_AddRefs(mEntriesDir
));
51 if (NS_WARN_IF(NS_FAILED(rv
))) {
55 rv
= mEntriesDir
->AppendNative(nsLiteralCString(ENTRIES_DIR
));
56 if (NS_WARN_IF(NS_FAILED(rv
))) {
60 if (!sDiskAlreadySearched
) {
61 LoadEvictInfoFromDisk();
62 if ((mEntries
.Length() != 0) && mIndexIsUpToDate
) {
71 void CacheFileContextEvictor::Shutdown() {
72 LOG(("CacheFileContextEvictor::Shutdown()"));
74 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
79 uint32_t CacheFileContextEvictor::ContextsCount() {
80 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
82 return mEntries
.Length();
85 nsresult
CacheFileContextEvictor::AddContext(
86 nsILoadContextInfo
* aLoadContextInfo
, bool aPinned
,
87 const nsAString
& aOrigin
) {
89 ("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p, "
91 this, aLoadContextInfo
, aPinned
));
95 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
97 CacheFileContextEvictorEntry
* entry
= nullptr;
98 if (aLoadContextInfo
) {
99 for (uint32_t i
= 0; i
< mEntries
.Length(); ++i
) {
100 if (mEntries
[i
]->mInfo
&& mEntries
[i
]->mInfo
->Equals(aLoadContextInfo
) &&
101 mEntries
[i
]->mPinned
== aPinned
&&
102 mEntries
[i
]->mOrigin
.Equals(aOrigin
)) {
103 entry
= mEntries
[i
].get();
108 // Not providing load context info means we want to delete everything,
109 // so let's not bother with any currently running context cleanups
110 // for the same pinning state.
111 for (uint32_t i
= mEntries
.Length(); i
> 0;) {
113 if (mEntries
[i
]->mInfo
&& mEntries
[i
]->mPinned
== aPinned
) {
114 RemoveEvictInfoFromDisk(mEntries
[i
]->mInfo
, mEntries
[i
]->mPinned
,
115 mEntries
[i
]->mOrigin
);
116 mEntries
.RemoveElementAt(i
);
122 entry
= new CacheFileContextEvictorEntry();
123 entry
->mInfo
= aLoadContextInfo
;
124 entry
->mPinned
= aPinned
;
125 entry
->mOrigin
= aOrigin
;
126 mEntries
.AppendElement(WrapUnique(entry
));
129 entry
->mTimeStamp
= PR_Now() / PR_USEC_PER_MSEC
;
131 PersistEvictionInfoToDisk(aLoadContextInfo
, aPinned
, aOrigin
);
133 if (mIndexIsUpToDate
) {
134 // Already existing context could be added again, in this case the iterator
135 // would be recreated. Close the old iterator explicitely.
136 if (entry
->mIterator
) {
137 entry
->mIterator
->Close();
138 entry
->mIterator
= nullptr;
141 rv
= CacheIndex::GetIterator(aLoadContextInfo
, false,
142 getter_AddRefs(entry
->mIterator
));
144 // This could probably happen during shutdown. Remove the entry from
145 // the array, but leave the info on the disk. No entry can be opened
146 // during shutdown and we'll load the eviction info on next start.
148 ("CacheFileContextEvictor::AddContext() - Cannot get an iterator. "
149 "[rv=0x%08" PRIx32
"]",
150 static_cast<uint32_t>(rv
)));
151 mEntries
.RemoveElement(entry
);
161 void CacheFileContextEvictor::CacheIndexStateChanged() {
162 LOG(("CacheFileContextEvictor::CacheIndexStateChanged() [this=%p]", this));
164 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
166 bool isUpToDate
= false;
167 CacheIndex::IsUpToDate(&isUpToDate
);
168 if (mEntries
.Length() == 0) {
169 // Just save the state and exit, since there is nothing to do
170 mIndexIsUpToDate
= isUpToDate
;
174 if (!isUpToDate
&& !mIndexIsUpToDate
) {
175 // Index is outdated and status has not changed, nothing to do.
179 if (isUpToDate
&& mIndexIsUpToDate
) {
180 // Status has not changed, but make sure the eviction is running.
185 // We're not evicting, but we should be evicting?!
187 ("CacheFileContextEvictor::CacheIndexStateChanged() - Index is up to "
188 "date, we have some context to evict but eviction is not running! "
192 mIndexIsUpToDate
= isUpToDate
;
194 if (mIndexIsUpToDate
) {
202 void CacheFileContextEvictor::WasEvicted(const nsACString
& aKey
, nsIFile
* aFile
,
203 bool* aEvictedAsPinned
,
204 bool* aEvictedAsNonPinned
) {
205 LOG(("CacheFileContextEvictor::WasEvicted() [key=%s]",
206 PromiseFlatCString(aKey
).get()));
208 *aEvictedAsPinned
= false;
209 *aEvictedAsNonPinned
= false;
211 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
213 nsCOMPtr
<nsILoadContextInfo
> info
= CacheFileUtils::ParseKey(aKey
);
216 LOG(("CacheFileContextEvictor::WasEvicted() - Cannot parse key!"));
220 for (uint32_t i
= 0; i
< mEntries
.Length(); ++i
) {
221 const auto& entry
= mEntries
[i
];
223 if (entry
->mInfo
&& !info
->Equals(entry
->mInfo
)) {
227 PRTime lastModifiedTime
;
228 if (NS_FAILED(aFile
->GetLastModifiedTime(&lastModifiedTime
))) {
230 ("CacheFileContextEvictor::WasEvicted() - Cannot get last modified "
231 "time, returning."));
235 if (lastModifiedTime
> entry
->mTimeStamp
) {
236 // File has been modified since context eviction.
241 ("CacheFileContextEvictor::WasEvicted() - evicted [pinning=%d, "
242 "mTimeStamp=%" PRId64
", lastModifiedTime=%" PRId64
"]",
243 entry
->mPinned
, entry
->mTimeStamp
, lastModifiedTime
));
245 if (entry
->mPinned
) {
246 *aEvictedAsPinned
= true;
248 *aEvictedAsNonPinned
= true;
253 nsresult
CacheFileContextEvictor::PersistEvictionInfoToDisk(
254 nsILoadContextInfo
* aLoadContextInfo
, bool aPinned
,
255 const nsAString
& aOrigin
) {
257 ("CacheFileContextEvictor::PersistEvictionInfoToDisk() [this=%p, "
258 "loadContextInfo=%p]",
259 this, aLoadContextInfo
));
263 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
265 nsCOMPtr
<nsIFile
> file
;
266 rv
= GetContextFile(aLoadContextInfo
, aPinned
, aOrigin
, getter_AddRefs(file
));
267 if (NS_WARN_IF(NS_FAILED(rv
))) {
271 nsCString path
= file
->HumanReadablePath();
275 file
->OpenNSPRFileDesc(PR_RDWR
| PR_CREATE_FILE
| PR_TRUNCATE
, 0600, &fd
);
276 if (NS_WARN_IF(NS_FAILED(rv
))) {
278 ("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Creating file "
279 "failed! [path=%s, rv=0x%08" PRIx32
"]",
280 path
.get(), static_cast<uint32_t>(rv
)));
287 ("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Successfully "
288 "created file. [path=%s]",
294 nsresult
CacheFileContextEvictor::RemoveEvictInfoFromDisk(
295 nsILoadContextInfo
* aLoadContextInfo
, bool aPinned
,
296 const nsAString
& aOrigin
) {
298 ("CacheFileContextEvictor::RemoveEvictInfoFromDisk() [this=%p, "
299 "loadContextInfo=%p]",
300 this, aLoadContextInfo
));
304 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
306 nsCOMPtr
<nsIFile
> file
;
307 rv
= GetContextFile(aLoadContextInfo
, aPinned
, aOrigin
, getter_AddRefs(file
));
308 if (NS_WARN_IF(NS_FAILED(rv
))) {
312 nsCString path
= file
->HumanReadablePath();
314 rv
= file
->Remove(false);
315 if (NS_WARN_IF(NS_FAILED(rv
))) {
317 ("CacheFileContextEvictor::RemoveEvictionInfoFromDisk() - Removing file"
318 " failed! [path=%s, rv=0x%08" PRIx32
"]",
319 path
.get(), static_cast<uint32_t>(rv
)));
324 ("CacheFileContextEvictor::RemoveEvictionInfoFromDisk() - Successfully "
325 "removed file. [path=%s]",
331 nsresult
CacheFileContextEvictor::LoadEvictInfoFromDisk() {
332 LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() [this=%p]", this));
336 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
338 sDiskAlreadySearched
= true;
340 nsCOMPtr
<nsIDirectoryEnumerator
> dirEnum
;
341 rv
= mCacheDirectory
->GetDirectoryEntries(getter_AddRefs(dirEnum
));
342 if (NS_WARN_IF(NS_FAILED(rv
))) {
347 nsCOMPtr
<nsIFile
> file
;
348 rv
= dirEnum
->GetNextFile(getter_AddRefs(file
));
354 file
->IsDirectory(&isDir
);
360 rv
= file
->GetNativeLeafName(leaf
);
363 ("CacheFileContextEvictor::LoadEvictInfoFromDisk() - "
364 "GetNativeLeafName() failed! Skipping file."));
368 if (leaf
.Length() < kContextEvictionPrefixLength
) {
372 if (!StringBeginsWith(leaf
, nsLiteralCString(CONTEXT_EVICTION_PREFIX
))) {
376 nsAutoCString encoded
;
377 encoded
= Substring(leaf
, kContextEvictionPrefixLength
);
378 encoded
.ReplaceChar('-', '/');
380 nsAutoCString decoded
;
381 rv
= Base64Decode(encoded
, decoded
);
384 ("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Base64 decoding "
385 "failed. Removing the file. [file=%s]",
391 bool pinned
= decoded
[0] == '\t';
393 decoded
= Substring(decoded
, 1);
396 // Let's see if we have an origin.
397 nsAutoCString origin
;
398 if (decoded
.Contains('\t')) {
399 auto split
= decoded
.Split('\t');
400 MOZ_ASSERT(decoded
.CountChar('\t') == 1);
402 auto splitIt
= split
.begin();
408 nsCOMPtr
<nsILoadContextInfo
> info
;
409 if (!"*"_ns
.Equals(decoded
)) {
410 // "*" is indication of 'delete all', info left null will pass
411 // to CacheFileContextEvictor::AddContext and clear all the cache data.
412 info
= CacheFileUtils::ParseKey(decoded
);
415 ("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Cannot parse "
416 "context key, removing file. [contextKey=%s, file=%s]",
417 decoded
.get(), leaf
.get()));
423 PRTime lastModifiedTime
;
424 rv
= file
->GetLastModifiedTime(&lastModifiedTime
);
429 CacheFileContextEvictorEntry
* entry
= new CacheFileContextEvictorEntry();
431 entry
->mPinned
= pinned
;
432 CopyUTF8toUTF16(origin
, entry
->mOrigin
);
433 entry
->mTimeStamp
= lastModifiedTime
;
434 mEntries
.AppendElement(entry
);
440 nsresult
CacheFileContextEvictor::GetContextFile(
441 nsILoadContextInfo
* aLoadContextInfo
, bool aPinned
,
442 const nsAString
& aOrigin
, nsIFile
** _retval
) {
445 nsAutoCString keyPrefix
;
447 // Mark pinned context files with a tab char at the start.
448 // Tab is chosen because it can never be used as a context key tag.
449 keyPrefix
.Append('\t');
451 if (aLoadContextInfo
) {
452 CacheFileUtils::AppendKeyPrefix(aLoadContextInfo
, keyPrefix
);
454 keyPrefix
.Append('*');
456 if (!aOrigin
.IsEmpty()) {
457 keyPrefix
.Append('\t');
458 keyPrefix
.Append(NS_ConvertUTF16toUTF8(aOrigin
));
461 nsAutoCString leafName
;
462 leafName
.AssignLiteral(CONTEXT_EVICTION_PREFIX
);
464 rv
= Base64EncodeAppend(keyPrefix
, leafName
);
465 if (NS_WARN_IF(NS_FAILED(rv
))) {
469 // Replace '/' with '-' since '/' cannot be part of the filename.
470 leafName
.ReplaceChar('/', '-');
472 nsCOMPtr
<nsIFile
> file
;
473 rv
= mCacheDirectory
->Clone(getter_AddRefs(file
));
474 if (NS_WARN_IF(NS_FAILED(rv
))) {
478 rv
= file
->AppendNative(leafName
);
479 if (NS_WARN_IF(NS_FAILED(rv
))) {
487 void CacheFileContextEvictor::CreateIterators() {
488 LOG(("CacheFileContextEvictor::CreateIterators() [this=%p]", this));
494 for (uint32_t i
= 0; i
< mEntries
.Length();) {
495 rv
= CacheIndex::GetIterator(mEntries
[i
]->mInfo
, false,
496 getter_AddRefs(mEntries
[i
]->mIterator
));
499 ("CacheFileContextEvictor::CreateIterators() - Cannot get an iterator"
500 ". [rv=0x%08" PRIx32
"]",
501 static_cast<uint32_t>(rv
)));
502 mEntries
.RemoveElementAt(i
);
510 void CacheFileContextEvictor::CloseIterators() {
511 LOG(("CacheFileContextEvictor::CloseIterators() [this=%p]", this));
513 for (uint32_t i
= 0; i
< mEntries
.Length(); ++i
) {
514 if (mEntries
[i
]->mIterator
) {
515 mEntries
[i
]->mIterator
->Close();
516 mEntries
[i
]->mIterator
= nullptr;
521 void CacheFileContextEvictor::StartEvicting() {
522 LOG(("CacheFileContextEvictor::StartEvicting() [this=%p]", this));
524 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
527 LOG(("CacheFileContextEvictor::StartEvicting() - already evicting."));
531 if (mEntries
.Length() == 0) {
532 LOG(("CacheFileContextEvictor::StartEvicting() - no context to evict."));
536 nsCOMPtr
<nsIRunnable
> ev
=
537 NewRunnableMethod("net::CacheFileContextEvictor::EvictEntries", this,
538 &CacheFileContextEvictor::EvictEntries
);
540 RefPtr
<CacheIOThread
> ioThread
= CacheFileIOManager::IOThread();
542 nsresult rv
= ioThread
->Dispatch(ev
, CacheIOThread::EVICT
);
545 ("CacheFileContextEvictor::StartEvicting() - Cannot dispatch event to "
546 "IO thread. [rv=0x%08" PRIx32
"]",
547 static_cast<uint32_t>(rv
)));
553 void CacheFileContextEvictor::EvictEntries() {
554 LOG(("CacheFileContextEvictor::EvictEntries()"));
558 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
562 if (!mIndexIsUpToDate
) {
564 ("CacheFileContextEvictor::EvictEntries() - Stopping evicting due to "
570 if (CacheObserver::ShuttingDown()) {
572 ("CacheFileContextEvictor::EvictEntries() - Stopping evicting due to "
575 true; // We don't want to start eviction again during shutdown
576 // process. Setting this flag to true ensures it.
580 if (CacheIOThread::YieldAndRerun()) {
582 ("CacheFileContextEvictor::EvictEntries() - Breaking loop for higher "
588 if (mEntries
.Length() == 0) {
590 ("CacheFileContextEvictor::EvictEntries() - Stopping evicting, there "
591 "is no context to evict."));
593 // Allow index to notify AsyncGetDiskConsumption callbacks. The size is
595 CacheIndex::OnAsyncEviction(false);
600 rv
= mEntries
[0]->mIterator
->GetNextHash(&hash
);
601 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
603 ("CacheFileContextEvictor::EvictEntries() - No more entries left in "
604 "iterator. [iterator=%p, info=%p]",
605 mEntries
[0]->mIterator
.get(), mEntries
[0]->mInfo
.get()));
606 RemoveEvictInfoFromDisk(mEntries
[0]->mInfo
, mEntries
[0]->mPinned
,
607 mEntries
[0]->mOrigin
);
608 mEntries
.RemoveElementAt(0);
613 ("CacheFileContextEvictor::EvictEntries() - Iterator failed to "
614 "provide next hash (shutdown?), keeping eviction info on disk."
615 " [iterator=%p, info=%p]",
616 mEntries
[0]->mIterator
.get(), mEntries
[0]->mInfo
.get()));
617 mEntries
.RemoveElementAt(0);
622 ("CacheFileContextEvictor::EvictEntries() - Processing hash. "
623 "[hash=%08x%08x%08x%08x%08x, iterator=%p, info=%p]",
624 LOGSHA1(&hash
), mEntries
[0]->mIterator
.get(),
625 mEntries
[0]->mInfo
.get()));
627 RefPtr
<CacheFileHandle
> handle
;
628 CacheFileIOManager::gInstance
->mHandles
.GetHandle(&hash
,
629 getter_AddRefs(handle
));
631 // We doom any active handle in CacheFileIOManager::EvictByContext(), so
632 // this must be a new one. Skip it.
634 ("CacheFileContextEvictor::EvictEntries() - Skipping entry since we "
635 "found an active handle. [handle=%p]",
640 CacheIndex::EntryStatus status
;
642 auto callback
= [&pinned
](const CacheIndexEntry
* aEntry
) {
643 pinned
= aEntry
->IsPinned();
645 rv
= CacheIndex::HasEntry(hash
, &status
, callback
);
646 // This must never fail, since eviction (this code) happens only when the
647 // index is up-to-date and thus the informatin is known.
648 MOZ_ASSERT(NS_SUCCEEDED(rv
));
650 if (pinned
!= mEntries
[0]->mPinned
) {
652 ("CacheFileContextEvictor::EvictEntries() - Skipping entry since "
654 "doesn't match [evicting pinned=%d, entry pinned=%d]",
655 mEntries
[0]->mPinned
, pinned
));
659 if (!mEntries
[0]->mOrigin
.IsEmpty()) {
660 nsCOMPtr
<nsIFile
> file
;
661 CacheFileIOManager::gInstance
->GetFile(&hash
, getter_AddRefs(file
));
663 // Read metadata from the file synchronously
664 RefPtr
<CacheFileMetadata
> metadata
= new CacheFileMetadata();
665 rv
= metadata
->SyncReadMetadata(file
);
666 if (NS_WARN_IF(NS_FAILED(rv
))) {
670 // Now get the context + enhance id + URL from the key.
671 nsAutoCString uriSpec
;
672 RefPtr
<nsILoadContextInfo
> info
=
673 CacheFileUtils::ParseKey(metadata
->GetKey(), nullptr, &uriSpec
);
679 nsCOMPtr
<nsIURI
> uri
;
680 rv
= NS_NewURI(getter_AddRefs(uri
), uriSpec
);
683 ("CacheFileContextEvictor::EvictEntries() - Skipping entry since "
684 "NS_NewURI failed to parse the uriSpec"));
688 nsAutoString urlOrigin
;
689 rv
= nsContentUtils::GetUTFOrigin(uri
, urlOrigin
);
692 ("CacheFileContextEvictor::EvictEntries() - Skipping entry since "
693 "We failed to extract an origin"));
697 if (!urlOrigin
.Equals(mEntries
[0]->mOrigin
)) {
699 ("CacheFileContextEvictor::EvictEntries() - Skipping entry since "
706 nsAutoCString leafName
;
707 CacheFileIOManager::HashToStr(&hash
, leafName
);
709 PRTime lastModifiedTime
;
710 nsCOMPtr
<nsIFile
> file
;
711 rv
= mEntriesDir
->Clone(getter_AddRefs(file
));
712 if (NS_SUCCEEDED(rv
)) {
713 rv
= file
->AppendNative(leafName
);
715 if (NS_SUCCEEDED(rv
)) {
716 rv
= file
->GetLastModifiedTime(&lastModifiedTime
);
720 ("CacheFileContextEvictor::EvictEntries() - Cannot get last modified "
721 "time, skipping entry."));
725 if (lastModifiedTime
> mEntries
[0]->mTimeStamp
) {
727 ("CacheFileContextEvictor::EvictEntries() - Skipping newer entry. "
728 "[mTimeStamp=%" PRId64
", lastModifiedTime=%" PRId64
"]",
729 mEntries
[0]->mTimeStamp
, lastModifiedTime
));
733 LOG(("CacheFileContextEvictor::EvictEntries - Removing entry."));
735 CacheIndex::RemoveEntry(&hash
);
738 MOZ_ASSERT_UNREACHABLE("We should never get here");
741 } // namespace mozilla::net