michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "CacheLog.h" michael@0: #include "CacheStorageService.h" michael@0: #include "CacheFileIOManager.h" michael@0: #include "CacheObserver.h" michael@0: #include "CacheIndex.h" michael@0: michael@0: #include "nsICacheStorageVisitor.h" michael@0: #include "nsIObserverService.h" michael@0: #include "CacheStorage.h" michael@0: #include "AppCacheStorage.h" michael@0: #include "CacheEntry.h" michael@0: #include "CacheFileUtils.h" michael@0: michael@0: #include "OldWrappers.h" michael@0: #include "nsCacheService.h" michael@0: #include "nsDeleteDir.h" michael@0: michael@0: #include "nsIFile.h" michael@0: #include "nsIURI.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/VisualEventTracer.h" michael@0: #include "mozilla/Services.h" michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: namespace { michael@0: michael@0: void AppendMemoryStorageID(nsAutoCString &key) michael@0: { michael@0: key.Append('/'); michael@0: key.Append('M'); michael@0: } michael@0: michael@0: } michael@0: michael@0: // Not defining as static or class member of CacheStorageService since michael@0: // it would otherwise need to include CacheEntry.h and that then would michael@0: // need to be exported to make nsNetModule.cpp compilable. michael@0: typedef nsClassHashtable michael@0: GlobalEntryTables; michael@0: michael@0: /** michael@0: * Keeps tables of entries. There is one entries table for each distinct load michael@0: * context type. The distinction is based on following load context info states: michael@0: * which builds a mapping key. michael@0: * michael@0: * Thread-safe to access, protected by the service mutex. michael@0: */ michael@0: static GlobalEntryTables* sGlobalEntryTables; michael@0: michael@0: CacheMemoryConsumer::CacheMemoryConsumer(uint32_t aFlags) michael@0: : mReportedMemoryConsumption(0) michael@0: , mFlags(aFlags) michael@0: { michael@0: } michael@0: michael@0: void michael@0: CacheMemoryConsumer::DoMemoryReport(uint32_t aCurrentSize) michael@0: { michael@0: if (!(mFlags & DONT_REPORT) && CacheStorageService::Self()) { michael@0: CacheStorageService::Self()->OnMemoryConsumptionChange(this, aCurrentSize); michael@0: } michael@0: } michael@0: michael@0: CacheStorageService::MemoryPool::MemoryPool(EType aType) michael@0: : mType(aType) michael@0: , mMemorySize(0) michael@0: { michael@0: } michael@0: michael@0: CacheStorageService::MemoryPool::~MemoryPool() michael@0: { michael@0: if (mMemorySize != 0) { michael@0: NS_ERROR("Network cache reported memory consumption is not at 0, probably leaking?"); michael@0: } michael@0: } michael@0: michael@0: uint32_t const michael@0: CacheStorageService::MemoryPool::Limit() const michael@0: { michael@0: switch (mType) { michael@0: case DISK: michael@0: return CacheObserver::MetadataMemoryLimit(); michael@0: case MEMORY: michael@0: return CacheObserver::MemoryCacheCapacity(); michael@0: } michael@0: michael@0: MOZ_CRASH("Bad pool type"); michael@0: return 0; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(CacheStorageService, michael@0: nsICacheStorageService, michael@0: nsIMemoryReporter, michael@0: nsITimerCallback) michael@0: michael@0: CacheStorageService* CacheStorageService::sSelf = nullptr; michael@0: michael@0: CacheStorageService::CacheStorageService() michael@0: : mLock("CacheStorageService") michael@0: , mShutdown(false) michael@0: , mDiskPool(MemoryPool::DISK) michael@0: , mMemoryPool(MemoryPool::MEMORY) michael@0: { michael@0: CacheFileIOManager::Init(); michael@0: michael@0: MOZ_ASSERT(!sSelf); michael@0: michael@0: sSelf = this; michael@0: sGlobalEntryTables = new GlobalEntryTables(); michael@0: michael@0: RegisterStrongMemoryReporter(this); michael@0: } michael@0: michael@0: CacheStorageService::~CacheStorageService() michael@0: { michael@0: LOG(("CacheStorageService::~CacheStorageService")); michael@0: sSelf = nullptr; michael@0: } michael@0: michael@0: void CacheStorageService::Shutdown() michael@0: { michael@0: if (mShutdown) michael@0: return; michael@0: michael@0: LOG(("CacheStorageService::Shutdown - start")); michael@0: michael@0: mShutdown = true; michael@0: michael@0: nsCOMPtr event = michael@0: NS_NewRunnableMethod(this, &CacheStorageService::ShutdownBackground); michael@0: Dispatch(event); michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: sGlobalEntryTables->Clear(); michael@0: delete sGlobalEntryTables; michael@0: sGlobalEntryTables = nullptr; michael@0: michael@0: LOG(("CacheStorageService::Shutdown - done")); michael@0: } michael@0: michael@0: void CacheStorageService::ShutdownBackground() michael@0: { michael@0: MOZ_ASSERT(IsOnManagementThread()); michael@0: michael@0: Pool(false).mFrecencyArray.Clear(); michael@0: Pool(false).mExpirationArray.Clear(); michael@0: Pool(true).mFrecencyArray.Clear(); michael@0: Pool(true).mExpirationArray.Clear(); michael@0: } michael@0: michael@0: // Internal management methods michael@0: michael@0: namespace { // anon michael@0: michael@0: // WalkRunnable michael@0: // Responsible to visit the storage and walk all entries on it asynchronously michael@0: michael@0: class WalkRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: WalkRunnable(nsCSubstring const & aContextKey, bool aVisitEntries, michael@0: bool aUsingDisk, michael@0: nsICacheStorageVisitor* aVisitor) michael@0: : mContextKey(aContextKey) michael@0: , mCallback(aVisitor) michael@0: , mSize(0) michael@0: , mNotifyStorage(true) michael@0: , mVisitEntries(aVisitEntries) michael@0: , mUsingDisk(aUsingDisk) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: } michael@0: michael@0: private: michael@0: NS_IMETHODIMP Run() michael@0: { michael@0: if (CacheStorageService::IsOnManagementThread()) { michael@0: LOG(("WalkRunnable::Run - collecting [this=%p, disk=%d]", this, (bool)mUsingDisk)); michael@0: // First, walk, count and grab all entries from the storage michael@0: // TODO michael@0: // - walk files on disk, when the storage is not private michael@0: // - should create representative entries only for the time michael@0: // of need michael@0: michael@0: mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock()); michael@0: michael@0: if (!CacheStorageService::IsRunning()) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: CacheEntryTable* entries; michael@0: if (sGlobalEntryTables->Get(mContextKey, &entries)) michael@0: entries->EnumerateRead(&WalkRunnable::WalkStorage, this); michael@0: michael@0: // Next, we dispatch to the main thread michael@0: } michael@0: else if (NS_IsMainThread()) { michael@0: LOG(("WalkRunnable::Run - notifying [this=%p, disk=%d]", this, (bool)mUsingDisk)); michael@0: if (mNotifyStorage) { michael@0: LOG((" storage")); michael@0: // Second, notify overall storage info michael@0: mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize); michael@0: if (!mVisitEntries) michael@0: return NS_OK; // done michael@0: michael@0: mNotifyStorage = false; michael@0: } michael@0: else { michael@0: LOG((" entry [left=%d]", mEntryArray.Length())); michael@0: // Third, notify each entry until depleted. michael@0: if (!mEntryArray.Length()) { michael@0: mCallback->OnCacheEntryVisitCompleted(); michael@0: return NS_OK; // done michael@0: } michael@0: michael@0: mCallback->OnCacheEntryInfo(mEntryArray[0]); michael@0: mEntryArray.RemoveElementAt(0); michael@0: michael@0: // Dispatch to the main thread again michael@0: } michael@0: } michael@0: else { michael@0: MOZ_ASSERT(false); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_DispatchToMainThread(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: virtual ~WalkRunnable() michael@0: { michael@0: if (mCallback) michael@0: ProxyReleaseMainThread(mCallback); michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: WalkStorage(const nsACString& aKey, michael@0: CacheEntry* aEntry, michael@0: void* aClosure) michael@0: { michael@0: WalkRunnable* walker = static_cast(aClosure); michael@0: michael@0: if (!walker->mUsingDisk && aEntry->IsUsingDiskLocked()) michael@0: return PL_DHASH_NEXT; michael@0: michael@0: walker->mSize += aEntry->GetMetadataMemoryConsumption(); michael@0: michael@0: int64_t size; michael@0: if (NS_SUCCEEDED(aEntry->GetDataSize(&size))) michael@0: walker->mSize += size; michael@0: michael@0: walker->mEntryArray.AppendElement(aEntry); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsCString mContextKey; michael@0: nsCOMPtr mCallback; michael@0: nsTArray > mEntryArray; michael@0: michael@0: uint64_t mSize; michael@0: michael@0: bool mNotifyStorage : 1; michael@0: bool mVisitEntries : 1; michael@0: bool mUsingDisk : 1; michael@0: }; michael@0: michael@0: PLDHashOperator CollectPrivateContexts(const nsACString& aKey, michael@0: CacheEntryTable* aTable, michael@0: void* aClosure) michael@0: { michael@0: nsCOMPtr info = CacheFileUtils::ParseKey(aKey); michael@0: if (info && info->IsPrivate()) { michael@0: nsTArray* keys = static_cast*>(aClosure); michael@0: keys->AppendElement(aKey); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator CollectContexts(const nsACString& aKey, michael@0: CacheEntryTable* aTable, michael@0: void* aClosure) michael@0: { michael@0: nsTArray* keys = static_cast*>(aClosure); michael@0: keys->AppendElement(aKey); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: } // anon michael@0: michael@0: void CacheStorageService::DropPrivateBrowsingEntries() michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: if (mShutdown) michael@0: return; michael@0: michael@0: nsTArray keys; michael@0: sGlobalEntryTables->EnumerateRead(&CollectPrivateContexts, &keys); michael@0: michael@0: for (uint32_t i = 0; i < keys.Length(); ++i) michael@0: DoomStorageEntries(keys[i], nullptr, true, nullptr); michael@0: } michael@0: michael@0: // static michael@0: void CacheStorageService::WipeCacheDirectory(uint32_t aVersion) michael@0: { michael@0: nsCOMPtr cacheDir; michael@0: switch (aVersion) { michael@0: case 0: michael@0: nsCacheService::GetDiskCacheDirectory(getter_AddRefs(cacheDir)); michael@0: break; michael@0: case 1: michael@0: CacheFileIOManager::GetCacheDirectory(getter_AddRefs(cacheDir)); michael@0: break; michael@0: } michael@0: michael@0: if (!cacheDir) michael@0: return; michael@0: michael@0: nsDeleteDir::DeleteDir(cacheDir, true, 30000); michael@0: } michael@0: michael@0: // Helper methods michael@0: michael@0: // static michael@0: bool CacheStorageService::IsOnManagementThread() michael@0: { michael@0: nsRefPtr service = Self(); michael@0: if (!service) michael@0: return false; michael@0: michael@0: nsCOMPtr target = service->Thread(); michael@0: if (!target) michael@0: return false; michael@0: michael@0: bool currentThread; michael@0: nsresult rv = target->IsOnCurrentThread(¤tThread); michael@0: return NS_SUCCEEDED(rv) && currentThread; michael@0: } michael@0: michael@0: already_AddRefed CacheStorageService::Thread() const michael@0: { michael@0: return CacheFileIOManager::IOTarget(); michael@0: } michael@0: michael@0: nsresult CacheStorageService::Dispatch(nsIRunnable* aEvent) michael@0: { michael@0: nsRefPtr cacheIOThread = CacheFileIOManager::IOThread(); michael@0: if (!cacheIOThread) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return cacheIOThread->Dispatch(aEvent, CacheIOThread::MANAGEMENT); michael@0: } michael@0: michael@0: // nsICacheStorageService michael@0: michael@0: NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(nsILoadContextInfo *aLoadContextInfo, michael@0: nsICacheStorage * *_retval) michael@0: { michael@0: NS_ENSURE_ARG(aLoadContextInfo); michael@0: NS_ENSURE_ARG(_retval); michael@0: michael@0: nsCOMPtr storage; michael@0: if (CacheObserver::UseNewCache()) { michael@0: storage = new CacheStorage(aLoadContextInfo, false, false); michael@0: } michael@0: else { michael@0: storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr); michael@0: } michael@0: michael@0: storage.forget(_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheStorageService::DiskCacheStorage(nsILoadContextInfo *aLoadContextInfo, michael@0: bool aLookupAppCache, michael@0: nsICacheStorage * *_retval) michael@0: { michael@0: NS_ENSURE_ARG(aLoadContextInfo); michael@0: NS_ENSURE_ARG(_retval); michael@0: michael@0: // TODO save some heap granularity - cache commonly used storages. michael@0: michael@0: // When disk cache is disabled, still provide a storage, but just keep stuff michael@0: // in memory. michael@0: bool useDisk = CacheObserver::UseDiskCache(); michael@0: michael@0: nsCOMPtr storage; michael@0: if (CacheObserver::UseNewCache()) { michael@0: storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache); michael@0: } michael@0: else { michael@0: storage = new _OldStorage(aLoadContextInfo, useDisk, aLookupAppCache, false, nullptr); michael@0: } michael@0: michael@0: storage.forget(_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheStorageService::AppCacheStorage(nsILoadContextInfo *aLoadContextInfo, michael@0: nsIApplicationCache *aApplicationCache, michael@0: nsICacheStorage * *_retval) michael@0: { michael@0: NS_ENSURE_ARG(aLoadContextInfo); michael@0: NS_ENSURE_ARG(_retval); michael@0: michael@0: nsCOMPtr storage; michael@0: if (CacheObserver::UseNewCache()) { michael@0: // Using classification since cl believes we want to instantiate this method michael@0: // having the same name as the desired class... michael@0: storage = new mozilla::net::AppCacheStorage(aLoadContextInfo, aApplicationCache); michael@0: } michael@0: else { michael@0: storage = new _OldStorage(aLoadContextInfo, true, false, true, aApplicationCache); michael@0: } michael@0: michael@0: storage.forget(_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheStorageService::Clear() michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (CacheObserver::UseNewCache()) { michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsTArray keys; michael@0: sGlobalEntryTables->EnumerateRead(&CollectContexts, &keys); michael@0: michael@0: for (uint32_t i = 0; i < keys.Length(); ++i) michael@0: DoomStorageEntries(keys[i], nullptr, true, nullptr); michael@0: } michael@0: michael@0: rv = CacheFileIOManager::EvictAll(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: nsCOMPtr serv = michael@0: do_GetService(NS_CACHESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = serv->EvictEntries(nsICache::STORE_ANYWHERE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat) michael@0: { michael@0: uint32_t what; michael@0: michael@0: switch (aWhat) { michael@0: case PURGE_DISK_DATA_ONLY: michael@0: what = CacheEntry::PURGE_DATA_ONLY_DISK_BACKED; michael@0: break; michael@0: michael@0: case PURGE_DISK_ALL: michael@0: what = CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED; michael@0: break; michael@0: michael@0: case PURGE_EVERYTHING: michael@0: what = CacheEntry::PURGE_WHOLE; michael@0: break; michael@0: michael@0: default: michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: nsCOMPtr event = michael@0: new PurgeFromMemoryRunnable(this, what); michael@0: michael@0: return Dispatch(event); michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheStorageService::AsyncGetDiskConsumption( michael@0: nsICacheStorageConsumptionObserver* aObserver) michael@0: { michael@0: NS_ENSURE_ARG(aObserver); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (CacheObserver::UseNewCache()) { michael@0: rv = CacheIndex::AsyncGetDiskConsumption(aObserver); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: rv = _OldGetDiskConsumption::Get(aObserver); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheStorageService::GetIoTarget(nsIEventTarget** aEventTarget) michael@0: { michael@0: NS_ENSURE_ARG(aEventTarget); michael@0: michael@0: if (CacheObserver::UseNewCache()) { michael@0: nsCOMPtr ioTarget = CacheFileIOManager::IOTarget(); michael@0: ioTarget.forget(aEventTarget); michael@0: } michael@0: else { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr serv = michael@0: do_GetService(NS_CACHESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = serv->GetCacheIOTarget(aEventTarget); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Methods used by CacheEntry for management of in-memory structures. michael@0: michael@0: namespace { // anon michael@0: michael@0: class FrecencyComparator michael@0: { michael@0: public: michael@0: bool Equals(CacheEntry* a, CacheEntry* b) const { michael@0: return a->GetFrecency() == b->GetFrecency(); michael@0: } michael@0: bool LessThan(CacheEntry* a, CacheEntry* b) const { michael@0: return a->GetFrecency() < b->GetFrecency(); michael@0: } michael@0: }; michael@0: michael@0: class ExpirationComparator michael@0: { michael@0: public: michael@0: bool Equals(CacheEntry* a, CacheEntry* b) const { michael@0: return a->GetExpirationTime() == b->GetExpirationTime(); michael@0: } michael@0: bool LessThan(CacheEntry* a, CacheEntry* b) const { michael@0: return a->GetExpirationTime() < b->GetExpirationTime(); michael@0: } michael@0: }; michael@0: michael@0: } // anon michael@0: michael@0: void michael@0: CacheStorageService::RegisterEntry(CacheEntry* aEntry) michael@0: { michael@0: MOZ_ASSERT(IsOnManagementThread()); michael@0: michael@0: if (mShutdown || !aEntry->CanRegister()) michael@0: return; michael@0: michael@0: LOG(("CacheStorageService::RegisterEntry [entry=%p]", aEntry)); michael@0: michael@0: MemoryPool& pool = Pool(aEntry->IsUsingDisk()); michael@0: pool.mFrecencyArray.InsertElementSorted(aEntry, FrecencyComparator()); michael@0: pool.mExpirationArray.InsertElementSorted(aEntry, ExpirationComparator()); michael@0: michael@0: aEntry->SetRegistered(true); michael@0: } michael@0: michael@0: void michael@0: CacheStorageService::UnregisterEntry(CacheEntry* aEntry) michael@0: { michael@0: MOZ_ASSERT(IsOnManagementThread()); michael@0: michael@0: if (!aEntry->IsRegistered()) michael@0: return; michael@0: michael@0: LOG(("CacheStorageService::UnregisterEntry [entry=%p]", aEntry)); michael@0: michael@0: MemoryPool& pool = Pool(aEntry->IsUsingDisk()); michael@0: mozilla::DebugOnly removedFrecency = pool.mFrecencyArray.RemoveElement(aEntry); michael@0: mozilla::DebugOnly removedExpiration = pool.mExpirationArray.RemoveElement(aEntry); michael@0: michael@0: MOZ_ASSERT(mShutdown || (removedFrecency && removedExpiration)); michael@0: michael@0: // Note: aEntry->CanRegister() since now returns false michael@0: aEntry->SetRegistered(false); michael@0: } michael@0: michael@0: static bool michael@0: AddExactEntry(CacheEntryTable* aEntries, michael@0: nsCString const& aKey, michael@0: CacheEntry* aEntry, michael@0: bool aOverwrite) michael@0: { michael@0: nsRefPtr existingEntry; michael@0: if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) { michael@0: bool equals = existingEntry == aEntry; michael@0: LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals)); michael@0: return equals; // Already there... michael@0: } michael@0: michael@0: LOG(("AddExactEntry [entry=%p put]", aEntry)); michael@0: aEntries->Put(aKey, aEntry); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: RemoveExactEntry(CacheEntryTable* aEntries, michael@0: nsCString const& aKey, michael@0: CacheEntry* aEntry, michael@0: bool aOverwrite) michael@0: { michael@0: nsRefPtr existingEntry; michael@0: if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) { michael@0: LOG(("RemoveExactEntry [entry=%p already gone]", aEntry)); michael@0: return false; // Already removed... michael@0: } michael@0: michael@0: if (!aOverwrite && existingEntry != aEntry) { michael@0: LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry)); michael@0: return false; // Already replaced... michael@0: } michael@0: michael@0: LOG(("RemoveExactEntry [entry=%p removed]", aEntry)); michael@0: aEntries->Remove(aKey); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CacheStorageService::RemoveEntry(CacheEntry* aEntry, bool aOnlyUnreferenced) michael@0: { michael@0: LOG(("CacheStorageService::RemoveEntry [entry=%p]", aEntry)); michael@0: michael@0: nsAutoCString entryKey; michael@0: nsresult rv = aEntry->HashingKey(entryKey); michael@0: if (NS_FAILED(rv)) { michael@0: NS_ERROR("aEntry->HashingKey() failed?"); michael@0: return false; michael@0: } michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: if (mShutdown) { michael@0: LOG((" after shutdown")); michael@0: return false; michael@0: } michael@0: michael@0: if (aOnlyUnreferenced && aEntry->IsReferenced()) { michael@0: LOG((" still referenced, not removing")); michael@0: return false; michael@0: } michael@0: michael@0: CacheEntryTable* entries; michael@0: if (sGlobalEntryTables->Get(aEntry->GetStorageID(), &entries)) michael@0: RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */); michael@0: michael@0: nsAutoCString memoryStorageID(aEntry->GetStorageID()); michael@0: AppendMemoryStorageID(memoryStorageID); michael@0: michael@0: if (sGlobalEntryTables->Get(memoryStorageID, &entries)) michael@0: RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: CacheStorageService::RecordMemoryOnlyEntry(CacheEntry* aEntry, michael@0: bool aOnlyInMemory, michael@0: bool aOverwrite) michael@0: { michael@0: LOG(("CacheStorageService::RecordMemoryOnlyEntry [entry=%p, memory=%d, overwrite=%d]", michael@0: aEntry, aOnlyInMemory, aOverwrite)); michael@0: // This method is responsible to put this entry to a special record hashtable michael@0: // that contains only entries that are stored in memory. michael@0: // Keep in mind that every entry, regardless of whether is in-memory-only or not michael@0: // is always recorded in the storage master hash table, the one identified by michael@0: // CacheEntry.StorageID(). michael@0: michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: if (mShutdown) { michael@0: LOG((" after shutdown")); michael@0: return; michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: nsAutoCString entryKey; michael@0: rv = aEntry->HashingKey(entryKey); michael@0: if (NS_FAILED(rv)) { michael@0: NS_ERROR("aEntry->HashingKey() failed?"); michael@0: return; michael@0: } michael@0: michael@0: CacheEntryTable* entries = nullptr; michael@0: nsAutoCString memoryStorageID(aEntry->GetStorageID()); michael@0: AppendMemoryStorageID(memoryStorageID); michael@0: michael@0: if (!sGlobalEntryTables->Get(memoryStorageID, &entries)) { michael@0: if (!aOnlyInMemory) { michael@0: LOG((" not recorded as memory only")); michael@0: return; michael@0: } michael@0: michael@0: entries = new CacheEntryTable(CacheEntryTable::MEMORY_ONLY); michael@0: sGlobalEntryTables->Put(memoryStorageID, entries); michael@0: LOG((" new memory-only storage table for %s", memoryStorageID.get())); michael@0: } michael@0: michael@0: if (aOnlyInMemory) { michael@0: AddExactEntry(entries, entryKey, aEntry, aOverwrite); michael@0: } michael@0: else { michael@0: RemoveExactEntry(entries, entryKey, aEntry, aOverwrite); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheStorageService::OnMemoryConsumptionChange(CacheMemoryConsumer* aConsumer, michael@0: uint32_t aCurrentMemoryConsumption) michael@0: { michael@0: LOG(("CacheStorageService::OnMemoryConsumptionChange [consumer=%p, size=%u]", michael@0: aConsumer, aCurrentMemoryConsumption)); michael@0: michael@0: uint32_t savedMemorySize = aConsumer->mReportedMemoryConsumption; michael@0: if (savedMemorySize == aCurrentMemoryConsumption) michael@0: return; michael@0: michael@0: // Exchange saved size with current one. michael@0: aConsumer->mReportedMemoryConsumption = aCurrentMemoryConsumption; michael@0: michael@0: bool usingDisk = !(aConsumer->mFlags & CacheMemoryConsumer::MEMORY_ONLY); michael@0: bool overLimit = Pool(usingDisk).OnMemoryConsumptionChange( michael@0: savedMemorySize, aCurrentMemoryConsumption); michael@0: michael@0: if (!overLimit) michael@0: return; michael@0: michael@0: // It's likely the timer has already been set when we get here, michael@0: // check outside the lock to save resources. michael@0: if (mPurgeTimer) michael@0: return; michael@0: michael@0: // We don't know if this is called under the service lock or not, michael@0: // hence rather dispatch. michael@0: nsRefPtr cacheIOTarget = Thread(); michael@0: if (!cacheIOTarget) michael@0: return; michael@0: michael@0: // Dispatch as a priority task, we want to set the purge timer michael@0: // ASAP to prevent vain redispatch of this event. michael@0: nsCOMPtr event = michael@0: NS_NewRunnableMethod(this, &CacheStorageService::SchedulePurgeOverMemoryLimit); michael@0: cacheIOTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); michael@0: } michael@0: michael@0: bool michael@0: CacheStorageService::MemoryPool::OnMemoryConsumptionChange(uint32_t aSavedMemorySize, michael@0: uint32_t aCurrentMemoryConsumption) michael@0: { michael@0: mMemorySize -= aSavedMemorySize; michael@0: mMemorySize += aCurrentMemoryConsumption; michael@0: michael@0: LOG((" mMemorySize=%u (+%u,-%u)", uint32_t(mMemorySize), aCurrentMemoryConsumption, aSavedMemorySize)); michael@0: michael@0: // Bypass purging when memory has not grew up significantly michael@0: if (aCurrentMemoryConsumption <= aSavedMemorySize) michael@0: return false; michael@0: michael@0: return mMemorySize > Limit(); michael@0: } michael@0: michael@0: void michael@0: CacheStorageService::SchedulePurgeOverMemoryLimit() michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: if (mPurgeTimer) michael@0: return; michael@0: michael@0: mPurgeTimer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: if (mPurgeTimer) michael@0: mPurgeTimer->InitWithCallback(this, 1000, nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CacheStorageService::Notify(nsITimer* aTimer) michael@0: { michael@0: if (aTimer == mPurgeTimer) { michael@0: mPurgeTimer = nullptr; michael@0: michael@0: nsCOMPtr event = michael@0: NS_NewRunnableMethod(this, &CacheStorageService::PurgeOverMemoryLimit); michael@0: Dispatch(event); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CacheStorageService::PurgeOverMemoryLimit() michael@0: { michael@0: MOZ_ASSERT(IsOnManagementThread()); michael@0: michael@0: LOG(("CacheStorageService::PurgeOverMemoryLimit")); michael@0: michael@0: Pool(true).PurgeOverMemoryLimit(); michael@0: Pool(false).PurgeOverMemoryLimit(); michael@0: } michael@0: michael@0: void michael@0: CacheStorageService::MemoryPool::PurgeOverMemoryLimit() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: TimeStamp start(TimeStamp::Now()); michael@0: #endif michael@0: michael@0: uint32_t const memoryLimit = Limit(); michael@0: if (mMemorySize > memoryLimit) { michael@0: LOG((" memory data consumption over the limit, abandon expired entries")); michael@0: PurgeExpired(); michael@0: } michael@0: michael@0: bool frecencyNeedsSort = true; michael@0: michael@0: // No longer makes sense since: michael@0: // Memory entries are never purged partially, only as a whole when the memory michael@0: // cache limit is overreached. michael@0: // Disk entries throw the data away ASAP so that only metadata are kept. michael@0: // TODO when this concept of two separate pools is found working, the code should michael@0: // clean up. michael@0: #if 0 michael@0: if (mMemorySize > memoryLimit) { michael@0: LOG((" memory data consumption over the limit, abandon disk backed data")); michael@0: PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_DATA_ONLY_DISK_BACKED); michael@0: } michael@0: michael@0: if (mMemorySize > memoryLimit) { michael@0: LOG((" metadata consumtion over the limit, abandon disk backed entries")); michael@0: PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED); michael@0: } michael@0: #endif michael@0: michael@0: if (mMemorySize > memoryLimit) { michael@0: LOG((" memory data consumption over the limit, abandon any entry")); michael@0: PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE); michael@0: } michael@0: michael@0: LOG((" purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds())); michael@0: } michael@0: michael@0: void michael@0: CacheStorageService::MemoryPool::PurgeExpired() michael@0: { michael@0: MOZ_ASSERT(IsOnManagementThread()); michael@0: michael@0: mExpirationArray.Sort(ExpirationComparator()); michael@0: uint32_t now = NowInSeconds(); michael@0: michael@0: uint32_t const memoryLimit = Limit(); michael@0: michael@0: for (uint32_t i = 0; mMemorySize > memoryLimit && i < mExpirationArray.Length();) { michael@0: if (CacheIOThread::YieldAndRerun()) michael@0: return; michael@0: michael@0: nsRefPtr entry = mExpirationArray[i]; michael@0: michael@0: uint32_t expirationTime = entry->GetExpirationTime(); michael@0: if (expirationTime > 0 && expirationTime <= now) { michael@0: LOG((" dooming expired entry=%p, exptime=%u (now=%u)", michael@0: entry.get(), entry->GetExpirationTime(), now)); michael@0: michael@0: entry->PurgeAndDoom(); michael@0: continue; michael@0: } michael@0: michael@0: // not purged, move to the next one michael@0: ++i; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheStorageService::MemoryPool::PurgeByFrecency(bool &aFrecencyNeedsSort, uint32_t aWhat) michael@0: { michael@0: MOZ_ASSERT(IsOnManagementThread()); michael@0: michael@0: if (aFrecencyNeedsSort) { michael@0: mFrecencyArray.Sort(FrecencyComparator()); michael@0: aFrecencyNeedsSort = false; michael@0: } michael@0: michael@0: uint32_t const memoryLimit = Limit(); michael@0: michael@0: for (uint32_t i = 0; mMemorySize > memoryLimit && i < mFrecencyArray.Length();) { michael@0: if (CacheIOThread::YieldAndRerun()) michael@0: return; michael@0: michael@0: nsRefPtr entry = mFrecencyArray[i]; michael@0: michael@0: if (entry->Purge(aWhat)) { michael@0: LOG((" abandoned (%d), entry=%p, frecency=%1.10f", michael@0: aWhat, entry.get(), entry->GetFrecency())); michael@0: continue; michael@0: } michael@0: michael@0: // not purged, move to the next one michael@0: ++i; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheStorageService::MemoryPool::PurgeAll(uint32_t aWhat) michael@0: { michael@0: LOG(("CacheStorageService::MemoryPool::PurgeAll aWhat=%d", aWhat)); michael@0: MOZ_ASSERT(IsOnManagementThread()); michael@0: michael@0: for (uint32_t i = 0; i < mFrecencyArray.Length();) { michael@0: if (CacheIOThread::YieldAndRerun()) michael@0: return; michael@0: michael@0: nsRefPtr entry = mFrecencyArray[i]; michael@0: michael@0: if (entry->Purge(aWhat)) { michael@0: LOG((" abandoned entry=%p", entry.get())); michael@0: continue; michael@0: } michael@0: michael@0: // not purged, move to the next one michael@0: ++i; michael@0: } michael@0: } michael@0: michael@0: // Methods exposed to and used by CacheStorage. michael@0: michael@0: nsresult michael@0: CacheStorageService::AddStorageEntry(CacheStorage const* aStorage, michael@0: nsIURI* aURI, michael@0: const nsACString & aIdExtension, michael@0: bool aCreateIfNotExist, michael@0: bool aReplace, michael@0: CacheEntryHandle** aResult) michael@0: { michael@0: NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: NS_ENSURE_ARG(aStorage); michael@0: michael@0: nsAutoCString contextKey; michael@0: CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey); michael@0: michael@0: return AddStorageEntry(contextKey, aURI, aIdExtension, michael@0: aStorage->WriteToDisk(), aCreateIfNotExist, aReplace, michael@0: aResult); michael@0: } michael@0: michael@0: nsresult michael@0: CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey, michael@0: nsIURI* aURI, michael@0: const nsACString & aIdExtension, michael@0: bool aWriteToDisk, michael@0: bool aCreateIfNotExist, michael@0: bool aReplace, michael@0: CacheEntryHandle** aResult) michael@0: { michael@0: NS_ENSURE_ARG(aURI); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsAutoCString entryKey; michael@0: rv = CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: LOG(("CacheStorageService::AddStorageEntry [entryKey=%s, contextKey=%s]", michael@0: entryKey.get(), aContextKey.BeginReading())); michael@0: michael@0: nsRefPtr entry; michael@0: nsRefPtr handle; michael@0: michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // Ensure storage table michael@0: CacheEntryTable* entries; michael@0: if (!sGlobalEntryTables->Get(aContextKey, &entries)) { michael@0: entries = new CacheEntryTable(CacheEntryTable::ALL_ENTRIES); michael@0: sGlobalEntryTables->Put(aContextKey, entries); michael@0: LOG((" new storage entries table for context %s", aContextKey.BeginReading())); michael@0: } michael@0: michael@0: bool entryExists = entries->Get(entryKey, getter_AddRefs(entry)); michael@0: michael@0: // check whether the file is already doomed michael@0: if (entryExists && entry->IsFileDoomed() && !aReplace) { michael@0: LOG((" file already doomed, replacing the entry")); michael@0: aReplace = true; michael@0: } michael@0: michael@0: // If truncate is demanded, delete and doom the current entry michael@0: if (entryExists && aReplace) { michael@0: entries->Remove(entryKey); michael@0: michael@0: LOG((" dooming entry %p for %s because of OPEN_TRUNCATE", entry.get(), entryKey.get())); michael@0: // On purpose called under the lock to prevent races of doom and open on I/O thread michael@0: // No need to remove from both memory-only and all-entries tables. The new entry michael@0: // will overwrite the shadow entry in its ctor. michael@0: entry->DoomAlreadyRemoved(); michael@0: michael@0: entry = nullptr; michael@0: entryExists = false; michael@0: } michael@0: michael@0: if (entryExists && entry->SetUsingDisk(aWriteToDisk)) { michael@0: RecordMemoryOnlyEntry(entry, !aWriteToDisk, true /* overwrite */); michael@0: } michael@0: michael@0: // Ensure entry for the particular URL, if not read/only michael@0: if (!entryExists && (aCreateIfNotExist || aReplace)) { michael@0: // Entry is not in the hashtable or has just been truncated... michael@0: entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk); michael@0: entries->Put(entryKey, entry); michael@0: LOG((" new entry %p for %s", entry.get(), entryKey.get())); michael@0: } michael@0: michael@0: if (entry) { michael@0: // Here, if this entry was not for a long time referenced by any consumer, michael@0: // gets again first 'handles count' reference. michael@0: handle = entry->NewHandle(); michael@0: } michael@0: } michael@0: michael@0: handle.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: class CacheEntryDoomByKeyCallback : public CacheFileIOListener michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback* aCallback) michael@0: : mCallback(aCallback) { } michael@0: virtual ~CacheEntryDoomByKeyCallback(); michael@0: michael@0: private: michael@0: NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) { return NS_OK; } michael@0: NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, nsresult aResult) { return NS_OK; } michael@0: NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) { return NS_OK; } michael@0: NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult); michael@0: NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) { return NS_OK; } michael@0: NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) { return NS_OK; } michael@0: michael@0: nsCOMPtr mCallback; michael@0: }; michael@0: michael@0: CacheEntryDoomByKeyCallback::~CacheEntryDoomByKeyCallback() michael@0: { michael@0: if (mCallback) michael@0: ProxyReleaseMainThread(mCallback); michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntryDoomByKeyCallback::OnFileDoomed(CacheFileHandle *aHandle, michael@0: nsresult aResult) michael@0: { michael@0: if (!mCallback) michael@0: return NS_OK; michael@0: michael@0: mCallback->OnCacheEntryDoomed(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(CacheEntryDoomByKeyCallback, CacheFileIOListener); michael@0: michael@0: } // anon michael@0: michael@0: nsresult michael@0: CacheStorageService::DoomStorageEntry(CacheStorage const* aStorage, michael@0: nsIURI *aURI, michael@0: const nsACString & aIdExtension, michael@0: nsICacheEntryDoomCallback* aCallback) michael@0: { michael@0: LOG(("CacheStorageService::DoomStorageEntry")); michael@0: michael@0: NS_ENSURE_ARG(aStorage); michael@0: NS_ENSURE_ARG(aURI); michael@0: michael@0: nsAutoCString contextKey; michael@0: CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey); michael@0: michael@0: nsAutoCString entryKey; michael@0: nsresult rv = CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsRefPtr entry; michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: CacheEntryTable* entries; michael@0: if (sGlobalEntryTables->Get(contextKey, &entries)) { michael@0: if (entries->Get(entryKey, getter_AddRefs(entry))) { michael@0: if (aStorage->WriteToDisk() || !entry->IsUsingDiskLocked()) { michael@0: // When evicting from disk storage, purge michael@0: // When evicting from memory storage and the entry is memory-only, purge michael@0: LOG((" purging entry %p for %s [storage use disk=%d, entry use disk=%d]", michael@0: entry.get(), entryKey.get(), aStorage->WriteToDisk(), entry->IsUsingDiskLocked())); michael@0: entries->Remove(entryKey); michael@0: } michael@0: else { michael@0: // Otherwise, leave it michael@0: LOG((" leaving entry %p for %s [storage use disk=%d, entry use disk=%d]", michael@0: entry.get(), entryKey.get(), aStorage->WriteToDisk(), entry->IsUsingDiskLocked())); michael@0: entry = nullptr; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (entry) { michael@0: LOG((" dooming entry %p for %s", entry.get(), entryKey.get())); michael@0: return entry->AsyncDoom(aCallback); michael@0: } michael@0: michael@0: LOG((" no entry loaded for %s", entryKey.get())); michael@0: michael@0: if (aStorage->WriteToDisk()) { michael@0: nsAutoCString contextKey; michael@0: CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey); michael@0: michael@0: rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, entryKey); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: LOG((" dooming file only for %s", entryKey.get())); michael@0: michael@0: nsRefPtr callback( michael@0: new CacheEntryDoomByKeyCallback(aCallback)); michael@0: rv = CacheFileIOManager::DoomFileByKey(entryKey, callback); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aCallback) michael@0: aCallback->OnCacheEntryDoomed(NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheStorageService::DoomStorageEntries(CacheStorage const* aStorage, michael@0: nsICacheEntryDoomCallback* aCallback) michael@0: { michael@0: LOG(("CacheStorageService::DoomStorageEntries")); michael@0: michael@0: NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); michael@0: NS_ENSURE_ARG(aStorage); michael@0: michael@0: nsAutoCString contextKey; michael@0: CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey); michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: return DoomStorageEntries(contextKey, aStorage->LoadInfo(), michael@0: aStorage->WriteToDisk(), aCallback); michael@0: } michael@0: michael@0: nsresult michael@0: CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey, michael@0: nsILoadContextInfo* aContext, michael@0: bool aDiskStorage, michael@0: nsICacheEntryDoomCallback* aCallback) michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsAutoCString memoryStorageID(aContextKey); michael@0: AppendMemoryStorageID(memoryStorageID); michael@0: michael@0: if (aDiskStorage) { michael@0: LOG((" dooming disk+memory storage of %s", aContextKey.BeginReading())); michael@0: michael@0: // Just remove all entries, CacheFileIOManager will take care of the files. michael@0: sGlobalEntryTables->Remove(aContextKey); michael@0: sGlobalEntryTables->Remove(memoryStorageID); michael@0: michael@0: if (aContext && !aContext->IsPrivate()) { michael@0: LOG((" dooming disk entries")); michael@0: CacheFileIOManager::EvictByContext(aContext); michael@0: } michael@0: } else { michael@0: LOG((" dooming memory-only storage of %s", aContextKey.BeginReading())); michael@0: michael@0: class MemoryEntriesRemoval { michael@0: public: michael@0: static PLDHashOperator EvictEntry(const nsACString& aKey, michael@0: CacheEntry* aEntry, michael@0: void* aClosure) michael@0: { michael@0: CacheEntryTable* entries = static_cast(aClosure); michael@0: nsCString key(aKey); michael@0: RemoveExactEntry(entries, key, aEntry, false); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: }; michael@0: michael@0: // Remove the memory entries table from the global tables. michael@0: // Since we store memory entries also in the disk entries table michael@0: // we need to remove the memory entries from the disk table one michael@0: // by one manually. michael@0: nsAutoPtr memoryEntries; michael@0: sGlobalEntryTables->RemoveAndForget(memoryStorageID, memoryEntries); michael@0: michael@0: CacheEntryTable* entries; michael@0: sGlobalEntryTables->Get(aContextKey, &entries); michael@0: if (memoryEntries && entries) michael@0: memoryEntries->EnumerateRead(&MemoryEntriesRemoval::EvictEntry, entries); michael@0: } michael@0: michael@0: // An artificial callback. This is a candidate for removal tho. In the new michael@0: // cache any 'doom' or 'evict' function ensures that the entry or entries michael@0: // being doomed is/are not accessible after the function returns. So there is michael@0: // probably no need for a callback - has no meaning. But for compatibility michael@0: // with the old cache that is still in the tree we keep the API similar to be michael@0: // able to make tests as well as other consumers work for now. michael@0: class Callback : public nsRunnable michael@0: { michael@0: public: michael@0: Callback(nsICacheEntryDoomCallback* aCallback) : mCallback(aCallback) { } michael@0: NS_IMETHODIMP Run() michael@0: { michael@0: mCallback->OnCacheEntryDoomed(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: nsCOMPtr mCallback; michael@0: }; michael@0: michael@0: if (aCallback) { michael@0: nsRefPtr callback = new Callback(aCallback); michael@0: return NS_DispatchToCurrentThread(callback); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheStorageService::WalkStorageEntries(CacheStorage const* aStorage, michael@0: bool aVisitEntries, michael@0: nsICacheStorageVisitor* aVisitor) michael@0: { michael@0: LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]", aVisitor, aVisitEntries)); michael@0: NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: NS_ENSURE_ARG(aStorage); michael@0: michael@0: nsAutoCString contextKey; michael@0: CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey); michael@0: michael@0: nsRefPtr event = new WalkRunnable( michael@0: contextKey, aVisitEntries, aStorage->WriteToDisk(), aVisitor); michael@0: return Dispatch(event); michael@0: } michael@0: michael@0: void michael@0: CacheStorageService::CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo, michael@0: const nsACString & aIdExtension, michael@0: const nsACString & aURISpec) michael@0: { michael@0: nsAutoCString contextKey; michael@0: CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey); michael@0: michael@0: nsAutoCString entryKey; michael@0: CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURISpec, entryKey); michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: if (mShutdown) michael@0: return; michael@0: michael@0: CacheEntryTable* entries; michael@0: if (!sGlobalEntryTables->Get(contextKey, &entries)) michael@0: return; michael@0: michael@0: nsRefPtr entry; michael@0: if (!entries->Get(entryKey, getter_AddRefs(entry))) michael@0: return; michael@0: michael@0: if (!entry->IsFileDoomed()) michael@0: return; michael@0: michael@0: if (entry->IsReferenced()) michael@0: return; michael@0: michael@0: // Need to remove under the lock to avoid possible race leading michael@0: // to duplication of the entry per its key. michael@0: RemoveExactEntry(entries, entryKey, entry, false); michael@0: entry->DoomAlreadyRemoved(); michael@0: } michael@0: michael@0: // nsIMemoryReporter michael@0: michael@0: size_t michael@0: CacheStorageService::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); michael@0: michael@0: size_t n = 0; michael@0: // The elemets are referenced by sGlobalEntryTables and are reported from there michael@0: n += Pool(true).mFrecencyArray.SizeOfExcludingThis(mallocSizeOf); michael@0: n += Pool(true).mExpirationArray.SizeOfExcludingThis(mallocSizeOf); michael@0: n += Pool(false).mFrecencyArray.SizeOfExcludingThis(mallocSizeOf); michael@0: n += Pool(false).mExpirationArray.SizeOfExcludingThis(mallocSizeOf); michael@0: // Entries reported manually in CacheStorageService::CollectReports callback michael@0: if (sGlobalEntryTables) { michael@0: n += sGlobalEntryTables->SizeOfIncludingThis(nullptr, mallocSizeOf); michael@0: } michael@0: michael@0: return n; michael@0: } michael@0: michael@0: size_t michael@0: CacheStorageService::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: class ReportStorageMemoryData michael@0: { michael@0: public: michael@0: nsIMemoryReporterCallback *mHandleReport; michael@0: nsISupports *mData; michael@0: }; michael@0: michael@0: size_t CollectEntryMemory(nsACString const & aKey, michael@0: nsRefPtr const & aEntry, michael@0: mozilla::MallocSizeOf mallocSizeOf, michael@0: void * aClosure) michael@0: { michael@0: CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); michael@0: michael@0: CacheEntryTable* aTable = static_cast(aClosure); michael@0: michael@0: size_t n = 0; michael@0: n += aKey.SizeOfExcludingThisIfUnshared(mallocSizeOf); michael@0: michael@0: // Bypass memory-only entries, those will be reported when iterating michael@0: // the memory only table. Memory-only entries are stored in both ALL_ENTRIES michael@0: // and MEMORY_ONLY hashtables. michael@0: if (aTable->Type() == CacheEntryTable::MEMORY_ONLY || aEntry->IsUsingDiskLocked()) michael@0: n += aEntry->SizeOfIncludingThis(mallocSizeOf); michael@0: michael@0: return n; michael@0: } michael@0: michael@0: PLDHashOperator ReportStorageMemory(const nsACString& aKey, michael@0: CacheEntryTable* aTable, michael@0: void* aClosure) michael@0: { michael@0: CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); michael@0: michael@0: size_t size = aTable->SizeOfIncludingThis(&CollectEntryMemory, michael@0: CacheStorageService::MallocSizeOf, michael@0: aTable); michael@0: michael@0: ReportStorageMemoryData& data = *static_cast(aClosure); michael@0: data.mHandleReport->Callback( michael@0: EmptyCString(), michael@0: nsPrintfCString("explicit/network/cache2/%s-storage(%s)", michael@0: aTable->Type() == CacheEntryTable::MEMORY_ONLY ? "memory" : "disk", michael@0: aKey.BeginReading()), michael@0: nsIMemoryReporter::KIND_HEAP, michael@0: nsIMemoryReporter::UNITS_BYTES, michael@0: size, michael@0: NS_LITERAL_CSTRING("Memory used by the cache storage."), michael@0: data.mData); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: } // anon michael@0: michael@0: NS_IMETHODIMP michael@0: CacheStorageService::CollectReports(nsIMemoryReporterCallback* aHandleReport, nsISupports* aData) michael@0: { michael@0: nsresult rv; michael@0: michael@0: rv = MOZ_COLLECT_REPORT( michael@0: "explicit/network/cache2/io", KIND_HEAP, UNITS_BYTES, michael@0: CacheFileIOManager::SizeOfIncludingThis(MallocSizeOf), michael@0: "Memory used by the cache IO manager."); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: rv = MOZ_COLLECT_REPORT( michael@0: "explicit/network/cache2/index", KIND_HEAP, UNITS_BYTES, michael@0: CacheIndex::SizeOfIncludingThis(MallocSizeOf), michael@0: "Memory used by the cache index."); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: // Report the service instance, this doesn't report entries, done lower michael@0: rv = MOZ_COLLECT_REPORT( michael@0: "explicit/network/cache2/service", KIND_HEAP, UNITS_BYTES, michael@0: SizeOfIncludingThis(MallocSizeOf), michael@0: "Memory used by the cache storage service."); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: // Report all entries, each storage separately (by the context key) michael@0: // michael@0: // References are: michael@0: // sGlobalEntryTables to N CacheEntryTable michael@0: // CacheEntryTable to N CacheEntry michael@0: // CacheEntry to 1 CacheFile michael@0: // CacheFile to michael@0: // N CacheFileChunk (keeping the actual data) michael@0: // 1 CacheFileMetadata (keeping http headers etc.) michael@0: // 1 CacheFileOutputStream michael@0: // N CacheFileInputStream michael@0: ReportStorageMemoryData data; michael@0: data.mHandleReport = aHandleReport; michael@0: data.mData = aData; michael@0: sGlobalEntryTables->EnumerateRead(&ReportStorageMemory, &data); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // net michael@0: } // mozilla