Bug 977766 - HTTP cache v2: selective deletion of data (by load context info), r=honzab, michal

This commit is contained in:
Michal Novotny 2014-04-10 12:47:20 +02:00
parent dad7119019
commit de7c98ded1
17 changed files with 1458 additions and 200 deletions

View File

@ -67,5 +67,13 @@ interface nsILoadContextInfo : nsISupports
GetIsAnonymous(&anon);
return anon;
}
bool Equals(nsILoadContextInfo *aOther)
{
return (IsPrivate() == aOther->IsPrivate() &&
AppId() == aOther->AppId() &&
IsInBrowserElement() == aOther->IsInBrowserElement() &&
IsAnonymous() == aOther->IsAnonymous());
}
%}
};

View File

@ -0,0 +1,605 @@
/* 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 "CacheFileContextEvictor.h"
#include "CacheFileIOManager.h"
#include "CacheIndex.h"
#include "CacheIndexIterator.h"
#include "CacheFileUtils.h"
#include "nsIFile.h"
#include "LoadContextInfo.h"
#include "nsThreadUtils.h"
#include "nsString.h"
#include "nsISimpleEnumerator.h"
#include "nsIDirectoryEnumerator.h"
#include "mozilla/Base64.h"
namespace mozilla {
namespace net {
const char kContextEvictionPrefix[] = "ce_";
const uint32_t kContextEvictionPrefixLength =
sizeof(kContextEvictionPrefix) - 1;
bool CacheFileContextEvictor::sDiskAlreadySearched = false;
CacheFileContextEvictor::CacheFileContextEvictor()
: mEvicting(false)
, mIndexIsUpToDate(false)
{
LOG(("CacheFileContextEvictor::CacheFileContextEvictor() [this=%p]", this));
}
CacheFileContextEvictor::~CacheFileContextEvictor()
{
LOG(("CacheFileContextEvictor::~CacheFileContextEvictor() [this=%p]", this));
}
nsresult
CacheFileContextEvictor::Init(nsIFile *aCacheDirectory)
{
LOG(("CacheFileContextEvictor::Init()"));
nsresult rv;
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
CacheIndex::IsUpToDate(&mIndexIsUpToDate);
mCacheDirectory = aCacheDirectory;
rv = aCacheDirectory->Clone(getter_AddRefs(mEntriesDir));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = mEntriesDir->AppendNative(NS_LITERAL_CSTRING(kEntriesDir));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!sDiskAlreadySearched) {
LoadEvictInfoFromDisk();
if ((mEntries.Length() != 0) && mIndexIsUpToDate) {
CreateIterators();
StartEvicting();
}
}
return NS_OK;
}
uint32_t
CacheFileContextEvictor::ContextsCount()
{
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
return mEntries.Length();
}
nsresult
CacheFileContextEvictor::AddContext(nsILoadContextInfo *aLoadContextInfo)
{
LOG(("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p]",
this, aLoadContextInfo));
nsresult rv;
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
CacheFileContextEvictorEntry *entry = nullptr;
for (uint32_t i = 0; i < mEntries.Length(); ++i) {
if (mEntries[i]->mInfo->Equals(aLoadContextInfo)) {
entry = mEntries[i];
break;
}
}
if (!entry) {
entry = new CacheFileContextEvictorEntry();
entry->mInfo = aLoadContextInfo;
mEntries.AppendElement(entry);
}
entry->mTimeStamp = PR_Now() / PR_USEC_PER_MSEC;
PersistEvictionInfoToDisk(aLoadContextInfo);
if (mIndexIsUpToDate) {
// Already existing context could be added again, in this case the iterator
// would be recreated. Close the old iterator explicitely.
if (entry->mIterator) {
entry->mIterator->Close();
entry->mIterator = nullptr;
}
rv = CacheIndex::GetIterator(aLoadContextInfo, false,
getter_AddRefs(entry->mIterator));
if (NS_FAILED(rv)) {
// This could probably happen during shutdown. Remove the entry from
// the array, but leave the info on the disk. No entry can be opened
// during shutdown and we'll load the eviction info on next start.
LOG(("CacheFileContextEvictor::AddContext() - Cannot get an iterator. "
"[rv=0x%08x]", rv));
mEntries.RemoveElement(entry);
return rv;
}
StartEvicting();
}
return NS_OK;
}
nsresult
CacheFileContextEvictor::CacheIndexStateChanged()
{
LOG(("CacheFileContextEvictor::CacheIndexStateChanged() [this=%p]", this));
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
bool isUpToDate = false;
CacheIndex::IsUpToDate(&isUpToDate);
if (mEntries.Length() == 0) {
// Just save the state and exit, since there is nothing to do
mIndexIsUpToDate = isUpToDate;
return NS_OK;
}
if (!isUpToDate && !mIndexIsUpToDate) {
// Index is outdated and status has not changed, nothing to do.
return NS_OK;
}
if (isUpToDate && mIndexIsUpToDate) {
// Status has not changed, but make sure the eviction is running.
if (mEvicting) {
return NS_OK;
}
// We're not evicting, but we should be evicting?!
LOG(("CacheFileContextEvictor::CacheIndexStateChanged() - Index is up to "
"date, we have some context to evict but eviction is not running! "
"Starting now."));
}
mIndexIsUpToDate = isUpToDate;
if (mIndexIsUpToDate) {
CreateIterators();
StartEvicting();
} else {
CloseIterators();
}
return NS_OK;
}
nsresult
CacheFileContextEvictor::WasEvicted(const nsACString &aKey, nsIFile *aFile,
bool *_retval)
{
LOG(("CacheFileContextEvictor::WasEvicted() [key=%s]",
PromiseFlatCString(aKey).get()));
nsresult rv;
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
MOZ_ASSERT(info);
if (!info) {
LOG(("CacheFileContextEvictor::WasEvicted() - Cannot parse key!"));
*_retval = false;
return NS_OK;
}
CacheFileContextEvictorEntry *entry = nullptr;
for (uint32_t i = 0; i < mEntries.Length(); ++i) {
if (info->Equals(mEntries[i]->mInfo)) {
entry = mEntries[i];
break;
}
}
if (!entry) {
LOG(("CacheFileContextEvictor::WasEvicted() - Didn't find equal context, "
"returning false."));
*_retval = false;
return NS_OK;
}
PRTime lastModifiedTime;
rv = aFile->GetLastModifiedTime(&lastModifiedTime);
if (NS_FAILED(rv)) {
LOG(("CacheFileContextEvictor::WasEvicted() - Cannot get last modified time"
", returning false."));
*_retval = false;
return NS_OK;
}
*_retval = !(lastModifiedTime > entry->mTimeStamp);
LOG(("CacheFileContextEvictor::WasEvicted() - returning %s. [mTimeStamp=%lld,"
" lastModifiedTime=%lld]", *_retval ? "true" : "false",
mEntries[0]->mTimeStamp, lastModifiedTime));
return NS_OK;
}
nsresult
CacheFileContextEvictor::PersistEvictionInfoToDisk(
nsILoadContextInfo *aLoadContextInfo)
{
LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() [this=%p, "
"loadContextInfo=%p]", this, aLoadContextInfo));
nsresult rv;
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
nsCOMPtr<nsIFile> file;
rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
#ifdef PR_LOGGING
nsAutoCString path;
file->GetNativePath(path);
#endif
PRFileDesc *fd;
rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600,
&fd);
if (NS_WARN_IF(NS_FAILED(rv))) {
LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Creating file "
"failed! [path=%s, rv=0x%08x]", path.get(), rv));
return rv;
}
PR_Close(fd);
LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Successfully "
"created file. [path=%s]", path.get()));
return NS_OK;
}
nsresult
CacheFileContextEvictor::RemoveEvictInfoFromDisk(
nsILoadContextInfo *aLoadContextInfo)
{
LOG(("CacheFileContextEvictor::RemoveEvictInfoFromDisk() [this=%p, "
"loadContextInfo=%p]", this, aLoadContextInfo));
nsresult rv;
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
nsCOMPtr<nsIFile> file;
rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
#ifdef PR_LOGGING
nsAutoCString path;
file->GetNativePath(path);
#endif
rv = file->Remove(false);
if (NS_WARN_IF(NS_FAILED(rv))) {
LOG(("CacheFileContextEvictor::RemoveEvictionInfoFromDisk() - Removing file"
" failed! [path=%s, rv=0x%08x]", path.get(), rv));
return rv;
}
LOG(("CacheFileContextEvictor::RemoveEvictionInfoFromDisk() - Successfully "
"removed file. [path=%s]", path.get()));
return NS_OK;
}
nsresult
CacheFileContextEvictor::LoadEvictInfoFromDisk()
{
LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() [this=%p]", this));
nsresult rv;
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
sDiskAlreadySearched = true;
nsCOMPtr<nsISimpleEnumerator> enumerator;
rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(enumerator));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIDirectoryEnumerator> dirEnum = do_QueryInterface(enumerator, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
while (true) {
nsCOMPtr<nsIFile> file;
rv = dirEnum->GetNextFile(getter_AddRefs(file));
if (!file) {
break;
}
bool isDir = false;
file->IsDirectory(&isDir);
if (isDir) {
continue;
}
nsAutoCString leaf;
rv = file->GetNativeLeafName(leaf);
if (NS_FAILED(rv)) {
LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - "
"GetNativeLeafName() failed! Skipping file."));
continue;
}
if (leaf.Length() < kContextEvictionPrefixLength) {
continue;
}
if (!StringBeginsWith(leaf, NS_LITERAL_CSTRING(kContextEvictionPrefix))) {
continue;
}
nsAutoCString encoded;
encoded = Substring(leaf, kContextEvictionPrefixLength);
encoded.ReplaceChar('-', '/');
nsAutoCString decoded;
rv = Base64Decode(encoded, decoded);
if (NS_FAILED(rv)) {
LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Base64 decoding "
"failed. Removing the file. [file=%s]", leaf.get()));
file->Remove(false);
continue;
}
nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(decoded);
if (!info) {
LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Cannot parse "
"context key, removing file. [contextKey=%s, file=%s]",
decoded.get(), leaf.get()));
file->Remove(false);
continue;
}
PRTime lastModifiedTime;
rv = file->GetLastModifiedTime(&lastModifiedTime);
if (NS_FAILED(rv)) {
continue;
}
CacheFileContextEvictorEntry *entry = new CacheFileContextEvictorEntry();
entry->mInfo = info;
entry->mTimeStamp = lastModifiedTime;
mEntries.AppendElement(entry);
}
return NS_OK;
}
nsresult
CacheFileContextEvictor::GetContextFile(nsILoadContextInfo *aLoadContextInfo,
nsIFile **_retval)
{
nsresult rv;
nsAutoCString leafName;
leafName.Assign(NS_LITERAL_CSTRING(kContextEvictionPrefix));
nsAutoCString keyPrefix;
CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
// TODO: This hack is needed because current CacheFileUtils::ParseKey() can
// parse only the whole key and not just the key prefix generated by
// CacheFileUtils::CreateKeyPrefix(). This should be removed once bug #968593
// is fixed.
keyPrefix.Append(":foo");
nsAutoCString data64;
rv = Base64Encode(keyPrefix, data64);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Replace '/' with '-' since '/' cannot be part of the filename.
data64.ReplaceChar('/', '-');
leafName.Append(data64);
nsCOMPtr<nsIFile> file;
rv = mCacheDirectory->Clone(getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = file->AppendNative(leafName);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
file.swap(*_retval);
return NS_OK;
}
void
CacheFileContextEvictor::CreateIterators()
{
LOG(("CacheFileContextEvictor::CreateIterators() [this=%p]", this));
CloseIterators();
nsresult rv;
for (uint32_t i = 0; i < mEntries.Length(); ) {
rv = CacheIndex::GetIterator(mEntries[i]->mInfo, false,
getter_AddRefs(mEntries[i]->mIterator));
if (NS_FAILED(rv)) {
LOG(("CacheFileContextEvictor::CreateIterators() - Cannot get an iterator"
". [rv=0x%08x]", rv));
mEntries.RemoveElementAt(i);
continue;
}
++i;
}
}
void
CacheFileContextEvictor::CloseIterators()
{
LOG(("CacheFileContextEvictor::CloseIterators() [this=%p]", this));
for (uint32_t i = 0; i < mEntries.Length(); ++i) {
if (mEntries[i]->mIterator) {
mEntries[i]->mIterator->Close();
mEntries[i]->mIterator = nullptr;
}
}
}
void
CacheFileContextEvictor::StartEvicting()
{
LOG(("CacheFileContextEvictor::StartEvicting() [this=%p]", this));
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
if (mEvicting) {
LOG(("CacheFileContextEvictor::StartEvicting() - already evicintg."));
return;
}
if (mEntries.Length() == 0) {
LOG(("CacheFileContextEvictor::StartEvicting() - no context to evict."));
return;
}
nsCOMPtr<nsIRunnable> ev;
ev = NS_NewRunnableMethod(this, &CacheFileContextEvictor::EvictEntries);
nsRefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
nsresult rv = ioThread->Dispatch(ev, CacheIOThread::EVICT);
if (NS_FAILED(rv)) {
LOG(("CacheFileContextEvictor::StartEvicting() - Cannot dispatch event to "
"IO thread. [rv=0x%08x]", rv));
}
mEvicting = true;
}
nsresult
CacheFileContextEvictor::EvictEntries()
{
LOG(("CacheFileContextEvictor::EvictEntries()"));
nsresult rv;
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
mEvicting = false;
if (!mIndexIsUpToDate) {
LOG(("CacheFileContextEvictor::EvictEntries() - Stopping evicting due to "
"outdated index."));
return NS_OK;
}
while (true) {
if (CacheIOThread::YieldAndRerun()) {
LOG(("CacheFileContextEvictor::EvictEntries() - Breaking loop for higher "
"level events."));
mEvicting = true;
return NS_OK;
}
if (mEntries.Length() == 0) {
LOG(("CacheFileContextEvictor::EvictEntries() - Stopping evicting, there "
"is no context to evict."));
return NS_OK;
}
SHA1Sum::Hash hash;
rv = mEntries[0]->mIterator->GetNextHash(&hash);
if (rv == NS_ERROR_NOT_AVAILABLE) {
LOG(("CacheFileContextEvictor::EvictEntries() - No more entries left in "
"iterator. [iterator=%p, info=%p]", mEntries[0]->mIterator.get(),
mEntries[0]->mInfo.get()));
RemoveEvictInfoFromDisk(mEntries[0]->mInfo);
mEntries.RemoveElementAt(0);
continue;
} else if (NS_FAILED(rv)) {
LOG(("CacheFileContextEvictor::EvictEntries() - Iterator failed to "
"provide next hash (shutdown?), keeping eviction info on disk."
" [iterator=%p, info=%p]", mEntries[0]->mIterator.get(),
mEntries[0]->mInfo.get()));
mEntries.RemoveElementAt(0);
continue;
}
LOG(("CacheFileContextEvictor::EvictEntries() - Processing hash. "
"[hash=%08x%08x%08x%08x%08x, iterator=%p, info=%p]", LOGSHA1(&hash),
mEntries[0]->mIterator.get(), mEntries[0]->mInfo.get()));
nsRefPtr<CacheFileHandle> handle;
CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false,
getter_AddRefs(handle));
if (handle) {
// We doom any active handle in CacheFileIOManager::EvictByContext(), so
// this must be a new one. Skip it.
LOG(("CacheFileContextEvictor::EvictEntries() - Skipping entry since we "
"found an active handle. [handle=%p]", handle.get()));
continue;
}
nsAutoCString leafName;
CacheFileIOManager::HashToStr(&hash, leafName);
PRTime lastModifiedTime;
nsCOMPtr<nsIFile> file;
rv = mEntriesDir->Clone(getter_AddRefs(file));
if (NS_SUCCEEDED(rv)) {
rv = file->AppendNative(leafName);
}
if (NS_SUCCEEDED(rv)) {
rv = file->GetLastModifiedTime(&lastModifiedTime);
}
if (NS_FAILED(rv)) {
LOG(("CacheFileContextEvictor::EvictEntries() - Cannot get last modified "
"time, skipping entry."));
continue;
}
if (lastModifiedTime > mEntries[0]->mTimeStamp) {
LOG(("CacheFileContextEvictor::EvictEntries() - Skipping newer entry. "
"[mTimeStamp=%lld, lastModifiedTime=%lld]", mEntries[0]->mTimeStamp,
lastModifiedTime));
continue;
}
LOG(("CacheFileContextEvictor::EvictEntries - Removing entry."));
file->Remove(false);
CacheIndex::RemoveEntry(&hash);
}
NS_NOTREACHED("We should never get here");
return NS_OK;
}
} // net
} // mozilla

View File

@ -0,0 +1,91 @@
/* 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/. */
#ifndef CacheFileContextEvictor__h__
#define CacheFileContextEvictor__h__
#include "nsTArray.h"
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
class nsIFile;
class nsILoadContextInfo;
namespace mozilla {
namespace net {
class CacheIndexIterator;
struct CacheFileContextEvictorEntry
{
nsCOMPtr<nsILoadContextInfo> mInfo;
PRTime mTimeStamp; // in milliseconds
nsRefPtr<CacheIndexIterator> mIterator;
};
class CacheFileContextEvictor
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheFileContextEvictor)
CacheFileContextEvictor();
virtual ~CacheFileContextEvictor();
nsresult Init(nsIFile *aCacheDirectory);
// Returns number of contexts that are being evicted.
uint32_t ContextsCount();
// Start evicting given context.
nsresult AddContext(nsILoadContextInfo *aLoadContextInfo);
// CacheFileIOManager calls this method when CacheIndex's state changes. We
// check whether the index is up to date and start or stop evicting according
// to index's state.
nsresult CacheIndexStateChanged();
// CacheFileIOManager calls this method to check whether an entry file should
// be considered as evicted. It returns true when there is a matching context
// info to the given key and the last modified time of the entry file is
// earlier than the time stamp of the time when the context was added to the
// evictor.
nsresult WasEvicted(const nsACString &aKey, nsIFile *aFile, bool *_retval);
private:
// Writes information about eviction of the given context to the disk. This is
// done for every context added to the evictor to be able to recover eviction
// after a shutdown or crash. When the context file is found after startup, we
// restore mTimeStamp from the last modified time of the file.
nsresult PersistEvictionInfoToDisk(nsILoadContextInfo *aLoadContextInfo);
// Once we are done with eviction for the given context, the eviction info is
// removed from the disk.
nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo *aLoadContextInfo);
// Tries to load all contexts from the disk. This method is called just once
// after startup.
nsresult LoadEvictInfoFromDisk();
nsresult GetContextFile(nsILoadContextInfo *aLoadContextInfo,
nsIFile **_retval);
void CreateIterators();
void CloseIterators();
void StartEvicting();
nsresult EvictEntries();
// Whether eviction is in progress
bool mEvicting;
// Whether index is up to date. We wait with eviction until the index finishes
// update process when it is outdated. NOTE: We also stop eviction in progress
// when the index is found outdated, the eviction is restarted again once the
// update process finishes.
bool mIndexIsUpToDate;
// Whether we already tried to restore unfinished jobs from previous run after
// startup.
static bool sDiskAlreadySearched;
// Array of contexts being evicted.
nsTArray<nsAutoPtr<CacheFileContextEvictorEntry> > mEntries;
nsCOMPtr<nsIFile> mCacheDirectory;
nsCOMPtr<nsIFile> mEntriesDir;
};
} // net
} // mozilla
#endif

