mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-06 09:05:45 +00:00
1608 lines
44 KiB
C++
1608 lines
44 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "CacheLog.h"
|
|
#include "CacheStorageService.h"
|
|
#include "CacheFileIOManager.h"
|
|
#include "CacheObserver.h"
|
|
#include "CacheIndex.h"
|
|
|
|
#include "nsICacheStorageVisitor.h"
|
|
#include "nsIObserverService.h"
|
|
#include "CacheStorage.h"
|
|
#include "AppCacheStorage.h"
|
|
#include "CacheEntry.h"
|
|
#include "CacheFileUtils.h"
|
|
|
|
#include "OldWrappers.h"
|
|
#include "nsCacheService.h"
|
|
#include "nsDeleteDir.h"
|
|
|
|
#include "nsIFile.h"
|
|
#include "nsIURI.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/VisualEventTracer.h"
|
|
#include "mozilla/Services.h"
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
namespace {
|
|
|
|
void AppendMemoryStorageID(nsAutoCString &key)
|
|
{
|
|
key.Append('M');
|
|
}
|
|
|
|
}
|
|
|
|
// Not defining as static or class member of CacheStorageService since
|
|
// it would otherwise need to include CacheEntry.h and that then would
|
|
// need to be exported to make nsNetModule.cpp compilable.
|
|
typedef nsClassHashtable<nsCStringHashKey, CacheEntryTable>
|
|
GlobalEntryTables;
|
|
|
|
/**
|
|
* Keeps tables of entries. There is one entries table for each distinct load
|
|
* context type. The distinction is based on following load context info states:
|
|
* <isPrivate|isAnon|appId|inBrowser> which builds a mapping key.
|
|
*
|
|
* Thread-safe to access, protected by the service mutex.
|
|
*/
|
|
static GlobalEntryTables* sGlobalEntryTables;
|
|
|
|
CacheMemoryConsumer::CacheMemoryConsumer()
|
|
: mReportedMemoryConsumption(0)
|
|
{
|
|
}
|
|
|
|
void
|
|
CacheMemoryConsumer::DoMemoryReport(uint32_t aCurrentSize)
|
|
{
|
|
if (CacheStorageService::Self())
|
|
CacheStorageService::Self()->OnMemoryConsumptionChange(this, aCurrentSize);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS2(CacheStorageService, nsICacheStorageService, nsIMemoryReporter)
|
|
|
|
CacheStorageService* CacheStorageService::sSelf = nullptr;
|
|
|
|
CacheStorageService::CacheStorageService()
|
|
: mLock("CacheStorageService")
|
|
, mShutdown(false)
|
|
, mMemorySize(0)
|
|
, mPurging(false)
|
|
{
|
|
CacheFileIOManager::Init();
|
|
|
|
MOZ_ASSERT(!sSelf);
|
|
|
|
sSelf = this;
|
|
sGlobalEntryTables = new GlobalEntryTables();
|
|
|
|
RegisterStrongMemoryReporter(this);
|
|
}
|
|
|
|
CacheStorageService::~CacheStorageService()
|
|
{
|
|
LOG(("CacheStorageService::~CacheStorageService"));
|
|
sSelf = nullptr;
|
|
|
|
if (mMemorySize != 0)
|
|
NS_ERROR("Network cache reported memory consumption is not at 0, probably leaking?");
|
|
}
|
|
|
|
void CacheStorageService::Shutdown()
|
|
{
|
|
if (mShutdown)
|
|
return;
|
|
|
|
LOG(("CacheStorageService::Shutdown - start"));
|
|
|
|
mShutdown = true;
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
NS_NewRunnableMethod(this, &CacheStorageService::ShutdownBackground);
|
|
Dispatch(event);
|
|
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
sGlobalEntryTables->Clear();
|
|
delete sGlobalEntryTables;
|
|
sGlobalEntryTables = nullptr;
|
|
|
|
LOG(("CacheStorageService::Shutdown - done"));
|
|
}
|
|
|
|
void CacheStorageService::ShutdownBackground()
|
|
{
|
|
MOZ_ASSERT(IsOnManagementThread());
|
|
|
|
mFrecencyArray.Clear();
|
|
mExpirationArray.Clear();
|
|
}
|
|
|
|
// Internal management methods
|
|
|
|
namespace { // anon
|
|
|
|
// EvictionRunnable
|
|
// Responsible for purgin and unregistring entries (loaded) in memory
|
|
|
|
class EvictionRunnable : public nsRunnable
|
|
{
|
|
public:
|
|
EvictionRunnable(nsCSubstring const & aContextKey, TCacheEntryTable* aEntries,
|
|
bool aUsingDisk,
|
|
nsICacheEntryDoomCallback* aCallback)
|
|
: mContextKey(aContextKey)
|
|
, mEntries(aEntries)
|
|
, mCallback(aCallback)
|
|
, mUsingDisk(aUsingDisk) { }
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
LOG(("EvictionRunnable::Run [this=%p, disk=%d]", this, mUsingDisk));
|
|
if (CacheStorageService::IsOnManagementThread()) {
|
|
if (mUsingDisk) {
|
|
// TODO for non private entries:
|
|
// - rename/move all files to TRASH, block shutdown
|
|
// - start the TRASH removal process
|
|
// - may also be invoked from the main thread...
|
|
}
|
|
|
|
if (mEntries) {
|
|
// Process only a limited number of entries during a single loop to
|
|
// prevent block of the management thread.
|
|
mBatch = 50;
|
|
mEntries->Enumerate(&EvictionRunnable::EvictEntry, this);
|
|
}
|
|
|
|
// Anything left? Process in a separate invokation.
|
|
if (mEntries && mEntries->Count())
|
|
NS_DispatchToCurrentThread(this);
|
|
else if (mCallback)
|
|
NS_DispatchToMainThread(this); // TODO - we may want caller thread
|
|
}
|
|
else if (NS_IsMainThread()) {
|
|
mCallback->OnCacheEntryDoomed(NS_OK);
|
|
}
|
|
else {
|
|
MOZ_ASSERT(false, "Not main or cache management thread");
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
virtual ~EvictionRunnable()
|
|
{
|
|
if (mCallback)
|
|
ProxyReleaseMainThread(mCallback);
|
|
}
|
|
|
|
static PLDHashOperator EvictEntry(const nsACString& aKey,
|
|
nsRefPtr<CacheEntry>& aEntry,
|
|
void* aClosure)
|
|
{
|
|
EvictionRunnable* evictor = static_cast<EvictionRunnable*>(aClosure);
|
|
|
|
LOG((" evicting entry=%p", aEntry.get()));
|
|
|
|
// HACK ...
|
|
// in-mem-only should only be Purge(WHOLE)'ed
|
|
// on-disk may use the same technique I think, disk eviction runs independently
|
|
if (!evictor->mUsingDisk) {
|
|
// When evicting memory-only entries we have to remove them from
|
|
// the master table as well. PurgeAndDoom() enters the service
|
|
// management lock.
|
|
aEntry->PurgeAndDoom();
|
|
}
|
|
else {
|
|
// Disk (+memory-only) entries are already removed from the master
|
|
// hash table, save locking here!
|
|
aEntry->DoomAlreadyRemoved();
|
|
}
|
|
|
|
if (!--evictor->mBatch)
|
|
return PLDHashOperator(PL_DHASH_REMOVE | PL_DHASH_STOP);
|
|
|
|
return PL_DHASH_REMOVE;
|
|
}
|
|
|
|
nsCString mContextKey;
|
|
nsAutoPtr<TCacheEntryTable> mEntries;
|
|
nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
|
|
uint32_t mBatch;
|
|
bool mUsingDisk;
|
|
};
|
|
|
|
// WalkRunnable
|
|
// Responsible to visit the storage and walk all entries on it asynchronously
|
|
|
|
class WalkRunnable : public nsRunnable
|
|
{
|
|
public:
|
|
WalkRunnable(nsCSubstring const & aContextKey, bool aVisitEntries,
|
|
bool aUsingDisk,
|
|
nsICacheStorageVisitor* aVisitor)
|
|
: mContextKey(aContextKey)
|
|
, mCallback(aVisitor)
|
|
, mSize(0)
|
|
, mNotifyStorage(true)
|
|
, mVisitEntries(aVisitEntries)
|
|
, mUsingDisk(aUsingDisk)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
}
|
|
|
|
private:
|
|
NS_IMETHODIMP Run()
|
|
{
|
|
if (CacheStorageService::IsOnManagementThread()) {
|
|
LOG(("WalkRunnable::Run - collecting [this=%p, disk=%d]", this, (bool)mUsingDisk));
|
|
// First, walk, count and grab all entries from the storage
|
|
// TODO
|
|
// - walk files on disk, when the storage is not private
|
|
// - should create representative entries only for the time
|
|
// of need
|
|
|
|
mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
|
|
|
|
if (!CacheStorageService::IsRunning())
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
CacheEntryTable* entries;
|
|
if (sGlobalEntryTables->Get(mContextKey, &entries))
|
|
entries->EnumerateRead(&WalkRunnable::WalkStorage, this);
|
|
|
|
// Next, we dispatch to the main thread
|
|
}
|
|
else if (NS_IsMainThread()) {
|
|
LOG(("WalkRunnable::Run - notifying [this=%p, disk=%d]", this, (bool)mUsingDisk));
|
|
if (mNotifyStorage) {
|
|
LOG((" storage"));
|
|
// Second, notify overall storage info
|
|
mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize);
|
|
if (!mVisitEntries)
|
|
return NS_OK; // done
|
|
|
|
mNotifyStorage = false;
|
|
}
|
|
else {
|
|
LOG((" entry [left=%d]", mEntryArray.Length()));
|
|
// Third, notify each entry until depleted.
|
|
if (!mEntryArray.Length()) {
|
|
mCallback->OnCacheEntryVisitCompleted();
|
|
return NS_OK; // done
|
|
}
|
|
|
|
mCallback->OnCacheEntryInfo(mEntryArray[0]);
|
|
mEntryArray.RemoveElementAt(0);
|
|
|
|
// Dispatch to the main thread again
|
|
}
|
|
}
|
|
else {
|
|
MOZ_ASSERT(false);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
NS_DispatchToMainThread(this);
|
|
return NS_OK;
|
|
}
|
|
|
|
virtual ~WalkRunnable()
|
|
{
|
|
if (mCallback)
|
|
ProxyReleaseMainThread(mCallback);
|
|
}
|
|
|
|
static PLDHashOperator
|
|
WalkStorage(const nsACString& aKey,
|
|
CacheEntry* aEntry,
|
|
void* aClosure)
|
|
{
|
|
WalkRunnable* walker = static_cast<WalkRunnable*>(aClosure);
|
|
|
|
if (!walker->mUsingDisk && aEntry->UsingDisk())
|
|
return PL_DHASH_NEXT;
|
|
|
|
walker->mSize += aEntry->GetMetadataMemoryConsumption();
|
|
|
|
int64_t size;
|
|
if (NS_SUCCEEDED(aEntry->GetDataSize(&size)))
|
|
walker->mSize += size;
|
|
|
|
walker->mEntryArray.AppendElement(aEntry);
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
nsCString mContextKey;
|
|
nsCOMPtr<nsICacheStorageVisitor> mCallback;
|
|
nsTArray<nsRefPtr<CacheEntry> > mEntryArray;
|
|
|
|
uint64_t mSize;
|
|
|
|
bool mNotifyStorage : 1;
|
|
bool mVisitEntries : 1;
|
|
bool mUsingDisk : 1;
|
|
};
|
|
|
|
PLDHashOperator CollectPrivateContexts(const nsACString& aKey,
|
|
CacheEntryTable* aTable,
|
|
void* aClosure)
|
|
{
|
|
if (aKey[0] == 'P') {
|
|
nsTArray<nsCString>* keys = static_cast<nsTArray<nsCString>*>(aClosure);
|
|
keys->AppendElement(aKey);
|
|
}
|
|
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
PLDHashOperator CollectContexts(const nsACString& aKey,
|
|
CacheEntryTable* aTable,
|
|
void* aClosure)
|
|
{
|
|
nsTArray<nsCString>* keys = static_cast<nsTArray<nsCString>*>(aClosure);
|
|
keys->AppendElement(aKey);
|
|
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
} // anon
|
|
|
|
void CacheStorageService::DropPrivateBrowsingEntries()
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
if (mShutdown)
|
|
return;
|
|
|
|
nsTArray<nsCString> keys;
|
|
sGlobalEntryTables->EnumerateRead(&CollectPrivateContexts, &keys);
|
|
|
|
for (uint32_t i = 0; i < keys.Length(); ++i)
|
|
DoomStorageEntries(keys[i], true, nullptr);
|
|
}
|
|
|
|
// static
|
|
void CacheStorageService::WipeCacheDirectory(uint32_t aVersion)
|
|
{
|
|
nsCOMPtr<nsIFile> cacheDir;
|
|
switch (aVersion) {
|
|
case 0:
|
|
nsCacheService::GetDiskCacheDirectory(getter_AddRefs(cacheDir));
|
|
break;
|
|
case 1:
|
|
CacheFileIOManager::GetCacheDirectory(getter_AddRefs(cacheDir));
|
|
break;
|
|
}
|
|
|
|
if (!cacheDir)
|
|
return;
|
|
|
|
nsDeleteDir::DeleteDir(cacheDir, true, 30000);
|
|
}
|
|
|
|
// Helper methods
|
|
|
|
// static
|
|
bool CacheStorageService::IsOnManagementThread()
|
|
{
|
|
nsRefPtr<CacheStorageService> service = Self();
|
|
if (!service)
|
|
return false;
|
|
|
|
nsCOMPtr<nsIEventTarget> target = service->Thread();
|
|
if (!target)
|
|
return false;
|
|
|
|
bool currentThread;
|
|
nsresult rv = target->IsOnCurrentThread(¤tThread);
|
|
return NS_SUCCEEDED(rv) && currentThread;
|
|
}
|
|
|
|
already_AddRefed<nsIEventTarget> CacheStorageService::Thread() const
|
|
{
|
|
return CacheFileIOManager::IOTarget();
|
|
}
|
|
|
|
nsresult CacheStorageService::Dispatch(nsIRunnable* aEvent)
|
|
{
|
|
nsRefPtr<CacheIOThread> cacheIOThread = CacheFileIOManager::IOThread();
|
|
if (!cacheIOThread)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return cacheIOThread->Dispatch(aEvent, CacheIOThread::MANAGEMENT);
|
|
}
|
|
|
|
// nsICacheStorageService
|
|
|
|
NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(nsILoadContextInfo *aLoadContextInfo,
|
|
nsICacheStorage * *_retval)
|
|
{
|
|
NS_ENSURE_ARG(aLoadContextInfo);
|
|
NS_ENSURE_ARG(_retval);
|
|
|
|
nsCOMPtr<nsICacheStorage> storage;
|
|
if (CacheObserver::UseNewCache()) {
|
|
storage = new CacheStorage(aLoadContextInfo, false, false);
|
|
}
|
|
else {
|
|
storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
|
|
}
|
|
|
|
storage.forget(_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP CacheStorageService::DiskCacheStorage(nsILoadContextInfo *aLoadContextInfo,
|
|
bool aLookupAppCache,
|
|
nsICacheStorage * *_retval)
|
|
{
|
|
NS_ENSURE_ARG(aLoadContextInfo);
|
|
NS_ENSURE_ARG(_retval);
|
|
|
|
// TODO save some heap granularity - cache commonly used storages.
|
|
|
|
// When disk cache is disabled, still provide a storage, but just keep stuff
|
|
// in memory.
|
|
bool useDisk = CacheObserver::UseDiskCache();
|
|
|
|
nsCOMPtr<nsICacheStorage> storage;
|
|
if (CacheObserver::UseNewCache()) {
|
|
storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache);
|
|
}
|
|
else {
|
|
storage = new _OldStorage(aLoadContextInfo, useDisk, aLookupAppCache, false, nullptr);
|
|
}
|
|
|
|
storage.forget(_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP CacheStorageService::AppCacheStorage(nsILoadContextInfo *aLoadContextInfo,
|
|
nsIApplicationCache *aApplicationCache,
|
|
nsICacheStorage * *_retval)
|
|
{
|
|
NS_ENSURE_ARG(aLoadContextInfo);
|
|
NS_ENSURE_ARG(_retval);
|
|
|
|
nsCOMPtr<nsICacheStorage> storage;
|
|
if (CacheObserver::UseNewCache()) {
|
|
// Using classification since cl believes we want to instantiate this method
|
|
// having the same name as the desired class...
|
|
storage = new mozilla::net::AppCacheStorage(aLoadContextInfo, aApplicationCache);
|
|
}
|
|
else {
|
|
storage = new _OldStorage(aLoadContextInfo, true, false, true, aApplicationCache);
|
|
}
|
|
|
|
storage.forget(_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace { // anon
|
|
|
|
class CacheFilesDeletor : public nsRunnable
|
|
, public CacheEntriesEnumeratorCallback
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
CacheFilesDeletor(nsICacheEntryDoomCallback* aCallback);
|
|
~CacheFilesDeletor();
|
|
|
|
nsresult DeleteAll();
|
|
nsresult DeleteDoomed();
|
|
|
|
private:
|
|
nsresult Init(CacheFileIOManager::EEnumerateMode aMode);
|
|
NS_IMETHOD Run();
|
|
NS_IMETHOD Execute();
|
|
void Callback();
|
|
virtual void OnFile(CacheFile* aFile);
|
|
|
|
nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
|
|
nsAutoPtr<CacheEntriesEnumerator> mEnumerator;
|
|
nsRefPtr<CacheIOThread> mIOThread;
|
|
|
|
uint32_t mRunning;
|
|
enum {
|
|
ALL,
|
|
DOOMED
|
|
} mMode;
|
|
nsresult mRv;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(CacheFilesDeletor, nsRunnable);
|
|
|
|
CacheFilesDeletor::CacheFilesDeletor(nsICacheEntryDoomCallback* aCallback)
|
|
: mCallback(aCallback)
|
|
, mRunning(0)
|
|
, mRv(NS_OK)
|
|
{
|
|
MOZ_COUNT_CTOR(CacheFilesDeletor);
|
|
MOZ_EVENT_TRACER_WAIT(static_cast<nsRunnable*>(this), "net::cache::deletor");
|
|
}
|
|
|
|
CacheFilesDeletor::~CacheFilesDeletor()
|
|
{
|
|
MOZ_COUNT_DTOR(CacheFilesDeletor);
|
|
MOZ_EVENT_TRACER_DONE(static_cast<nsRunnable*>(this), "net::cache::deletor");
|
|
|
|
if (mMode == ALL) {
|
|
// Now delete the doomed entries if some left.
|
|
nsRefPtr<CacheFilesDeletor> deletor = new CacheFilesDeletor(mCallback);
|
|
|
|
nsRefPtr<nsRunnableMethod<CacheFilesDeletor, nsresult> > event =
|
|
NS_NewRunnableMethod(deletor.get(), &CacheFilesDeletor::DeleteDoomed);
|
|
NS_DispatchToMainThread(event);
|
|
}
|
|
}
|
|
|
|
nsresult CacheFilesDeletor::DeleteAll()
|
|
{
|
|
mMode = ALL;
|
|
return Init(CacheFileIOManager::ENTRIES);
|
|
}
|
|
|
|
nsresult CacheFilesDeletor::DeleteDoomed()
|
|
{
|
|
mMode = DOOMED;
|
|
return Init(CacheFileIOManager::DOOMED);
|
|
}
|
|
|
|
nsresult CacheFilesDeletor::Init(CacheFileIOManager::EEnumerateMode aMode)
|
|
{
|
|
nsresult rv;
|
|
|
|
rv = CacheFileIOManager::EnumerateEntryFiles(
|
|
aMode, getter_Transfers(mEnumerator));
|
|
|
|
if (NS_ERROR_FILE_NOT_FOUND == rv || NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv) {
|
|
rv = NS_OK;
|
|
}
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mIOThread = CacheFileIOManager::IOThread();
|
|
NS_ENSURE_TRUE(mIOThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
rv = mIOThread->Dispatch(this, CacheIOThread::EVICT);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void CacheFilesDeletor::Callback()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
|
|
if (obsSvc) {
|
|
obsSvc->NotifyObservers(CacheStorageService::SelfISupports(),
|
|
"cacheservice:empty-cache",
|
|
nullptr);
|
|
}
|
|
|
|
if (!mCallback)
|
|
return;
|
|
|
|
nsCOMPtr<nsICacheEntryDoomCallback> callback;
|
|
callback.swap(mCallback);
|
|
callback->OnCacheEntryDoomed(mRv);
|
|
}
|
|
|
|
NS_IMETHODIMP CacheFilesDeletor::Run()
|
|
{
|
|
if (!mRunning) {
|
|
MOZ_EVENT_TRACER_EXEC(static_cast<nsRunnable*>(this), "net::cache::deletor");
|
|
}
|
|
|
|
MOZ_EVENT_TRACER_EXEC(static_cast<nsRunnable*>(this), "net::cache::deletor::exec");
|
|
|
|
nsresult rv = Execute();
|
|
if (NS_SUCCEEDED(mRv))
|
|
mRv = rv;
|
|
|
|
if (!mEnumerator || !mEnumerator->HasMore()) {
|
|
// No enumerator or no more elements means the job is done.
|
|
mEnumerator = nullptr;
|
|
|
|
if (mMode != ALL) {
|
|
nsRefPtr<nsRunnableMethod<CacheFilesDeletor> > event =
|
|
NS_NewRunnableMethod(this, &CacheFilesDeletor::Callback);
|
|
NS_DispatchToMainThread(event);
|
|
}
|
|
}
|
|
|
|
MOZ_EVENT_TRACER_DONE(static_cast<nsRunnable*>(this), "net::cache::deletor::exec");
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheFilesDeletor::Execute()
|
|
{
|
|
LOG(("CacheFilesDeletor::Execute [this=%p]", this));
|
|
|
|
if (!mEnumerator) {
|
|
// No enumerator means the job is done.
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv;
|
|
TimeStamp start;
|
|
|
|
switch (mMode) {
|
|
case ALL:
|
|
case DOOMED:
|
|
// Simply delete all files, don't doom then though the backend
|
|
start = TimeStamp::NowLoRes();
|
|
|
|
while (mEnumerator->HasMore()) {
|
|
nsCOMPtr<nsIFile> file;
|
|
rv = mEnumerator->GetNextFile(getter_AddRefs(file));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
#ifdef PR_LOG
|
|
nsAutoCString key;
|
|
file->GetNativeLeafName(key);
|
|
LOG((" deleting file with key=%s", key.get()));
|
|
#endif
|
|
|
|
rv = file->Remove(false);
|
|
if (NS_FAILED(rv)) {
|
|
LOG((" could not remove the file, probably doomed, rv=0x%08x", rv));
|
|
}
|
|
|
|
++mRunning;
|
|
|
|
if (!(mRunning % (1 << 5)) && mEnumerator->HasMore()) {
|
|
TimeStamp now(TimeStamp::NowLoRes());
|
|
#define DELETOR_LOOP_LIMIT_MS 200
|
|
static TimeDuration const kLimitms = TimeDuration::FromMilliseconds(DELETOR_LOOP_LIMIT_MS);
|
|
if ((now - start) > kLimitms) {
|
|
LOG((" deleted %u files, breaking %dms loop", mRunning, DELETOR_LOOP_LIMIT_MS));
|
|
rv = mIOThread->Dispatch(this, CacheIOThread::EVICT);
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void CacheFilesDeletor::OnFile(CacheFile* aFile)
|
|
{
|
|
LOG(("CacheFilesDeletor::OnFile [this=%p, file=%p]", this, aFile));
|
|
|
|
if (!aFile)
|
|
return;
|
|
|
|
MOZ_EVENT_TRACER_EXEC(static_cast<nsRunnable*>(this), "net::cache::deletor::file");
|
|
|
|
#ifdef PR_LOG
|
|
nsAutoCString key;
|
|
aFile->Key(key);
|
|
#endif
|
|
|
|
switch (mMode) {
|
|
case ALL:
|
|
case DOOMED:
|
|
LOG((" dooming file with key=%s", key.get()));
|
|
// Uncompromisely delete the file!
|
|
aFile->Doom(nullptr);
|
|
break;
|
|
}
|
|
|
|
MOZ_EVENT_TRACER_DONE(static_cast<nsRunnable*>(this), "net::cache::deletor::file");
|
|
}
|
|
|
|
} // anon
|
|
|
|
NS_IMETHODIMP CacheStorageService::Clear()
|
|
{
|
|
nsresult rv;
|
|
|
|
if (CacheObserver::UseNewCache()) {
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsTArray<nsCString> keys;
|
|
sGlobalEntryTables->EnumerateRead(&CollectContexts, &keys);
|
|
|
|
for (uint32_t i = 0; i < keys.Length(); ++i)
|
|
DoomStorageEntries(keys[i], true, nullptr);
|
|
}
|
|
|
|
// TODO - Callback can be provided!
|
|
nsRefPtr<CacheFilesDeletor> deletor = new CacheFilesDeletor(nullptr);
|
|
rv = deletor->DeleteAll();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
else {
|
|
nsCOMPtr<nsICacheService> serv =
|
|
do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = serv->EvictEntries(nsICache::STORE_ANYWHERE);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat)
|
|
{
|
|
uint32_t what;
|
|
|
|
switch (aWhat) {
|
|
case PURGE_DISK_DATA_ONLY:
|
|
what = CacheEntry::PURGE_DATA_ONLY_DISK_BACKED;
|
|
break;
|
|
|
|
case PURGE_DISK_ALL:
|
|
what = CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED;
|
|
break;
|
|
|
|
case PURGE_EVERYTHING:
|
|
what = CacheEntry::PURGE_WHOLE;
|
|
break;
|
|
|
|
default:
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
new PurgeFromMemoryRunnable(this, what);
|
|
|
|
return Dispatch(event);
|
|
}
|
|
|
|
NS_IMETHODIMP CacheStorageService::GetIoTarget(nsIEventTarget** aEventTarget)
|
|
{
|
|
NS_ENSURE_ARG(aEventTarget);
|
|
|
|
if (CacheObserver::UseNewCache()) {
|
|
nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
|
|
ioTarget.forget(aEventTarget);
|
|
}
|
|
else {
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsICacheService> serv =
|
|
do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = serv->GetCacheIOTarget(aEventTarget);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Methods used by CacheEntry for management of in-memory structures.
|
|
|
|
namespace { // anon
|
|
|
|
class FrecencyComparator
|
|
{
|
|
public:
|
|
bool Equals(CacheEntry* a, CacheEntry* b) const {
|
|
return a->GetFrecency() == b->GetFrecency();
|
|
}
|
|
bool LessThan(CacheEntry* a, CacheEntry* b) const {
|
|
return a->GetFrecency() < b->GetFrecency();
|
|
}
|
|
};
|
|
|
|
class ExpirationComparator
|
|
{
|
|
public:
|
|
bool Equals(CacheEntry* a, CacheEntry* b) const {
|
|
return a->GetExpirationTime() == b->GetExpirationTime();
|
|
}
|
|
bool LessThan(CacheEntry* a, CacheEntry* b) const {
|
|
return a->GetExpirationTime() < b->GetExpirationTime();
|
|
}
|
|
};
|
|
|
|
} // anon
|
|
|
|
void
|
|
CacheStorageService::RegisterEntry(CacheEntry* aEntry)
|
|
{
|
|
MOZ_ASSERT(IsOnManagementThread());
|
|
|
|
if (mShutdown || !aEntry->CanRegister())
|
|
return;
|
|
|
|
LOG(("CacheStorageService::RegisterEntry [entry=%p]", aEntry));
|
|
|
|
mFrecencyArray.InsertElementSorted(aEntry, FrecencyComparator());
|
|
mExpirationArray.InsertElementSorted(aEntry, ExpirationComparator());
|
|
|
|
aEntry->SetRegistered(true);
|
|
}
|
|
|
|
void
|
|
CacheStorageService::UnregisterEntry(CacheEntry* aEntry)
|
|
{
|
|
MOZ_ASSERT(IsOnManagementThread());
|
|
|
|
if (!aEntry->IsRegistered())
|
|
return;
|
|
|
|
LOG(("CacheStorageService::UnregisterEntry [entry=%p]", aEntry));
|
|
|
|
mozilla::DebugOnly<bool> removedFrecency = mFrecencyArray.RemoveElement(aEntry);
|
|
mozilla::DebugOnly<bool> removedExpiration = mExpirationArray.RemoveElement(aEntry);
|
|
|
|
MOZ_ASSERT(mShutdown || (removedFrecency && removedExpiration));
|
|
|
|
// Note: aEntry->CanRegister() since now returns false
|
|
aEntry->SetRegistered(false);
|
|
}
|
|
|
|
static bool
|
|
AddExactEntry(CacheEntryTable* aEntries,
|
|
nsCString const& aKey,
|
|
CacheEntry* aEntry,
|
|
bool aOverwrite)
|
|
{
|
|
nsRefPtr<CacheEntry> existingEntry;
|
|
if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
|
|
bool equals = existingEntry == aEntry;
|
|
LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals));
|
|
return equals; // Already there...
|
|
}
|
|
|
|
LOG(("AddExactEntry [entry=%p put]", aEntry));
|
|
aEntries->Put(aKey, aEntry);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RemoveExactEntry(CacheEntryTable* aEntries,
|
|
nsCString const& aKey,
|
|
CacheEntry* aEntry,
|
|
bool aOverwrite)
|
|
{
|
|
nsRefPtr<CacheEntry> existingEntry;
|
|
if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
|
|
LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
|
|
return false; // Already removed...
|
|
}
|
|
|
|
if (!aOverwrite && existingEntry != aEntry) {
|
|
LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry));
|
|
return false; // Already replaced...
|
|
}
|
|
|
|
LOG(("RemoveExactEntry [entry=%p removed]", aEntry));
|
|
aEntries->Remove(aKey);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CacheStorageService::RemoveEntry(CacheEntry* aEntry, bool aOnlyUnreferenced)
|
|
{
|
|
LOG(("CacheStorageService::RemoveEntry [entry=%p]", aEntry));
|
|
|
|
nsAutoCString entryKey;
|
|
nsresult rv = aEntry->HashingKey(entryKey);
|
|
if (NS_FAILED(rv)) {
|
|
NS_ERROR("aEntry->HashingKey() failed?");
|
|
return false;
|
|
}
|
|
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
if (mShutdown) {
|
|
LOG((" after shutdown"));
|
|
return false;
|
|
}
|
|
|
|
if (aOnlyUnreferenced && aEntry->IsReferenced()) {
|
|
LOG((" still referenced, not removing"));
|
|
return false;
|
|
}
|
|
|
|
CacheEntryTable* entries;
|
|
if (sGlobalEntryTables->Get(aEntry->GetStorageID(), &entries))
|
|
RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
|
|
|
|
nsAutoCString memoryStorageID(aEntry->GetStorageID());
|
|
AppendMemoryStorageID(memoryStorageID);
|
|
|
|
if (sGlobalEntryTables->Get(memoryStorageID, &entries))
|
|
RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
CacheStorageService::RecordMemoryOnlyEntry(CacheEntry* aEntry,
|
|
bool aOnlyInMemory,
|
|
bool aOverwrite)
|
|
{
|
|
LOG(("CacheStorageService::RecordMemoryOnlyEntry [entry=%p, memory=%d, overwrite=%d]",
|
|
aEntry, aOnlyInMemory, aOverwrite));
|
|
// This method is responsible to put this entry to a special record hashtable
|
|
// that contains only entries that are stored in memory.
|
|
// Keep in mind that every entry, regardless of whether is in-memory-only or not
|
|
// is always recorded in the storage master hash table, the one identified by
|
|
// CacheEntry.StorageID().
|
|
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
if (mShutdown) {
|
|
LOG((" after shutdown"));
|
|
return;
|
|
}
|
|
|
|
nsresult rv;
|
|
|
|
nsAutoCString entryKey;
|
|
rv = aEntry->HashingKey(entryKey);
|
|
if (NS_FAILED(rv)) {
|
|
NS_ERROR("aEntry->HashingKey() failed?");
|
|
return;
|
|
}
|
|
|
|
CacheEntryTable* entries = nullptr;
|
|
nsAutoCString memoryStorageID(aEntry->GetStorageID());
|
|
AppendMemoryStorageID(memoryStorageID);
|
|
|
|
if (!sGlobalEntryTables->Get(memoryStorageID, &entries)) {
|
|
if (!aOnlyInMemory) {
|
|
LOG((" not recorded as memory only"));
|
|
return;
|
|
}
|
|
|
|
entries = new CacheEntryTable(CacheEntryTable::MEMORY_ONLY);
|
|
sGlobalEntryTables->Put(memoryStorageID, entries);
|
|
LOG((" new memory-only storage table for %s", memoryStorageID.get()));
|
|
}
|
|
|
|
if (aOnlyInMemory) {
|
|
AddExactEntry(entries, entryKey, aEntry, aOverwrite);
|
|
}
|
|
else {
|
|
RemoveExactEntry(entries, entryKey, aEntry, aOverwrite);
|
|
}
|
|
}
|
|
|
|
void
|
|
CacheStorageService::OnMemoryConsumptionChange(CacheMemoryConsumer* aConsumer,
|
|
uint32_t aCurrentMemoryConsumption)
|
|
{
|
|
LOG(("CacheStorageService::OnMemoryConsumptionChange [consumer=%p, size=%u]",
|
|
aConsumer, aCurrentMemoryConsumption));
|
|
|
|
uint32_t savedMemorySize = aConsumer->mReportedMemoryConsumption;
|
|
if (savedMemorySize == aCurrentMemoryConsumption)
|
|
return;
|
|
|
|
// Exchange saved size with current one.
|
|
aConsumer->mReportedMemoryConsumption = aCurrentMemoryConsumption;
|
|
|
|
mMemorySize -= savedMemorySize;
|
|
mMemorySize += aCurrentMemoryConsumption;
|
|
|
|
LOG((" mMemorySize=%u (+%u,-%u)", uint32_t(mMemorySize), aCurrentMemoryConsumption, savedMemorySize));
|
|
|
|
// Bypass purging when memory has not grew up significantly
|
|
if (aCurrentMemoryConsumption <= savedMemorySize)
|
|
return;
|
|
|
|
if (mPurging) {
|
|
LOG((" already purging"));
|
|
return;
|
|
}
|
|
|
|
if (mMemorySize <= CacheObserver::MemoryLimit())
|
|
return;
|
|
|
|
// Throw the oldest data or whole entries away when over certain limits
|
|
mPurging = true;
|
|
|
|
// Must always dipatch, since this can be called under e.g. a CacheFile's lock.
|
|
nsCOMPtr<nsIRunnable> event =
|
|
NS_NewRunnableMethod(this, &CacheStorageService::PurgeOverMemoryLimit);
|
|
|
|
Dispatch(event);
|
|
}
|
|
|
|
void
|
|
CacheStorageService::PurgeOverMemoryLimit()
|
|
{
|
|
MOZ_ASSERT(IsOnManagementThread());
|
|
|
|
LOG(("CacheStorageService::PurgeOverMemoryLimit"));
|
|
|
|
#ifdef PR_LOG
|
|
TimeStamp start(TimeStamp::Now());
|
|
#endif
|
|
|
|
uint32_t const memoryLimit = CacheObserver::MemoryLimit();
|
|
|
|
if (mMemorySize > memoryLimit) {
|
|
LOG((" memory data consumption over the limit, abandon expired entries"));
|
|
PurgeExpired();
|
|
}
|
|
|
|
bool frecencyNeedsSort = true;
|
|
if (mMemorySize > memoryLimit) {
|
|
LOG((" memory data consumption over the limit, abandon disk backed data"));
|
|
PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_DATA_ONLY_DISK_BACKED);
|
|
}
|
|
|
|
if (mMemorySize > memoryLimit) {
|
|
LOG((" metadata consumtion over the limit, abandon disk backed entries"));
|
|
PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED);
|
|
}
|
|
|
|
if (mMemorySize > memoryLimit) {
|
|
LOG((" memory data consumption over the limit, abandon any entry"));
|
|
PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE);
|
|
}
|
|
|
|
LOG((" purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds()));
|
|
|
|
mPurging = false;
|
|
}
|
|
|
|
void
|
|
CacheStorageService::PurgeExpired()
|
|
{
|
|
MOZ_ASSERT(IsOnManagementThread());
|
|
|
|
mExpirationArray.Sort(ExpirationComparator());
|
|
uint32_t now = NowInSeconds();
|
|
|
|
uint32_t const memoryLimit = CacheObserver::MemoryLimit();
|
|
|
|
for (uint32_t i = 0; mMemorySize > memoryLimit && i < mExpirationArray.Length();) {
|
|
nsRefPtr<CacheEntry> entry = mExpirationArray[i];
|
|
|
|
uint32_t expirationTime = entry->GetExpirationTime();
|
|
if (expirationTime > 0 && expirationTime <= now) {
|
|
LOG((" dooming expired entry=%p, exptime=%u (now=%u)",
|
|
entry.get(), entry->GetExpirationTime(), now));
|
|
|
|
entry->PurgeAndDoom();
|
|
continue;
|
|
}
|
|
|
|
// not purged, move to the next one
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void
|
|
CacheStorageService::PurgeByFrecency(bool &aFrecencyNeedsSort, uint32_t aWhat)
|
|
{
|
|
MOZ_ASSERT(IsOnManagementThread());
|
|
|
|
if (aFrecencyNeedsSort) {
|
|
mFrecencyArray.Sort(FrecencyComparator());
|
|
aFrecencyNeedsSort = false;
|
|
}
|
|
|
|
uint32_t const memoryLimit = CacheObserver::MemoryLimit();
|
|
|
|
for (uint32_t i = 0; mMemorySize > memoryLimit && i < mFrecencyArray.Length();) {
|
|
nsRefPtr<CacheEntry> entry = mFrecencyArray[i];
|
|
|
|
if (entry->Purge(aWhat)) {
|
|
LOG((" abandoned (%d), entry=%p, frecency=%1.10f",
|
|
aWhat, entry.get(), entry->GetFrecency()));
|
|
continue;
|
|
}
|
|
|
|
// not purged, move to the next one
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void
|
|
CacheStorageService::PurgeAll(uint32_t aWhat)
|
|
{
|
|
LOG(("CacheStorageService::PurgeAll aWhat=%d", aWhat));
|
|
MOZ_ASSERT(IsOnManagementThread());
|
|
|
|
for (uint32_t i = 0; i < mFrecencyArray.Length();) {
|
|
nsRefPtr<CacheEntry> entry = mFrecencyArray[i];
|
|
|
|
if (entry->Purge(aWhat)) {
|
|
LOG((" abandoned entry=%p", entry.get()));
|
|
continue;
|
|
}
|
|
|
|
// not purged, move to the next one
|
|
++i;
|
|
}
|
|
}
|
|
|
|
// Methods exposed to and used by CacheStorage.
|
|
|
|
nsresult
|
|
CacheStorageService::AddStorageEntry(CacheStorage const* aStorage,
|
|
nsIURI* aURI,
|
|
const nsACString & aIdExtension,
|
|
bool aCreateIfNotExist,
|
|
bool aReplace,
|
|
CacheEntryHandle** aResult)
|
|
{
|
|
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
NS_ENSURE_ARG(aStorage);
|
|
|
|
nsAutoCString contextKey;
|
|
CacheFileUtils::CreateKeyPrefix(aStorage->LoadInfo(), contextKey);
|
|
|
|
return AddStorageEntry(contextKey, aURI, aIdExtension,
|
|
aStorage->WriteToDisk(), aCreateIfNotExist, aReplace,
|
|
aResult);
|
|
}
|
|
|
|
nsresult
|
|
CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey,
|
|
nsIURI* aURI,
|
|
const nsACString & aIdExtension,
|
|
bool aWriteToDisk,
|
|
bool aCreateIfNotExist,
|
|
bool aReplace,
|
|
CacheEntryHandle** aResult)
|
|
{
|
|
NS_ENSURE_ARG(aURI);
|
|
|
|
nsresult rv;
|
|
|
|
nsAutoCString entryKey;
|
|
rv = CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
LOG(("CacheStorageService::AddStorageEntry [entryKey=%s, contextKey=%s]",
|
|
entryKey.get(), aContextKey.BeginReading()));
|
|
|
|
nsRefPtr<CacheEntry> entry;
|
|
nsRefPtr<CacheEntryHandle> handle;
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
// Ensure storage table
|
|
CacheEntryTable* entries;
|
|
if (!sGlobalEntryTables->Get(aContextKey, &entries)) {
|
|
entries = new CacheEntryTable(CacheEntryTable::ALL_ENTRIES);
|
|
sGlobalEntryTables->Put(aContextKey, entries);
|
|
LOG((" new storage entries table for context %s", aContextKey.BeginReading()));
|
|
}
|
|
|
|
bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
|
|
|
|
// check whether the file is already doomed
|
|
if (entryExists && entry->IsFileDoomed() && !aReplace) {
|
|
aReplace = true;
|
|
}
|
|
|
|
// Check entry that is memory-only is also in related memory-only hashtable.
|
|
// If not, it has been evicted and we will truncate it ; doom is pending for it,
|
|
// this consumer just made it sooner then the entry has actually been removed
|
|
// from the master hash table.
|
|
// (This can be bypassed when entry is about to be replaced anyway.)
|
|
if (entryExists && !entry->UsingDisk() && !aReplace) {
|
|
nsAutoCString memoryStorageID(aContextKey);
|
|
AppendMemoryStorageID(memoryStorageID);
|
|
CacheEntryTable* memoryEntries;
|
|
aReplace = sGlobalEntryTables->Get(memoryStorageID, &memoryEntries) &&
|
|
memoryEntries->GetWeak(entryKey) != entry;
|
|
|
|
#ifdef MOZ_LOGGING
|
|
if (aReplace) {
|
|
LOG((" memory-only entry %p for %s already doomed, replacing", entry.get(), entryKey.get()));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// If truncate is demanded, delete and doom the current entry
|
|
if (entryExists && aReplace) {
|
|
entries->Remove(entryKey);
|
|
|
|
LOG((" dooming entry %p for %s because of OPEN_TRUNCATE", entry.get(), entryKey.get()));
|
|
// On purpose called under the lock to prevent races of doom and open on I/O thread
|
|
entry->DoomAlreadyRemoved();
|
|
|
|
entry = nullptr;
|
|
entryExists = false;
|
|
}
|
|
|
|
if (entryExists && entry->SetUsingDisk(aWriteToDisk)) {
|
|
RecordMemoryOnlyEntry(entry, !aWriteToDisk, true /* overwrite */);
|
|
}
|
|
|
|
// Ensure entry for the particular URL, if not read/only
|
|
if (!entryExists && (aCreateIfNotExist || aReplace)) {
|
|
// Entry is not in the hashtable or has just been truncated...
|
|
entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk);
|
|
entries->Put(entryKey, entry);
|
|
LOG((" new entry %p for %s", entry.get(), entryKey.get()));
|
|
}
|
|
|
|
if (entry) {
|
|
// Here, if this entry was not for a long time referenced by any consumer,
|
|
// gets again first 'handlers count' reference.
|
|
handle = entry->NewHandle();
|
|
}
|
|
}
|
|
|
|
handle.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace { // anon
|
|
|
|
class CacheEntryDoomByKeyCallback : public CacheFileIOListener
|
|
{
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback* aCallback)
|
|
: mCallback(aCallback) { }
|
|
virtual ~CacheEntryDoomByKeyCallback();
|
|
|
|
private:
|
|
NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) { return NS_OK; }
|
|
NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, nsresult aResult) { return NS_OK; }
|
|
NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) { return NS_OK; }
|
|
NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult);
|
|
NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) { return NS_OK; }
|
|
NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) { return NS_OK; }
|
|
|
|
nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
|
|
};
|
|
|
|
CacheEntryDoomByKeyCallback::~CacheEntryDoomByKeyCallback()
|
|
{
|
|
if (mCallback)
|
|
ProxyReleaseMainThread(mCallback);
|
|
}
|
|
|
|
NS_IMETHODIMP CacheEntryDoomByKeyCallback::OnFileDoomed(CacheFileHandle *aHandle,
|
|
nsresult aResult)
|
|
{
|
|
if (!mCallback)
|
|
return NS_OK;
|
|
|
|
mCallback->OnCacheEntryDoomed(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS1(CacheEntryDoomByKeyCallback, CacheFileIOListener);
|
|
|
|
} // anon
|
|
|
|
nsresult
|
|
CacheStorageService::DoomStorageEntry(CacheStorage const* aStorage,
|
|
nsIURI *aURI,
|
|
const nsACString & aIdExtension,
|
|
nsICacheEntryDoomCallback* aCallback)
|
|
{
|
|
LOG(("CacheStorageService::DoomStorageEntry"));
|
|
|
|
NS_ENSURE_ARG(aStorage);
|
|
NS_ENSURE_ARG(aURI);
|
|
|
|
nsAutoCString contextKey;
|
|
CacheFileUtils::CreateKeyPrefix(aStorage->LoadInfo(), contextKey);
|
|
|
|
nsAutoCString entryKey;
|
|
nsresult rv = CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsRefPtr<CacheEntry> entry;
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
CacheEntryTable* entries;
|
|
if (sGlobalEntryTables->Get(contextKey, &entries)) {
|
|
if (entries->Get(entryKey, getter_AddRefs(entry))) {
|
|
if (aStorage->WriteToDisk() || !entry->UsingDisk()) {
|
|
// When evicting from disk storage, purge
|
|
// When evicting from memory storage and the entry is memory-only, purge
|
|
LOG((" purging entry %p for %s [storage use disk=%d, entry use disk=%d]",
|
|
entry.get(), entryKey.get(), aStorage->WriteToDisk(), entry->UsingDisk()));
|
|
entries->Remove(entryKey);
|
|
}
|
|
else {
|
|
// Otherwise, leave it
|
|
LOG((" leaving entry %p for %s [storage use disk=%d, entry use disk=%d]",
|
|
entry.get(), entryKey.get(), aStorage->WriteToDisk(), entry->UsingDisk()));
|
|
entry = nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (entry) {
|
|
LOG((" dooming entry %p for %s", entry.get(), entryKey.get()));
|
|
return entry->AsyncDoom(aCallback);
|
|
}
|
|
|
|
LOG((" no entry loaded for %s", entryKey.get()));
|
|
|
|
if (aStorage->WriteToDisk()) {
|
|
nsAutoCString contextKey;
|
|
CacheFileUtils::CreateKeyPrefix(aStorage->LoadInfo(), contextKey);
|
|
|
|
rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, entryKey);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
LOG((" dooming file only for %s", entryKey.get()));
|
|
|
|
nsRefPtr<CacheEntryDoomByKeyCallback> callback(
|
|
new CacheEntryDoomByKeyCallback(aCallback));
|
|
rv = CacheFileIOManager::DoomFileByKey(entryKey, callback);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
if (aCallback)
|
|
aCallback->OnCacheEntryDoomed(NS_ERROR_NOT_AVAILABLE);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheStorageService::DoomStorageEntries(CacheStorage const* aStorage,
|
|
nsICacheEntryDoomCallback* aCallback)
|
|
{
|
|
LOG(("CacheStorageService::DoomStorageEntries"));
|
|
|
|
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
|
|
NS_ENSURE_ARG(aStorage);
|
|
|
|
nsAutoCString contextKey;
|
|
CacheFileUtils::CreateKeyPrefix(aStorage->LoadInfo(), contextKey);
|
|
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
return DoomStorageEntries(contextKey, aStorage->WriteToDisk(), aCallback);
|
|
}
|
|
|
|
nsresult
|
|
CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
|
|
bool aDiskStorage,
|
|
nsICacheEntryDoomCallback* aCallback)
|
|
{
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsAutoCString memoryStorageID(aContextKey);
|
|
AppendMemoryStorageID(memoryStorageID);
|
|
|
|
nsAutoPtr<CacheEntryTable> entries;
|
|
if (aDiskStorage) {
|
|
LOG((" dooming disk+memory storage of %s", aContextKey.BeginReading()));
|
|
// Grab all entries in this storage
|
|
sGlobalEntryTables->RemoveAndForget(aContextKey, entries);
|
|
// Just remove the memory-only records table
|
|
sGlobalEntryTables->Remove(memoryStorageID);
|
|
}
|
|
else {
|
|
LOG((" dooming memory-only storage of %s", aContextKey.BeginReading()));
|
|
// Grab the memory-only records table, EvictionRunnable will safely remove
|
|
// entries one by one from the master hashtable on the background management
|
|
// thread. Code at AddStorageEntry ensures a new entry will always replace
|
|
// memory only entries that EvictionRunnable yet didn't manage to remove.
|
|
sGlobalEntryTables->RemoveAndForget(memoryStorageID, entries);
|
|
}
|
|
|
|
nsRefPtr<EvictionRunnable> evict = new EvictionRunnable(
|
|
aContextKey, entries.forget(), aDiskStorage, aCallback);
|
|
|
|
return Dispatch(evict);
|
|
}
|
|
|
|
nsresult
|
|
CacheStorageService::WalkStorageEntries(CacheStorage const* aStorage,
|
|
bool aVisitEntries,
|
|
nsICacheStorageVisitor* aVisitor)
|
|
{
|
|
LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]", aVisitor, aVisitEntries));
|
|
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
NS_ENSURE_ARG(aStorage);
|
|
|
|
nsAutoCString contextKey;
|
|
CacheFileUtils::CreateKeyPrefix(aStorage->LoadInfo(), contextKey);
|
|
|
|
nsRefPtr<WalkRunnable> event = new WalkRunnable(
|
|
contextKey, aVisitEntries, aStorage->WriteToDisk(), aVisitor);
|
|
return Dispatch(event);
|
|
}
|
|
|
|
nsresult
|
|
CacheStorageService::CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
|
|
const nsACString & aURL)
|
|
{
|
|
nsRefPtr<CacheEntry> entry;
|
|
nsAutoCString contextKey;
|
|
CacheFileUtils::CreateKeyPrefix(aLoadContextInfo, contextKey);
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
CacheEntryTable* entries;
|
|
if (sGlobalEntryTables->Get(contextKey, &entries)) {
|
|
entries->Get(aURL, getter_AddRefs(entry));
|
|
}
|
|
}
|
|
|
|
if (entry && entry->IsFileDoomed()) {
|
|
entry->PurgeAndDoom();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsIMemoryReporter
|
|
|
|
size_t
|
|
CacheStorageService::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
|
|
{
|
|
CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
|
|
|
|
size_t n = 0;
|
|
// The elemets are referenced by sGlobalEntryTables and are reported from there
|
|
n += mFrecencyArray.SizeOfExcludingThis(mallocSizeOf);
|
|
// The elemets are referenced by sGlobalEntryTables and are reported from there
|
|
n += mExpirationArray.SizeOfExcludingThis(mallocSizeOf);
|
|
// Entries reported manually in CacheStorageService::CollectReports callback
|
|
if (sGlobalEntryTables) {
|
|
n += sGlobalEntryTables->SizeOfIncludingThis(nullptr, mallocSizeOf);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
size_t
|
|
CacheStorageService::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
|
|
{
|
|
return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
|
|
}
|
|
|
|
namespace { // anon
|
|
|
|
class ReportStorageMemoryData
|
|
{
|
|
public:
|
|
nsIMemoryReporterCallback *mHandleReport;
|
|
nsISupports *mData;
|
|
};
|
|
|
|
size_t CollectEntryMemory(nsACString const & aKey,
|
|
nsRefPtr<mozilla::net::CacheEntry> const & aEntry,
|
|
mozilla::MallocSizeOf mallocSizeOf,
|
|
void * aClosure)
|
|
{
|
|
CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
|
|
|
|
CacheEntryTable* aTable = static_cast<CacheEntryTable*>(aClosure);
|
|
|
|
size_t n = 0;
|
|
n += aKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
|
|
|
|
// Bypass memory-only entries, those will be reported when iterating
|
|
// the memory only table. Memory-only entries are stored in both ALL_ENTRIES
|
|
// and MEMORY_ONLY hashtables.
|
|
if (aTable->Type() == CacheEntryTable::MEMORY_ONLY || aEntry->UsingDisk())
|
|
n += aEntry->SizeOfIncludingThis(mallocSizeOf);
|
|
|
|
return n;
|
|
}
|
|
|
|
PLDHashOperator ReportStorageMemory(const nsACString& aKey,
|
|
CacheEntryTable* aTable,
|
|
void* aClosure)
|
|
{
|
|
CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
|
|
|
|
size_t size = aTable->SizeOfIncludingThis(&CollectEntryMemory,
|
|
CacheStorageService::MallocSizeOf,
|
|
aTable);
|
|
|
|
ReportStorageMemoryData& data = *static_cast<ReportStorageMemoryData*>(aClosure);
|
|
data.mHandleReport->Callback(
|
|
EmptyCString(),
|
|
nsPrintfCString("explicit/network/cache2/%s-storage(%s)",
|
|
aTable->Type() == CacheEntryTable::MEMORY_ONLY ? "memory" : "disk",
|
|
aKey.BeginReading()),
|
|
nsIMemoryReporter::KIND_HEAP,
|
|
nsIMemoryReporter::UNITS_BYTES,
|
|
size,
|
|
NS_LITERAL_CSTRING("Memory used by the cache storage."),
|
|
data.mData);
|
|
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
} // anon
|
|
|
|
NS_IMETHODIMP
|
|
CacheStorageService::CollectReports(nsIMemoryReporterCallback* aHandleReport, nsISupports* aData)
|
|
{
|
|
nsresult rv;
|
|
|
|
rv = MOZ_COLLECT_REPORT(
|
|
"explicit/network/cache2/io", KIND_HEAP, UNITS_BYTES,
|
|
CacheFileIOManager::SizeOfIncludingThis(MallocSizeOf),
|
|
"Memory used by the cache IO manager.");
|
|
if (NS_WARN_IF(NS_FAILED(rv)))
|
|
return rv;
|
|
|
|
rv = MOZ_COLLECT_REPORT(
|
|
"explicit/network/cache2/index", KIND_HEAP, UNITS_BYTES,
|
|
CacheIndex::SizeOfIncludingThis(MallocSizeOf),
|
|
"Memory used by the cache index.");
|
|
if (NS_WARN_IF(NS_FAILED(rv)))
|
|
return rv;
|
|
|
|
MutexAutoLock lock(mLock);
|
|
|
|
// Report the service instance, this doesn't report entries, done lower
|
|
rv = MOZ_COLLECT_REPORT(
|
|
"explicit/network/cache2/service", KIND_HEAP, UNITS_BYTES,
|
|
SizeOfIncludingThis(MallocSizeOf),
|
|
"Memory used by the cache storage service.");
|
|
if (NS_WARN_IF(NS_FAILED(rv)))
|
|
return rv;
|
|
|
|
// Report all entries, each storage separately (by the context key)
|
|
//
|
|
// References are:
|
|
// sGlobalEntryTables to N CacheEntryTable
|
|
// CacheEntryTable to N CacheEntry
|
|
// CacheEntry to 1 CacheFile
|
|
// CacheFile to
|
|
// N CacheFileChunk (keeping the actual data)
|
|
// 1 CacheFileMetadata (keeping http headers etc.)
|
|
// 1 CacheFileOutputStream
|
|
// N CacheFileInputStream
|
|
ReportStorageMemoryData data;
|
|
data.mHandleReport = aHandleReport;
|
|
data.mData = aData;
|
|
sGlobalEntryTables->EnumerateRead(&ReportStorageMemory, &data);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // net
|
|
} // mozilla
|