tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit d7a5e533b313308f47a0244975a0b426b2a9fdaa
parent 29767dae3f0bbd8b2542ead07b935c286e480487
Author: Randell Jesup <rjesup@mozilla.com>
Date:   Mon,  3 Nov 2025 18:26:15 +0000

Bug 1996560: Clear from dictionary cache on file removal, not metadata removal r=necko-reviewers,valentin,sunil

Introduces a new cache entry type to ease eviction scan.  Makes dictionary
origin removal depend directly on the origin being empty.
Also fix an issue with RemoveOrigin()

Differential Revision: https://phabricator.services.mozilla.com/D270169

Diffstat:
Mnetwerk/cache2/CacheEntry.cpp | 14++++++++++++++
Mnetwerk/cache2/CacheEntry.h | 4++++
Mnetwerk/cache2/CacheIndex.cpp | 12+++++++++---
Mnetwerk/cache2/CacheStorageService.cpp | 9---------
Mnetwerk/cache2/Dictionary.cpp | 86++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mnetwerk/cache2/Dictionary.h | 5+++--
Mnetwerk/cache2/nsICacheEntry.idl | 8+++++++-
Mnetwerk/protocol/about/nsAboutCacheEntry.cpp | 23++++++++++++++---------
8 files changed, 108 insertions(+), 53 deletions(-)