View File

@ -14,6 +14,7 @@
#include "CacheFile.h"
#include "CacheObserver.h"
#include "nsIFile.h"
#include "CacheFileContextEvictor.h"
#include "nsITimer.h"
#include "nsISimpleEnumerator.h"
#include "nsIDirectoryEnumerator.h"
@ -593,7 +594,7 @@ public:
mRV = mIOMan->OpenSpecialFileInternal(mKey, mFlags,
getter_AddRefs(mHandle));
} else {
mRV = mIOMan->OpenFileInternal(&mHash, mFlags,
mRV = mIOMan->OpenFileInternal(&mHash, mKey, mFlags,
getter_AddRefs(mHandle));
}
mIOMan = nullptr;
@ -1548,11 +1549,13 @@ CacheFileIOManager::OpenFile(const nsACString &aKey,
nsresult
CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
const nsACString &aKey,
uint32_t aFlags,
CacheFileHandle **_retval)
{
LOG(("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, "
"flags=%d]", LOGSHA1(aHash), aFlags));
"key=%s, flags=%d]", LOGSHA1(aHash), PromiseFlatCString(aKey).get(),
aFlags));
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
@ -1615,6 +1618,22 @@ CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
rv = file->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (exists && mContextEvictor) {
if (mContextEvictor->ContextsCount() == 0) {
mContextEvictor = nullptr;
} else {
bool wasEvicted = false;
mContextEvictor->WasEvicted(aKey, file, &wasEvicted);
if (wasEvicted) {
LOG(("CacheFileIOManager::OpenFileInternal() - Removing file since the "
"entry was evicted by EvictByContext()"));
exists = false;
file->Remove(false);
CacheIndex::RemoveEntry(aHash);
}
}
}
if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
return NS_ERROR_NOT_AVAILABLE;
}
@ -2564,7 +2583,8 @@ CacheFileIOManager::EvictAllInternal()
for (uint32_t i = 0; i < handles.Length(); ++i) {
rv = DoomFileInternal(handles[i]);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
LOG(("CacheFileIOManager::EvictAllInternal() - Cannot doom handle "
"[handle=%p]", handles[i].get()));
}
}
@ -2599,6 +2619,144 @@ CacheFileIOManager::EvictAllInternal()
return NS_OK;
}
// static
nsresult
CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo)
{
LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
aLoadContextInfo));
nsresult rv;
nsRefPtr<CacheFileIOManager> ioMan = gInstance;
if (!ioMan) {
return NS_ERROR_NOT_INITIALIZED;
}
nsCOMPtr<nsIRunnable> ev;
ev = NS_NewRunnableMethodWithArg<nsCOMPtr<nsILoadContextInfo> >
(ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo);
rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult
CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo)
{
LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, "
"anonymous=%u, inBrowser=%u, appId=%u]", aLoadContextInfo,
aLoadContextInfo->IsAnonymous(), aLoadContextInfo->IsInBrowserElement(),
aLoadContextInfo->AppId()));
nsresult rv;
MOZ_ASSERT(mIOThread->IsCurrentThread());
MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
if (aLoadContextInfo->IsPrivate()) {
return NS_ERROR_INVALID_ARG;
}
if (!mCacheDirectory) {
return NS_ERROR_FILE_INVALID_PATH;
}
if (mShuttingDown) {
return NS_ERROR_NOT_INITIALIZED;
}
if (!mTreeCreated) {
rv = CreateCacheTree();
if (NS_FAILED(rv)) {
return rv;
}
}
// Doom all active handles that matches the load context
nsTArray<nsRefPtr<CacheFileHandle> > handles;
mHandles.GetActiveHandles(&handles);
for (uint32_t i = 0; i < handles.Length(); ++i) {
bool equals;
rv = CacheFileUtils::KeyMatchesLoadContextInfo(handles[i]->Key(),
aLoadContextInfo,
&equals);
if (NS_FAILED(rv)) {
LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
"handle! [handle=%p, key=%s]", handles[i].get(),
handles[i]->Key().get()));
MOZ_CRASH("Unexpected error!");
}
if (equals) {
rv = DoomFileInternal(handles[i]);
if (NS_WARN_IF(NS_FAILED(rv))) {
LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
" [handle=%p]", handles[i].get()));
}
}
}
if (!mContextEvictor) {
mContextEvictor = new CacheFileContextEvictor();
mContextEvictor->Init(mCacheDirectory);
}
mContextEvictor->AddContext(aLoadContextInfo);
return NS_OK;
}
// static
nsresult
CacheFileIOManager::CacheIndexStateChanged()
{
LOG(("CacheFileIOManager::CacheIndexStateChanged()"));
nsresult rv;
// CacheFileIOManager lives longer than CacheIndex so gInstance must be
// non-null here.
MOZ_ASSERT(gInstance);
// We have to re-distatch even if we are on IO thread to prevent reentering
// the lock in CacheIndex
nsCOMPtr<nsIRunnable> ev;
ev = NS_NewRunnableMethod(
gInstance, &CacheFileIOManager::CacheIndexStateChangedInternal);
nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
MOZ_ASSERT(ioTarget);
rv = ioTarget->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult
CacheFileIOManager::CacheIndexStateChangedInternal()
{
if (mShuttingDown) {
// ignore notification during shutdown
return NS_OK;
}
if (!mContextEvictor) {
return NS_OK;
}
mContextEvictor->CacheIndexStateChanged();
return NS_OK;
}
nsresult
CacheFileIOManager::TrashDirectory(nsIFile *aFile)
{
@ -3231,6 +3389,18 @@ CacheFileIOManager::CreateCacheTree()
mTreeCreated = true;
if (!mContextEvictor) {
nsRefPtr<CacheFileContextEvictor> contextEvictor;
contextEvictor = new CacheFileContextEvictor();
// Init() method will try to load unfinished contexts from the disk. Store
// the evictor as a member only when there is some unfinished job.
contextEvictor->Init(mCacheDirectory);
if (contextEvictor->ContextsCount()) {
contextEvictor.swap(mContextEvictor);
}
}
StartRemovingTrash();
return NS_OK;

View File

@ -21,6 +21,7 @@
class nsIFile;
class nsITimer;
class nsIDirectoryEnumerator;
class nsILoadContextInfo;
namespace mozilla {
namespace net {
@ -174,6 +175,7 @@ class CloseFileEvent;
class ReadEvent;
class WriteEvent;
class MetadataWriteScheduleEvent;
class CacheFileContextEvictor;
#define CACHEFILEIOLISTENER_IID \
{ /* dcaf2ddc-17cf-4242-bca1-8c86936375a5 */ \
@ -257,6 +259,7 @@ public:
CacheFileIOListener *aCallback);
static nsresult EvictIfOverLimit();
static nsresult EvictAll();
static nsresult EvictByContext(nsILoadContextInfo *aLoadContextInfo);
static nsresult InitIndexEntry(CacheFileHandle *aHandle,
uint32_t aAppId,
@ -295,6 +298,7 @@ private:
friend class RenameFileEvent;
friend class CacheIndex;
friend class MetadataWriteScheduleEvent;
friend class CacheFileContextEvictor;
virtual ~CacheFileIOManager();
@ -302,6 +306,7 @@ private:
nsresult ShutdownInternal();
nsresult OpenFileInternal(const SHA1Sum::Hash *aHash,
const nsACString &aKey,
uint32_t aFlags,
CacheFileHandle **_retval);
nsresult OpenSpecialFileInternal(const nsACString &aKey,
@ -322,6 +327,7 @@ private:
nsresult EvictIfOverLimitInternal();
nsresult OverLimitEvictionInternal();
nsresult EvictAllInternal();
nsresult EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo);
nsresult TrashDirectory(nsIFile *aFile);
static void OnTrashTimer(nsITimer *aTimer, void *aClosure);
@ -350,6 +356,9 @@ private:
nsresult UnscheduleMetadataWriteInternal(CacheFile * aFile);
nsresult ShutdownMetadataWriteSchedulingInternal();
static nsresult CacheIndexStateChanged();
nsresult CacheIndexStateChangedInternal();
// Memory reporting (private part)
size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
@ -370,6 +379,7 @@ private:
nsCOMPtr<nsIFile> mTrashDir;
nsCOMPtr<nsIDirectoryEnumerator> mTrashDirEnumerator;
nsTArray<nsCString> mFailedTrashDirs;
nsRefPtr<CacheFileContextEvictor> mContextEvictor;
};
} // net

View File

@ -271,6 +271,20 @@ AppendTagWithValue(nsACString & aTarget, char const aTag, nsCSubstring const & a
aTarget.Append(',');
}
nsresult
KeyMatchesLoadContextInfo(const nsACString &aKey, nsILoadContextInfo *aInfo,
bool *_retval)
{
nsCOMPtr<nsILoadContextInfo> info = ParseKey(aKey);
if (!info) {
return NS_ERROR_FAILURE;
}
*_retval = info->Equals(aInfo);
return NS_OK;
}
} // CacheFileUtils
} // net
} // mozilla

View File

@ -27,6 +27,11 @@ AppendKeyPrefix(nsILoadContextInfo *aInfo, nsACString &_retval);
void
AppendTagWithValue(nsACString & aTarget, char const aTag, nsCSubstring const & aValue);
nsresult
KeyMatchesLoadContextInfo(const nsACString &aKey,
nsILoadContextInfo *aInfo,
bool *_retval);
} // CacheFileUtils
} // net
} // mozilla

View File

@ -7,6 +7,8 @@
#include "CacheLog.h"
#include "CacheFileIOManager.h"
#include "CacheFileMetadata.h"
#include "CacheIndexIterator.h"
#include "CacheIndexContextIterator.h"
#include "nsThreadUtils.h"
#include "nsISimpleEnumerator.h"
#include "nsIDirectoryEnumerator.h"
@ -73,9 +75,11 @@ public:
if (entry && !mOldRecord) {
mIndex->InsertRecordToFrecencyArray(entry->mRec);
mIndex->InsertRecordToExpirationArray(entry->mRec);
mIndex->AddRecordToIterators(entry->mRec);
} else if (!entry && mOldRecord) {
mIndex->RemoveRecordFromFrecencyArray(mOldRecord);
mIndex->RemoveRecordFromExpirationArray(mOldRecord);
mIndex->RemoveRecordFromIterators(mOldRecord);
} else if (entry && mOldRecord) {
bool replaceFrecency = false;
bool replaceExpiration = false;
@ -83,14 +87,14 @@ public:
if (entry->mRec != mOldRecord) {
// record has a different address, we have to replace it
replaceFrecency = replaceExpiration = true;
mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec);
} else {
if (entry->mRec->mFrecency == 0 &&
entry->mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME) {
// This is a special case when we want to make sure that the entry is
// placed at the end of the lists even when the values didn't change.
replaceFrecency = replaceExpiration = true;
}
else {
} else {
if (entry->mRec->mFrecency != mOldFrecency) {
replaceFrecency = true;
}
@ -157,70 +161,6 @@ private:
bool mDoNotSearchInUpdates;
};
class CacheIndexAutoLock {
public:
CacheIndexAutoLock(CacheIndex *aIndex)
: mIndex(aIndex)
, mLocked(true)
{
mIndex->Lock();
}
~CacheIndexAutoLock()
{
if (mLocked) {
mIndex->Unlock();
}
}
void Lock()
{
MOZ_ASSERT(!mLocked);
mIndex->Lock();
mLocked = true;
}
void Unlock()
{
MOZ_ASSERT(mLocked);
mIndex->Unlock();
mLocked = false;
}
private:
nsRefPtr<CacheIndex> mIndex;
bool mLocked;
};
class CacheIndexAutoUnlock {
public:
CacheIndexAutoUnlock(CacheIndex *aIndex)
: mIndex(aIndex)
, mLocked(false)
{
mIndex->Unlock();
}
~CacheIndexAutoUnlock()
{
if (!mLocked) {
mIndex->Lock();
}
}
void Lock()
{
MOZ_ASSERT(!mLocked);
mIndex->Lock();
mLocked = true;
}
void Unlock()
{
MOZ_ASSERT(mLocked);
mIndex->Unlock();
mLocked = false;
}
private:
nsRefPtr<CacheIndex> mIndex;
bool mLocked;
};
class FileOpenHelper : public CacheFileIOListener
{
public:
@ -330,7 +270,7 @@ CacheIndex::~CacheIndex()
ReleaseBuffer();
}
inline void
void
CacheIndex::Lock()
{
mLock.Lock();
@ -338,7 +278,7 @@ CacheIndex::Lock()
MOZ_ASSERT(!mIndexStats.StateLogged());
}
inline void
void
CacheIndex::Unlock()
{
MOZ_ASSERT(!mIndexStats.StateLogged());
@ -411,6 +351,18 @@ CacheIndex::PreShutdown()
"dontMarkIndexClean=%d]", index->mState, index->mIndexOnDiskIsValid,
index->mDontMarkIndexClean));
LOG(("CacheIndex::PreShutdown() - Closing iterators."));
for (uint32_t i = 0; i < index->mIterators.Length(); ) {
rv = index->mIterators[i]->CloseInternal(NS_ERROR_FAILURE);
if (NS_FAILED(rv)) {
// CacheIndexIterator::CloseInternal() removes itself from mIteratos iff
// it returns success.
LOG(("CacheIndex::PreShutdown() - Failed to remove iterator %p. "
"[rv=0x%08x]", rv));
i++;
}
}
index->mShuttingDown = true;
if (index->mState == READY) {
@ -1291,6 +1243,64 @@ CacheIndex::AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserve
return NS_OK;
}
// static
nsresult
CacheIndex::GetIterator(nsILoadContextInfo *aInfo, bool aAddNew,
CacheIndexIterator **_retval)
{
LOG(("CacheIndex::GetIterator() [info=%p, addNew=%d]", aInfo, aAddNew));
nsRefPtr<CacheIndex> index = gInstance;
if (!index) {
return NS_ERROR_NOT_INITIALIZED;
}
CacheIndexAutoLock lock(index);
if (!index->IsIndexUsable()) {
return NS_ERROR_NOT_AVAILABLE;
}
nsRefPtr<CacheIndexIterator> iter;
if (aInfo) {
iter = new CacheIndexContextIterator(index, aAddNew, aInfo);
} else {
iter = new CacheIndexIterator(index, aAddNew);
}
iter->AddRecords(index->mFrecencyArray);
index->mIterators.AppendElement(iter);
iter.swap(*_retval);
return NS_OK;
}
// static
nsresult
CacheIndex::IsUpToDate(bool *_retval)
{
LOG(("CacheIndex::IsUpToDate()"));
nsRefPtr<CacheIndex> index = gInstance;
if (!index) {
return NS_ERROR_NOT_INITIALIZED;
}
CacheIndexAutoLock lock(index);
if (!index->IsIndexUsable()) {
return NS_ERROR_NOT_AVAILABLE;
}
*_retval = (index->mState == READY || index->mState == WRITING) &&
!index->mIndexNeedsUpdate && !index->mShuttingDown;
LOG(("CacheIndex::IsUpToDate() - returning %p", *_retval));
return NS_OK;
}
bool
CacheIndex::IsIndexUsable()
{
@ -3012,6 +3022,10 @@ CacheIndex::ChangeState(EState aNewState)
mState = aNewState;
if (mState != SHUTDOWN) {
CacheFileIOManager::CacheIndexStateChanged();
}
if (mState == READY && mDiskConsumptionObservers.Length()) {
for (uint32_t i = 0; i < mDiskConsumptionObservers.Length(); ++i) {
DiskConsumptionObserver* o = mDiskConsumptionObservers[i];
@ -3130,6 +3144,48 @@ CacheIndex::RemoveRecordFromExpirationArray(CacheIndexRecord *aRecord)
MOZ_ASSERT(removed);
}
void
CacheIndex::AddRecordToIterators(CacheIndexRecord *aRecord)
{
AssertOwnsLock();
for (uint32_t i = 0; i < mIterators.Length(); ++i) {
// Add a new record only when iterator is supposed to be updated.
if (mIterators[i]->ShouldBeNewAdded()) {
mIterators[i]->AddRecord(aRecord);
}
}
}
void
CacheIndex::RemoveRecordFromIterators(CacheIndexRecord *aRecord)
{
AssertOwnsLock();
for (uint32_t i = 0; i < mIterators.Length(); ++i) {
// Remove the record from iterator always, it makes no sence to return
// non-existing entries. Also the pointer to the record is no longer valid
// once the entry is removed from index.
mIterators[i]->RemoveRecord(aRecord);
}
}
void
CacheIndex::ReplaceRecordInIterators(CacheIndexRecord *aOldRecord,
CacheIndexRecord *aNewRecord)
{
AssertOwnsLock();
for (uint32_t i = 0; i < mIterators.Length(); ++i) {
// We have to replace the record always since the pointer is no longer
// valid after this point. NOTE: Replacing the record doesn't mean that
// a new entry was added, it just means that the data in the entry was
// changed (e.g. a file size) and we had to track this change in
// mPendingUpdates since mIndex was read-only.
mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord);
}
}
nsresult
CacheIndex::Run()
{

View File

@ -34,6 +34,7 @@ namespace net {
class CacheFileMetadata;
class FileOpenHelper;
class CacheIndexIterator;
typedef struct {
// Version of the index. The index must be ignored and deleted when the file
@ -250,6 +251,19 @@ public:
GetExpirationTime(), GetFileSize()));
}
static bool RecordMatchesLoadContextInfo(CacheIndexRecord *aRec,
nsILoadContextInfo *aInfo)
{
if (!aInfo->IsPrivate() &&
aInfo->AppId() == aRec->mAppId &&
aInfo->IsAnonymous() == !!(aRec->mFlags & kAnonymousMask) &&
aInfo->IsInBrowserElement() == !!(aRec->mFlags & kInBrowserMask)) {
return true;
}
return false;
}
// Memory reporting
size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
{
@ -552,6 +566,18 @@ public:
// Asynchronously gets the disk cache size, used for display in the UI.
static nsresult AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserver);
// Returns an iterator that returns entries matching a given context that were
// present in the index at the time this method was called. If aAddNew is true
// then the iterator will also return entries created after this call.
// NOTE: When some entry is removed from index it is removed also from the
// iterator regardless what aAddNew was passed.
static nsresult GetIterator(nsILoadContextInfo *aInfo, bool aAddNew,
CacheIndexIterator **_retval);
// Returns true if we _think_ that the index is up to date. I.e. the state is
// READY or WRITING and mIndexNeedsUpdate as well as mShuttingDown is false.
static nsresult IsUpToDate(bool *_retval);
// Memory reporting
static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
@ -561,6 +587,7 @@ private:
friend class CacheIndexAutoLock;
friend class CacheIndexAutoUnlock;
friend class FileOpenHelper;
friend class CacheIndexIterator;
virtual ~CacheIndex();
@ -799,6 +826,12 @@ private:
void RemoveRecordFromFrecencyArray(CacheIndexRecord *aRecord);
void RemoveRecordFromExpirationArray(CacheIndexRecord *aRecord);
// Methods used by CacheIndexEntryAutoManage to keep the iterators up to date.
void AddRecordToIterators(CacheIndexRecord *aRecord);
void RemoveRecordFromIterators(CacheIndexRecord *aRecord);
void ReplaceRecordInIterators(CacheIndexRecord *aOldRecord,
CacheIndexRecord *aNewRecord);
// Memory reporting (private part)
size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
@ -902,6 +935,8 @@ private:
nsTArray<CacheIndexRecord *> mFrecencyArray;
nsTArray<CacheIndexRecord *> mExpirationArray;
nsTArray<CacheIndexIterator *> mIterators;
class DiskConsumptionObserver : public nsRunnable
{
public:
@ -947,6 +982,69 @@ private:
nsTArray<nsRefPtr<DiskConsumptionObserver> > mDiskConsumptionObservers;
};
class CacheIndexAutoLock {
public:
CacheIndexAutoLock(CacheIndex *aIndex)
: mIndex(aIndex)
, mLocked(true)
{
mIndex->Lock();
}
~CacheIndexAutoLock()
{
if (mLocked) {
mIndex->Unlock();
}
}
void Lock()
{
MOZ_ASSERT(!mLocked);
mIndex->Lock();
mLocked = true;
}
void Unlock()
{
MOZ_ASSERT(mLocked);
mIndex->Unlock();
mLocked = false;
}
private:
nsRefPtr<CacheIndex> mIndex;
bool mLocked;
};
class CacheIndexAutoUnlock {
public:
CacheIndexAutoUnlock(CacheIndex *aIndex)
: mIndex(aIndex)
, mLocked(false)
{
mIndex->Unlock();
}
~CacheIndexAutoUnlock()
{
if (!mLocked) {
mIndex->Lock();
}
}
void Lock()
{
MOZ_ASSERT(!mLocked);
mIndex->Lock();
mLocked = true;
}
void Unlock()
{
MOZ_ASSERT(mLocked);
mIndex->Unlock();
mLocked = false;
}
private:
nsRefPtr<CacheIndex> mIndex;
bool mLocked;
};
} // net
} // mozilla