diff --git a/netwerk/cache2/CacheEntry.cpp b/netwerk/cache2/CacheEntry.cpp @@ -1487,6 +1487,11 @@ nsresult CacheEntry::SetMetaDataElement(const char* aKey, const char* aValue) { return mFile->SetElement(aKey, aValue); } +nsresult CacheEntry::GetIsEmpty(bool* aEmpty) { + *aEmpty = GetMetadataMemoryConsumption() == 0; + return NS_OK; +} + nsresult CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor* aVisitor) { NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); @@ -1770,6 +1775,15 @@ void CacheEntry::DoomAlreadyRemoved() { mIsDoomed = true; + // Remove from DictionaryCache immediately, to ensure the removal is + // synchronous + LOG(("DoomAlreadyRemoved [entry=%p removed]", this)); + if (mEnhanceID.EqualsLiteral("dict:")) { + DictionaryCache::RemoveOriginFor(mURI); + } else { + DictionaryCache::RemoveDictionaryFor(mURI); + } + // Pretend pinning is know. This entry is now doomed for good, so don't // bother with defering doom because of unknown pinning state any more. mPinningKnown = true; diff --git a/netwerk/cache2/CacheEntry.h b/netwerk/cache2/CacheEntry.h @@ -90,6 +90,7 @@ class CacheEntry final : public nsIRunnable, nsresult AsyncDoom(nsICacheEntryDoomCallback* aCallback); nsresult GetMetaDataElement(const char* key, char** aRetval); nsresult SetMetaDataElement(const char* key, const char* value); + nsresult GetIsEmpty(bool* aEmpty); nsresult VisitMetaData(nsICacheEntryMetaDataVisitor* visitor); nsresult MetaDataReady(void); nsresult SetValid(void); @@ -525,6 +526,9 @@ class CacheEntryHandle final : public nsICacheEntry { NS_IMETHOD SetMetaDataElement(const char* key, const char* value) override { return mEntry->SetMetaDataElement(key, value); } + NS_IMETHOD GetIsEmpty(bool* empty) override { + return mEntry->GetIsEmpty(empty); + } NS_IMETHOD VisitMetaData(nsICacheEntryMetaDataVisitor* visitor) override { return mEntry->VisitMetaData(visitor); } diff --git a/netwerk/cache2/CacheIndex.cpp b/netwerk/cache2/CacheIndex.cpp @@ -1349,8 +1349,14 @@ nsresult CacheIndex::GetEntryForEviction(EvictionSortedSnapshot& aSnapshot, ++skipped; - if (evictMedia && CacheIndexEntry::GetContentType(rec) != - nsICacheEntry::CONTENT_TYPE_MEDIA) { + uint32_t type = CacheIndexEntry::GetContentType(rec); + + if (evictMedia && type != nsICacheEntry::CONTENT_TYPE_MEDIA) { + continue; + } + + if (type == nsICacheEntry::CONTENT_TYPE_DICTIONARY) { + // Let them be removed by becoming empty and removing themselves continue; } @@ -3871,7 +3877,7 @@ void CacheIndex::DoTelemetryReport() { static const nsLiteralCString contentTypeNames[nsICacheEntry::CONTENT_TYPE_LAST] = { "UNKNOWN"_ns, "OTHER"_ns, "JAVASCRIPT"_ns, "IMAGE"_ns, - "MEDIA"_ns, "STYLESHEET"_ns, "WASM"_ns}; + "MEDIA"_ns, "STYLESHEET"_ns, "WASM"_ns, "DICTIONARY"_ns}; for (uint32_t i = 0; i < nsICacheEntry::CONTENT_TYPE_LAST; ++i) { if (mIndexStats.Size() > 0) { diff --git a/netwerk/cache2/CacheStorageService.cpp b/netwerk/cache2/CacheStorageService.cpp @@ -744,15 +744,6 @@ static bool RemoveExactEntry(CacheEntryTable* aEntries, nsACString const& aKey, return false; // Already replaced... } - // Remove from DictionaryCache immediately, to ensure the removal is - // synchronous - - if (aEntry->GetEnhanceID().EqualsLiteral("dict:")) { - DictionaryCache::RemoveOriginFor(aEntry->GetURI()); - } else { - DictionaryCache::RemoveDictionaryFor(aEntry->GetURI()); - } - LOG(("RemoveExactEntry [entry=%p removed]", aEntry)); aEntries->Remove(aKey); return true; diff --git a/netwerk/cache2/Dictionary.cpp b/netwerk/cache2/Dictionary.cpp @@ -731,10 +731,16 @@ NS_IMETHODIMP DictionaryOriginReader::OnCacheEntryAvailable( AUTO_PROFILER_FLOW_MARKER("DictionaryOriginReader::VisitMetaData", NETWORK, Flow::FromPointer(this)); - mOrigin->SetCacheEntry(aCacheEntry); - // There's no data in the cache entry, just metadata - nsCOMPtr<nsICacheEntryMetaDataVisitor> metadata(mOrigin); - aCacheEntry->VisitMetaData(metadata); + bool empty = false; + aCacheEntry->GetIsEmpty(&empty); + if (empty) { + // New cache entry, set type + mOrigin->SetCacheEntry(aCacheEntry); + } else { + // There's no data in the cache entry, just metadata + nsCOMPtr<nsICacheEntryMetaDataVisitor> metadata(mOrigin); + aCacheEntry->VisitMetaData(metadata); + } // This list is the only thing keeping us alive RefPtr<DictionaryOriginReader> safety(this); @@ -910,6 +916,8 @@ void DictionaryCache::Clear() { // static void DictionaryCache::RemoveDictionaryFor(const nsACString& aKey) { RefPtr<DictionaryCache> cache = GetInstance(); + DICTIONARY_LOG( + ("Removing dictionary for %s", PromiseFlatCString(aKey).get())); NS_DispatchToMainThread(NewRunnableMethod<const nsCString>( "DictionaryCache::RemoveDictionaryFor", cache, &DictionaryCache::RemoveDictionary, aKey)); @@ -918,7 +926,7 @@ void DictionaryCache::RemoveDictionaryFor(const nsACString& aKey) { // Remove a dictionary if it exists for the key given void DictionaryCache::RemoveDictionary(const nsACString& aKey) { DICTIONARY_LOG( - ("Removing dictionary for %80s", PromiseFlatCString(aKey).get())); + ("Removing dictionary for %s", PromiseFlatCString(aKey).get())); nsCOMPtr<nsIURI> uri; if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aKey))) { @@ -936,16 +944,14 @@ void DictionaryCache::RemoveDictionary(const nsACString& aKey) { void DictionaryCache::RemoveOriginFor(const nsACString& aKey) { RefPtr<DictionaryCache> cache = GetInstance(); DICTIONARY_LOG( - ("Removing dictionary for %80s", PromiseFlatCString(aKey).get())); + ("Removing dictionary origin %s", PromiseFlatCString(aKey).get())); NS_DispatchToMainThread(NewRunnableMethod<const nsCString>( - "DictionaryCache::RemoveDictionaryFor", cache, - &DictionaryCache::RemoveDictionary, aKey)); + "DictionaryCache::RemoveOriginFor", cache, &DictionaryCache::RemoveOrigin, + aKey)); } -// Remove a dictionary if it exists for the key given +// Remove a dictionary if it exists for the key given, if it's empty void DictionaryCache::RemoveOrigin(const nsACString& aKey) { - DICTIONARY_LOG(("Removing origin for %80s", PromiseFlatCString(aKey).get())); - nsCOMPtr<nsIURI> uri; if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aKey))) { return; @@ -953,10 +959,15 @@ void DictionaryCache::RemoveOrigin(const nsACString& aKey) { nsAutoCString prepath; if (NS_SUCCEEDED(GetDictPath(uri, prepath))) { if (auto origin = mDictionaryCache.Lookup(prepath)) { - origin.Data()->AssertEmpty(); + if (MOZ_UNLIKELY(origin.Data()->IsEmpty())) { + DICTIONARY_LOG( + ("Removing origin for %s", PromiseFlatCString(aKey).get())); + mDictionaryCache.Remove(prepath); + } else { + DICTIONARY_LOG( + ("Origin not empty: %s", PromiseFlatCString(aKey).get())); + } } - - mDictionaryCache.Remove(prepath); } } @@ -1128,6 +1139,7 @@ nsresult DictionaryOrigin::Write(DictionaryCacheEntry* aDictEntry) { void DictionaryOrigin::SetCacheEntry(nsICacheEntry* aEntry) { mEntry = aEntry; + mEntry->SetContentType(nsICacheEntry::CONTENT_TYPE_DICTIONARY); if (mDeferredWrites) { for (auto& entry : mEntries) { if (NS_FAILED(Write(entry))) { @@ -1245,12 +1257,13 @@ already_AddRefed<DictionaryCacheEntry> DictionaryOrigin::AddEntry( nsresult DictionaryOrigin::RemoveEntry(const nsACString& aKey) { DICTIONARY_LOG( ("DictionaryOrigin::RemoveEntry for %s", PromiseFlatCString(aKey).get())); + RefPtr<DictionaryCacheEntry> hold; for (const auto& dict : mEntries) { DICTIONARY_LOG( (" Comparing to %s", PromiseFlatCString(dict->GetURI()).get())); if (dict->GetURI().Equals(aKey)) { // Ensure it doesn't disappear on us - RefPtr<DictionaryCacheEntry> hold(dict); + hold = dict; DICTIONARY_LOG(("Removing %p", dict.get())); mEntries.RemoveElement(dict); if (mEntry) { @@ -1259,25 +1272,34 @@ nsresult DictionaryOrigin::RemoveEntry(const nsACString& aKey) { // We don't have the cache entry yet. Defer the removal from // the entry until we do mPendingRemove.AppendElement(hold); + return NS_OK; } - return NS_OK; + break; } } - DICTIONARY_LOG(("DictionaryOrigin::RemoveEntry (pending) for %s", - PromiseFlatCString(aKey).get())); - for (const auto& dict : mPendingEntries) { - DICTIONARY_LOG( - (" Comparing to %s", PromiseFlatCString(dict->GetURI()).get())); - if (dict->GetURI().Equals(aKey)) { - // Ensure it doesn't disappear on us - RefPtr<DictionaryCacheEntry> hold(dict); - DICTIONARY_LOG(("Removing %p", dict.get())); - mPendingEntries.RemoveElement(dict); - hold->RemoveEntry(mEntry); - return NS_OK; + if (!hold) { + DICTIONARY_LOG(("DictionaryOrigin::RemoveEntry (pending) for %s", + PromiseFlatCString(aKey).get())); + for (const auto& dict : mPendingEntries) { + DICTIONARY_LOG( + (" Comparing to %s", PromiseFlatCString(dict->GetURI()).get())); + if (dict->GetURI().Equals(aKey)) { + // Ensure it doesn't disappear on us + RefPtr<DictionaryCacheEntry> hold(dict); + DICTIONARY_LOG(("Removing %p", dict.get())); + mPendingEntries.RemoveElement(dict); + break; + } } } - return NS_ERROR_FAILURE; + // If this origin has no entries, remove it and doom the entry + if (IsEmpty()) { + if (mEntry) { + mEntry->AsyncDoom(nullptr); + } + gDictionaryCache->RemoveOrigin(mOrigin); + } + return hold ? NS_OK : NS_ERROR_FAILURE; } void DictionaryOrigin::FinishAddEntry(DictionaryCacheEntry* aEntry) { @@ -1373,6 +1395,12 @@ nsresult DictionaryOrigin::OnMetaDataElement(const char* asciiKey, DICTIONARY_LOG(("DictionaryOrigin::OnMetaDataElement %s %s", asciiKey ? asciiKey : "", asciiValue)); + // We set the content ID to CONTENT_TYPE_DICTIONARY, ensure that's correct + if (strcmp(asciiKey, "ctid") == 0) { + MOZ_ASSERT(strcmp(asciiValue, "7") == 0); + return NS_OK; + } + // All other keys should be URLs for dictionaries // If we already have an entry for this key (pending or in the list), // don't override it for (auto& entry : mEntries) { diff --git a/netwerk/cache2/Dictionary.h b/netwerk/cache2/Dictionary.h @@ -279,8 +279,9 @@ class DictionaryOrigin : public nsICacheEntryMetaDataVisitor { void FinishAddEntry(DictionaryCacheEntry* aEntry); void DumpEntries(); void Clear(); - void AssertEmpty() const { - MOZ_DIAGNOSTIC_ASSERT(mEntries.IsEmpty() && mPendingEntries.IsEmpty()); + bool IsEmpty() const { + return mEntries.IsEmpty() && mPendingEntries.IsEmpty() && + mPendingRemove.IsEmpty() && mWaitingCacheRead.IsEmpty(); } private: diff --git a/netwerk/cache2/nsICacheEntry.idl b/netwerk/cache2/nsICacheEntry.idl @@ -29,12 +29,13 @@ interface nsICacheEntry : nsISupports const unsigned long CONTENT_TYPE_MEDIA = 4; const unsigned long CONTENT_TYPE_STYLESHEET = 5; const unsigned long CONTENT_TYPE_WASM = 6; + const unsigned long CONTENT_TYPE_DICTIONARY = 7; /** * Content type that is used internally to check whether the value parsed * from disk is within allowed limits. Don't pass CONTENT_TYPE_LAST to * setContentType method. */ - const unsigned long CONTENT_TYPE_LAST = 7; + const unsigned long CONTENT_TYPE_LAST = 8; /** * Placeholder for the initial value of expiration time. @@ -204,6 +205,11 @@ interface nsICacheEntry : nsISupports void setMetaDataElement(in string key, in string value); /** + * Get if the entry is empty (new) + */ + readonly attribute boolean isEmpty; + + /** * Obtain the list of metadata keys this entry keeps. * * NOTE: The callback is invoked under the CacheFile's lock. It means diff --git a/netwerk/protocol/about/nsAboutCacheEntry.cpp b/netwerk/protocol/about/nsAboutCacheEntry.cpp @@ -491,15 +491,20 @@ nsAboutCacheEntry::Channel::OnMetaDataElement(char const* key, ":</th>\n" " <td>"); if (mEnhanceId.EqualsLiteral("dict:")) { - RefPtr<DictionaryCacheEntry> dict = new DictionaryCacheEntry("temp"); - dict->ParseMetadata(value); - nsAppendEscapedHTML( - nsPrintfCString( - "Hash: %s\nPattern: %s\nId: %s\nMatch-Id: ", dict->GetHash().get(), - dict->GetPattern().get(), dict->GetId().get()), - *mBuffer); - dict->AppendMatchDest(*mBuffer); - mBuffer->AppendLiteral("\n"); + // We set the content ID to CONTENT_TYPE_DICTIONARY, ensure that's correct + if (strcmp(key, "ctid") == 0) { + MOZ_ASSERT(strcmp(value, "7") == 0); + } else { + RefPtr<DictionaryCacheEntry> dict = new DictionaryCacheEntry("temp"); + dict->ParseMetadata(value); + nsAppendEscapedHTML( + nsPrintfCString("Hash: %s\nPattern: %s\nId: %s\nMatch-Id: ", + dict->GetHash().get(), dict->GetPattern().get(), + dict->GetId().get()), + *mBuffer); + dict->AppendMatchDest(*mBuffer); + mBuffer->AppendLiteral("\n"); + } } nsAppendEscapedHTML(nsDependentCString(value), *mBuffer); mBuffer->AppendLiteral(