View File

@ -0,0 +1,45 @@
/* 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 "CacheIndexContextIterator.h"
#include "CacheIndex.h"
#include "nsString.h"
namespace mozilla {
namespace net {
CacheIndexContextIterator::CacheIndexContextIterator(CacheIndex *aIndex,
bool aAddNew,
nsILoadContextInfo *aInfo)
: CacheIndexIterator(aIndex, aAddNew)
, mInfo(aInfo)
{
}
CacheIndexContextIterator::~CacheIndexContextIterator()
{
}
void
CacheIndexContextIterator::AddRecord(CacheIndexRecord *aRecord)
{
if (CacheIndexEntry::RecordMatchesLoadContextInfo(aRecord, mInfo)) {
CacheIndexIterator::AddRecord(aRecord);
}
}
void
CacheIndexContextIterator::AddRecords(
const nsTArray<CacheIndexRecord *> &aRecords)
{
// We need to add one by one so that those with wrong context are ignored.
for (uint32_t i = 0; i < aRecords.Length(); ++i) {
AddRecord(aRecords[i]);
}
}
} // net
} // mozilla

View File

@ -0,0 +1,32 @@
/* 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/. */
#ifndef CacheIndexContextIterator__h__
#define CacheIndexContextIterator__h__
#include "CacheIndexIterator.h"
class nsILoadContextInfo;
namespace mozilla {
namespace net {
class CacheIndexContextIterator : public CacheIndexIterator
{
public:
CacheIndexContextIterator(CacheIndex *aIndex, bool aAddNew,
nsILoadContextInfo *aInfo);
virtual ~CacheIndexContextIterator();
private:
virtual void AddRecord(CacheIndexRecord *aRecord);
virtual void AddRecords(const nsTArray<CacheIndexRecord *> &aRecords);
nsCOMPtr<nsILoadContextInfo> mInfo;
};
} // net
} // mozilla
#endif

View File

@ -0,0 +1,126 @@
/* 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 "CacheIndexIterator.h"
#include "CacheIndex.h"
#include "nsString.h"
#include "mozilla/DebugOnly.h"
namespace mozilla {
namespace net {
CacheIndexIterator::CacheIndexIterator(CacheIndex *aIndex, bool aAddNew)
: mStatus(NS_OK)
, mIndex(aIndex)
, mAddNew(aAddNew)
{
LOG(("CacheIndexIterator::CacheIndexIterator() [this=%p]", this));
}
CacheIndexIterator::~CacheIndexIterator()
{
LOG(("CacheIndexIterator::~CacheIndexIterator() [this=%p]", this));
Close();
}
nsresult
CacheIndexIterator::GetNextHash(SHA1Sum::Hash *aHash)
{
LOG(("CacheIndexIterator::GetNextHash() [this=%p]", this));
CacheIndexAutoLock lock(mIndex);
if (NS_FAILED(mStatus)) {
return mStatus;
}
if (!mRecords.Length()) {
CloseInternal(NS_ERROR_NOT_AVAILABLE);
return mStatus;
}
memcpy(aHash, mRecords[mRecords.Length() - 1]->mHash, sizeof(SHA1Sum::Hash));
mRecords.RemoveElementAt(mRecords.Length() - 1);
return NS_OK;
}
nsresult
CacheIndexIterator::Close()
{
LOG(("CacheIndexIterator::Close() [this=%p]", this));
CacheIndexAutoLock lock(mIndex);
return CloseInternal(NS_ERROR_NOT_AVAILABLE);
}
nsresult
CacheIndexIterator::CloseInternal(nsresult aStatus)
{
LOG(("CacheIndexIterator::CloseInternal() [this=%p, status=0x%08x]", this,
aStatus));
// Make sure status will be a failure
MOZ_ASSERT(NS_FAILED(aStatus));
if (NS_SUCCEEDED(aStatus)) {
aStatus = NS_ERROR_UNEXPECTED;
}
if (NS_FAILED(mStatus)) {
return NS_ERROR_NOT_AVAILABLE;
}
DebugOnly<bool> removed = mIndex->mIterators.RemoveElement(this);
MOZ_ASSERT(removed);
mStatus = aStatus;
return NS_OK;
}
void
CacheIndexIterator::AddRecord(CacheIndexRecord *aRecord)
{
LOG(("CacheIndexIterator::AddRecord() [this=%p, record=%p]", this, aRecord));
mRecords.AppendElement(aRecord);
}
void
CacheIndexIterator::AddRecords(const nsTArray<CacheIndexRecord *> &aRecords)
{
LOG(("CacheIndexIterator::AddRecords() [this=%p]", this));
mRecords.AppendElements(aRecords);
}
bool
CacheIndexIterator::RemoveRecord(CacheIndexRecord *aRecord)
{
LOG(("CacheIndexIterator::RemoveRecord() [this=%p, record=%p]", this,
aRecord));
return mRecords.RemoveElement(aRecord);
}
bool
CacheIndexIterator::ReplaceRecord(CacheIndexRecord *aOldRecord,
CacheIndexRecord *aNewRecord)
{
LOG(("CacheIndexIterator::ReplaceRecord() [this=%p, oldRecord=%p, "
"newRecord=%p]", this, aOldRecord, aNewRecord));
if (RemoveRecord(aOldRecord)) {
AddRecord(aNewRecord);
return true;
}
return false;
}
} // net
} // mozilla

View File

@ -0,0 +1,57 @@
/* 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/. */
#ifndef CacheIndexIterator__h__
#define CacheIndexIterator__h__
#include "nsTArray.h"
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "mozilla/SHA1.h"
namespace mozilla {
namespace net {
class CacheIndex;
struct CacheIndexRecord;
class CacheIndexIterator
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheIndexIterator)
CacheIndexIterator(CacheIndex *aIndex, bool aAddNew);
virtual ~CacheIndexIterator();
// Returns a hash of a next entry. If there is no entry NS_ERROR_NOT_AVAILABLE
// is returned and the iterator is closed. Other error is returned when the
// iterator is closed for other reason, e.g. shutdown.
nsresult GetNextHash(SHA1Sum::Hash *aHash);
// Closes the iterator. This means the iterator is removed from the list of
// iterators in CacheIndex.
nsresult Close();
protected:
friend class CacheIndex;
nsresult CloseInternal(nsresult aStatus);
bool ShouldBeNewAdded() { return mAddNew; }
virtual void AddRecord(CacheIndexRecord *aRecord);
virtual void AddRecords(const nsTArray<CacheIndexRecord *> &aRecords);
bool RemoveRecord(CacheIndexRecord *aRecord);
bool ReplaceRecord(CacheIndexRecord *aOldRecord,
CacheIndexRecord *aNewRecord);
nsresult mStatus;
nsRefPtr<CacheIndex> mIndex;
nsTArray<CacheIndexRecord *> mRecords;
bool mAddNew;
};
} // net
} // mozilla
#endif

View File

@ -163,97 +163,6 @@ void CacheStorageService::ShutdownBackground()
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
@ -402,7 +311,7 @@ void CacheStorageService::DropPrivateBrowsingEntries()
sGlobalEntryTables->EnumerateRead(&CollectPrivateContexts, &keys);
for (uint32_t i = 0; i < keys.Length(); ++i)
DoomStorageEntries(keys[i], true, nullptr);
DoomStorageEntries(keys[i], nullptr, true, nullptr);
}
// static
@ -536,7 +445,7 @@ NS_IMETHODIMP CacheStorageService::Clear()
sGlobalEntryTables->EnumerateRead(&CollectContexts, &keys);
for (uint32_t i = 0; i < keys.Length(); ++i)
DoomStorageEntries(keys[i], true, nullptr);
DoomStorageEntries(keys[i], nullptr, true, nullptr);
}
rv = CacheFileIOManager::EvictAll();
@ -1174,31 +1083,14 @@ CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey,
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->IsUsingDiskLocked() && !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
// No need to remove from both memory-only and all-entries tables. The new entry
// will overwrite the shadow entry in its ctor.
entry->DoomAlreadyRemoved();
entry = nullptr;
@ -1358,11 +1250,13 @@ CacheStorageService::DoomStorageEntries(CacheStorage const* aStorage,
mozilla::MutexAutoLock lock(mLock);
return DoomStorageEntries(contextKey, aStorage->WriteToDisk(), aCallback);
return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
aStorage->WriteToDisk(), aCallback);
}
nsresult
CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
nsILoadContextInfo* aContext,
bool aDiskStorage,
nsICacheEntryDoomCallback* aCallback)
{
@ -1373,27 +1267,70 @@ CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
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
// Just remove all entries, CacheFileIOManager will take care of the files.
sGlobalEntryTables->Remove(aContextKey);
sGlobalEntryTables->Remove(memoryStorageID);
}
else {
if (aContext && !aContext->IsPrivate()) {
LOG((" dooming disk entries"));
CacheFileIOManager::EvictByContext(aContext);
}
} 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);
class MemoryEntriesRemoval {
public:
static PLDHashOperator EvictEntry(const nsACString& aKey,
CacheEntry* aEntry,
void* aClosure)
{
CacheEntryTable* entries = static_cast<CacheEntryTable*>(aClosure);
nsCString key(aKey);
RemoveExactEntry(entries, key, aEntry, false);
return PL_DHASH_NEXT;
}
};
// Remove the memory entries table from the global tables.
// Since we store memory entries also in the disk entries table
// we need to remove the memory entries from the disk table one
// by one manually.
nsAutoPtr<CacheEntryTable> memoryEntries;
sGlobalEntryTables->RemoveAndForget(memoryStorageID, memoryEntries);
CacheEntryTable* entries;
sGlobalEntryTables->Get(aContextKey, &entries);
if (memoryEntries && entries)
memoryEntries->EnumerateRead(&MemoryEntriesRemoval::EvictEntry, entries);
}
nsRefPtr<EvictionRunnable> evict = new EvictionRunnable(
aContextKey, entries.forget(), aDiskStorage, aCallback);
// An artificial callback. This is a candidate for removal tho. In the new
// cache any 'doom' or 'evict' function ensures that the entry or entries
// being doomed is/are not accessible after the function returns. So there is
// probably no need for a callback - has no meaning. But for compatibility
// with the old cache that is still in the tree we keep the API similar to be
// able to make tests as well as other consumers work for now.
class Callback : public nsRunnable
{
public:
Callback(nsICacheEntryDoomCallback* aCallback) : mCallback(aCallback) { }
NS_IMETHODIMP Run()
{
mCallback->OnCacheEntryDoomed(NS_OK);
return NS_OK;
}
nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
};
return Dispatch(evict);
if (aCallback) {
nsRefPtr<nsRunnable> callback = new Callback(aCallback);
return NS_DispatchToCurrentThread(callback);
}
return NS_OK;
}
nsresult

View File

@ -205,6 +205,7 @@ private:
private:
nsresult DoomStorageEntries(nsCSubstring const& aContextKey,
nsILoadContextInfo* aContext,
bool aDiskStorage,
nsICacheEntryDoomCallback* aCallback);
nsresult AddStorageEntry(nsCSubstring const& aContextKey,

View File

@ -34,12 +34,15 @@ SOURCES += [
'CacheEntry.cpp',
'CacheFile.cpp',
'CacheFileChunk.cpp',
'CacheFileContextEvictor.cpp',
'CacheFileInputStream.cpp',
'CacheFileIOManager.cpp',
'CacheFileMetadata.cpp',
'CacheFileOutputStream.cpp',
'CacheFileUtils.cpp',
'CacheIndex.cpp',
'CacheIndexContextIterator.cpp',
'CacheIndexIterator.cpp',
'CacheLog.cpp',
'CacheStorage.cpp',
'CacheStorageService.cpp',

View File

@ -48,9 +48,9 @@ function run_test()
);
asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
new OpenCallback(NEW, "a1m", "a1d", function(entry) {
new OpenCallback(NEW, "b1m", "b1d", function(entry) {
asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
new OpenCallback(NORMAL, "b1m", "b1d", function(entry) {
mc.fired();
})
);