Bug 1032254 - Generic way to pin reasource in the HTTP cache, r=michal

* * *
Bug NNNNNNN - message, r=reviewer

--HG--
rename : netwerk/test/unit/test_cache2-28-concurrent_read_resumable_entry_size_zero.js => netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js
rename : netwerk/test/unit/test_cache2-29-concurrent_read_non-resumable_entry_size_zero.js => netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js
This commit is contained in:
Honza Bambas 2015-10-22 12:11:00 +02:00
Родитель e0436217a8
Коммит 8f88fd6cdc
38 изменённых файлов: 1219 добавлений и 267 удалений

Просмотреть файл

@ -17,7 +17,7 @@ interface nsIFile;
* 3) Support for uniquely identifying cached data in cases when the URL
* is insufficient (e.g., HTTP form submission).
*/
[scriptable, uuid(436b939d-e391-48e5-ba64-ab0e496e3400)]
[scriptable, uuid(dd1d6122-5ecf-4fe4-8f0f-995e7ab3121a)]
interface nsICachingChannel : nsICacheInfoChannel
{
/**
@ -53,6 +53,11 @@ interface nsICachingChannel : nsICacheInfoChannel
*/
attribute boolean cacheOnlyMetadata;
/**
* Tells the channel to use the pinning storage.
*/
attribute boolean pin;
/**************************************************************************
* Caching channel specific load flags:
*/

Просмотреть файл

@ -25,7 +25,7 @@ NS_IMPL_ISUPPORTS_INHERITED0(AppCacheStorage, CacheStorage)
AppCacheStorage::AppCacheStorage(nsILoadContextInfo* aInfo,
nsIApplicationCache* aAppCache)
: CacheStorage(aInfo, true /* disk */, false /* lookup app cache */, false /* skip size check */)
: CacheStorage(aInfo, true /* disk */, false /* lookup app cache */, false /* skip size check */, false /* pin */)
, mAppCache(aAppCache)
{
MOZ_COUNT_CTOR(AppCacheStorage);

Просмотреть файл

@ -87,6 +87,8 @@ CacheEntry::Callback::Callback(CacheEntry* aEntry,
, mRecheckAfterWrite(false)
, mNotWanted(false)
, mSecret(aSecret)
, mDoomWhenFoundPinned(false)
, mDoomWhenFoundNonPinned(false)
{
MOZ_COUNT_CTOR(CacheEntry::Callback);
@ -96,6 +98,22 @@ CacheEntry::Callback::Callback(CacheEntry* aEntry,
mEntry->AddHandleRef();
}
CacheEntry::Callback::Callback(CacheEntry* aEntry, bool aDoomWhenFoundInPinStatus)
: mEntry(aEntry)
, mReadOnly(false)
, mRevalidating(false)
, mCheckOnAnyThread(true)
, mRecheckAfterWrite(false)
, mNotWanted(false)
, mSecret(false)
, mDoomWhenFoundPinned(aDoomWhenFoundInPinStatus == true)
, mDoomWhenFoundNonPinned(aDoomWhenFoundInPinStatus == false)
{
MOZ_COUNT_CTOR(CacheEntry::Callback);
MOZ_ASSERT(mEntry->HandlesCount());
mEntry->AddHandleRef();
}
CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
: mEntry(aThat.mEntry)
, mCallback(aThat.mCallback)
@ -106,6 +124,8 @@ CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
, mRecheckAfterWrite(aThat.mRecheckAfterWrite)
, mNotWanted(aThat.mNotWanted)
, mSecret(aThat.mSecret)
, mDoomWhenFoundPinned(aThat.mDoomWhenFoundPinned)
, mDoomWhenFoundNonPinned(aThat.mDoomWhenFoundNonPinned)
{
MOZ_COUNT_CTOR(CacheEntry::Callback);
@ -136,6 +156,20 @@ void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry)
mEntry = aEntry;
}
bool CacheEntry::Callback::DeferDoom(bool *aDoom) const
{
MOZ_ASSERT(mEntry->mPinningKnown);
if (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) || MOZ_UNLIKELY(mDoomWhenFoundPinned)) {
*aDoom = (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && MOZ_LIKELY(!mEntry->mPinned)) ||
(MOZ_UNLIKELY(mDoomWhenFoundPinned) && MOZ_UNLIKELY(mEntry->mPinned));
return true;
}
return false;
}
nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const
{
if (!mCheckOnAnyThread) {
@ -164,7 +198,8 @@ CacheEntry::CacheEntry(const nsACString& aStorageID,
nsIURI* aURI,
const nsACString& aEnhanceID,
bool aUseDisk,
bool aSkipSizeCheck)
bool aSkipSizeCheck,
bool aPin)
: mFrecency(0)
, mSortingExpirationTime(uint32_t(-1))
, mLock("CacheEntry")
@ -174,10 +209,12 @@ CacheEntry::CacheEntry(const nsACString& aStorageID,
, mStorageID(aStorageID)
, mUseDisk(aUseDisk)
, mSkipSizeCheck(aSkipSizeCheck)
, mIsDoomed(false)
, mSecurityInfoLoaded(false)
, mPreventCallbacks(false)
, mHasData(false)
, mPinned(aPin)
, mPinningKnown(false)
, mIsDoomed(false)
, mState(NOTLOADED)
, mRegistration(NEVERREGISTERED)
, mWriter(nullptr)
@ -350,16 +387,18 @@ bool CacheEntry::Load(bool aTruncate, bool aPriority)
if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) {
switch (status) {
case CacheIndex::DOES_NOT_EXIST:
LOG((" entry doesn't exist according information from the index, truncating"));
// Doesn't apply to memory-only entries, Load() is called only once for them
// and never again for their session lifetime.
if (!aTruncate && mUseDisk) {
LOG((" entry doesn't exist according information from the index, truncating"));
reportMiss = true;
aTruncate = true;
}
aTruncate = true;
break;
case CacheIndex::EXISTS:
case CacheIndex::DO_NOT_KNOW:
if (!mUseDisk) {
LOG((" entry open as memory-only, but there is (status=%d) a file, dooming it", status));
LOG((" entry open as memory-only, but there is a file, status=%d, dooming it", status));
CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
}
break;
@ -376,6 +415,7 @@ bool CacheEntry::Load(bool aTruncate, bool aPriority)
// mLoadStart will be used to calculate telemetry of life-time of this entry.
// Low resulution is then enough.
mLoadStart = TimeStamp::NowLoRes();
mPinningKnown = true;
} else {
mLoadStart = TimeStamp::Now();
}
@ -395,6 +435,7 @@ bool CacheEntry::Load(bool aTruncate, bool aPriority)
!mUseDisk,
mSkipSizeCheck,
aPriority,
mPinned,
directLoad ? nullptr : this);
}
@ -432,9 +473,10 @@ NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
}
// OnFileReady, that is the only code that can transit from LOADING
// to any follow-on state, can only be invoked ones on an entry,
// thus no need to lock. Until this moment there is no consumer that
// could manipulate the entry state.
// to any follow-on state and can only be invoked ones on an entry.
// Until this moment there is no consumer that could manipulate
// the entry state.
mozilla::MutexAutoLock lock(mLock);
MOZ_ASSERT(mState == LOADING);
@ -445,6 +487,10 @@ NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
mFileStatus = aResult;
mPinned = mFile->IsPinned();;
mPinningKnown = true;
LOG((" pinning=%d", mPinned));
if (mState == READY) {
mHasData = true;
@ -456,6 +502,7 @@ NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
}
InvokeCallbacks();
return NS_OK;
}
@ -483,6 +530,13 @@ already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(bool aMemoryOnly,
RefPtr<CacheEntryHandle> handle;
RefPtr<CacheEntry> newEntry;
{
if (mPinned) {
MOZ_ASSERT(mUseDisk);
// We want to pin even no-store entries (the case we recreate a disk entry as
// a memory-only entry.)
aMemoryOnly = false;
}
mozilla::MutexAutoUnlock unlock(mLock);
// The following call dooms this entry (calls DoomAlreadyRemoved on us)
@ -490,6 +544,7 @@ already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(bool aMemoryOnly,
GetStorageID(), GetURI(), GetEnhanceID(),
mUseDisk && !aMemoryOnly,
mSkipSizeCheck,
mPinned,
true, // always create
true, // truncate existing (this one)
getter_AddRefs(handle));
@ -577,6 +632,8 @@ bool CacheEntry::InvokeCallbacks(bool aReadOnly)
{
mLock.AssertCurrentThreadOwns();
RefPtr<CacheEntryHandle> recreatedHandle;
uint32_t i = 0;
while (i < mCallbacks.Length()) {
if (mPreventCallbacks) {
@ -589,6 +646,18 @@ bool CacheEntry::InvokeCallbacks(bool aReadOnly)
return false;
}
bool recreate;
if (mCallbacks[i].DeferDoom(&recreate)) {
mCallbacks.RemoveElementAt(i);
if (!recreate) {
continue;
}
LOG((" defer doom marker callback hit positive, recreating"));
recreatedHandle = ReopenTruncated(!mUseDisk, nullptr);
break;
}
if (mCallbacks[i].mReadOnly != aReadOnly) {
// Callback is not r/w or r/o, go to another one in line
++i;
@ -625,6 +694,12 @@ bool CacheEntry::InvokeCallbacks(bool aReadOnly)
}
}
if (recreatedHandle) {
// Must be released outside of the lock, enters InvokeCallback on the new entry
mozilla::MutexAutoUnlock unlock(mLock);
recreatedHandle = nullptr;
}
return true;
}
@ -991,6 +1066,13 @@ NS_IMETHODIMP CacheEntry::GetIsForcedValid(bool *aIsForcedValid)
{
NS_ENSURE_ARG(aIsForcedValid);
MOZ_ASSERT(mState > LOADING);
if (mPinned) {
*aIsForcedValid = true;
return NS_OK;
}
nsAutoCString key;
nsresult rv = HashingKeyWithStorage(key);
@ -1442,6 +1524,28 @@ void CacheEntry::SetRegistered(bool aRegistered)
}
}
bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned)
{
LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this));
mozilla::MutexAutoLock lock(mLock);
if (mPinningKnown) {
LOG((" pinned=%d, caller=%d", mPinned, aPinned));
// Bypass when the pin status of this entry doesn't match the pin status
// caller wants to remove
return mPinned != aPinned;
}
LOG((" pinning unknown, caller=%d", aPinned));
// Oterwise, remember to doom after the status is determined for any
// callback opening the entry after this point...
Callback c(this, aPinned);
RememberCallback(c);
// ...and always bypass
return true;
}
bool CacheEntry::Purge(uint32_t aWhat)
{
LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
@ -1523,6 +1627,10 @@ void CacheEntry::DoomAlreadyRemoved()
mIsDoomed = true;
// Pretend pinning is know. This entry is now doomed for good, so don't
// bother with defering doom because of unknown pinning state any more.
mPinningKnown = true;
// This schedules dooming of the file, dooming is ensured to happen
// sooner than demand to open the same file made after this point
// so that we don't get this file for any newer opened entry(s).

Просмотреть файл

@ -55,7 +55,7 @@ public:
NS_DECL_NSIRUNNABLE
CacheEntry(const nsACString& aStorageID, nsIURI* aURI, const nsACString& aEnhanceID,
bool aUseDisk, bool aSkipSizeCheck);
bool aUseDisk, bool aSkipSizeCheck, bool aPin);
void AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags);
@ -92,6 +92,7 @@ public:
PURGE_WHOLE,
};
bool DeferOrBypassRemovalOnPinStatus(bool aPinned);
bool Purge(uint32_t aWhat);
void PurgeAndDoom();
void DoomAlreadyRemoved();
@ -136,6 +137,9 @@ private:
Callback(CacheEntry* aEntry,
nsICacheEntryOpenCallback *aCallback,
bool aReadOnly, bool aCheckOnAnyThread, bool aSecret);
// Special constructor for Callback objects added to the chain
// just to ensure proper defer dooming (recreation) of this entry.
Callback(CacheEntry* aEntry, bool aDoomWhenFoundInPinStatus);
Callback(Callback const &aThat);
~Callback();
@ -143,6 +147,10 @@ private:
// mainly during recreation.
void ExchangeEntry(CacheEntry* aEntry);
// Returns true when an entry is about to be "defer" doomed and this is
// a "defer" callback.
bool DeferDoom(bool *aDoom) const;
// We are raising reference count here to take into account the pending
// callback (that virtually holds a ref to this entry before it gets
// it's pointer).
@ -156,6 +164,13 @@ private:
bool mNotWanted : 1;
bool mSecret : 1;
// These are set only for the defer-doomer Callback instance inserted
// to the callback chain. When any of these is set and also any of
// the corressponding flags on the entry is set, this callback will
// indicate (via DeferDoom()) the entry have to be recreated/doomed.
bool mDoomWhenFoundPinned : 1;
bool mDoomWhenFoundNonPinned : 1;
nsresult OnCheckThread(bool *aOnCheckThread) const;
nsresult OnAvailThread(bool *aOnAvailThread) const;
};
@ -274,14 +289,9 @@ private:
nsCString mStorageID;
// Whether it's allowed to persist the data to disk
bool const mUseDisk;
bool const mUseDisk : 1;
// Whether it should skip max size check.
bool const mSkipSizeCheck;
// Set when entry is doomed with AsyncDoom() or DoomAlreadyRemoved().
// Left as a standalone flag to not bother with locking (there is no need).
bool mIsDoomed;
bool const mSkipSizeCheck : 1;
// Following flags are all synchronized with the cache entry lock.
@ -296,6 +306,15 @@ private:
// false: after load and a new file, or dropped to back to false when a writer
// fails to open an output stream.
bool mHasData : 1;
// The indication of pinning this entry was open with
bool mPinned : 1;
// Whether the pinning state of the entry is known (equals to the actual state
// of the cache file)
bool mPinningKnown : 1;
// Set when entry is doomed with AsyncDoom() or DoomAlreadyRemoved().
// Left as a standalone flag to not bother with locking (there is no need).
bool mIsDoomed;
static char const * StateString(uint32_t aState);

Просмотреть файл

@ -102,7 +102,7 @@ protected:
nsCOMPtr<CacheFileChunkListener> mCallback;
nsresult mRV;
uint32_t mChunkIdx;
RefPtr<CacheFileChunk> mChunk;
RefPtr<CacheFileChunk> mChunk;
};
@ -185,6 +185,7 @@ CacheFile::CacheFile()
, mMemoryOnly(false)
, mSkipSizeCheck(false)
, mOpenAsMemoryOnly(false)
, mPinned(false)
, mPriority(false)
, mDataAccessed(false)
, mDataIsDirty(false)
@ -215,17 +216,21 @@ CacheFile::Init(const nsACString &aKey,
bool aMemoryOnly,
bool aSkipSizeCheck,
bool aPriority,
bool aPinned,
CacheFileListener *aCallback)
{
MOZ_ASSERT(!mListener);
MOZ_ASSERT(!mHandle);
MOZ_ASSERT(!(aMemoryOnly && aPinned));
nsresult rv;
mKey = aKey;
mOpenAsMemoryOnly = mMemoryOnly = aMemoryOnly;
mSkipSizeCheck = aSkipSizeCheck;
mPriority = aPriority;
mPinned = aPinned;
// Some consumers (at least nsHTTPCompressConv) assume that Read() can read
// such amount of data that was announced by Available().
@ -244,7 +249,7 @@ CacheFile::Init(const nsACString &aKey,
if (mMemoryOnly) {
MOZ_ASSERT(!aCallback);
mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, false, mKey);
mReady = true;
mDataSize = mMetadata->Offset();
return NS_OK;
@ -256,7 +261,7 @@ CacheFile::Init(const nsACString &aKey,
flags = CacheFileIOManager::CREATE_NEW;
// make sure we can use this entry immediately
mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey);
mReady = true;
mDataSize = mMetadata->Offset();
} else {
@ -267,6 +272,10 @@ CacheFile::Init(const nsACString &aKey,
flags |= CacheFileIOManager::PRIORITY;
}
if (mPinned) {
flags |= CacheFileIOManager::PINNED;
}
mOpeningFile = true;
mListener = aCallback;
rv = CacheFileIOManager::OpenFile(mKey, flags, this);
@ -274,6 +283,12 @@ CacheFile::Init(const nsACString &aKey,
mListener = nullptr;
mOpeningFile = false;
if (mPinned) {
LOG(("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
"but we want to pin, fail the file opening. [this=%p]", this));
return NS_ERROR_NOT_AVAILABLE;
}
if (aCreateNew) {
NS_WARNING("Forcing memory-only entry since OpenFile failed");
LOG(("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
@ -289,7 +304,7 @@ CacheFile::Init(const nsACString &aKey,
"initializing entry as memory-only. [this=%p]", this));
mMemoryOnly = true;
mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey);
mReady = true;
mDataSize = mMetadata->Offset();
@ -482,7 +497,8 @@ CacheFile::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
autoDoom.mAlreadyDoomed = true;
return NS_OK;
}
else if (NS_FAILED(aResult)) {
if (NS_FAILED(aResult)) {
if (mMetadata) {
// This entry was initialized as createNew, just switch to memory-only
// mode.
@ -494,7 +510,8 @@ CacheFile::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
mMemoryOnly = true;
return NS_OK;
}
else if (aResult == NS_ERROR_FILE_INVALID_PATH) {
if (aResult == NS_ERROR_FILE_INVALID_PATH) {
// CacheFileIOManager doesn't have mCacheDirectory, switch to
// memory-only mode.
NS_WARNING("Forcing memory-only entry since CacheFileIOManager doesn't "
@ -504,22 +521,20 @@ CacheFile::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
this));
mMemoryOnly = true;
mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey);
mReady = true;
mDataSize = mMetadata->Offset();
isNew = true;
retval = NS_OK;
}
else {
} else {
// CacheFileIOManager::OpenFile() failed for another reason.
isNew = false;
retval = aResult;
}
mListener.swap(listener);
}
else {
} else {
mHandle = aHandle;
if (NS_FAILED(mStatus)) {
CacheFileIOManager::DoomFile(mHandle, nullptr);
@ -583,6 +598,7 @@ CacheFile::OnMetadataRead(nsresult aResult)
bool isNew = false;
if (NS_SUCCEEDED(aResult)) {
mPinned = mMetadata->Pinned();
mReady = true;
mDataSize = mMetadata->Offset();
if (mDataSize == 0 && mMetadata->ElementsSize() == 0) {
@ -1970,7 +1986,8 @@ CacheFile::InitIndexEntry()
rv = CacheFileIOManager::InitIndexEntry(mHandle,
mMetadata->OriginAttributes().mAppId,
mMetadata->IsAnonymous(),
mMetadata->OriginAttributes().mInBrowser);
mMetadata->OriginAttributes().mInBrowser,
mPinned);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t expTime;

Просмотреть файл

@ -58,6 +58,7 @@ public:
bool aMemoryOnly,
bool aSkipSizeCheck,
bool aPriority,
bool aPinned,
CacheFileListener *aCallback);
NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) override;
@ -103,6 +104,7 @@ public:
bool DataSize(int64_t* aSize);
void Key(nsACString& aKey) { aKey = mKey; }
bool IsDoomed();
bool IsPinned() const { return mPinned; }
bool IsWriteInProgress();
// Memory reporting
@ -196,6 +198,7 @@ private:
bool mMemoryOnly;
bool mSkipSizeCheck;
bool mOpenAsMemoryOnly;
bool mPinned;
bool mPriority;
bool mDataAccessed;
bool mDataIsDirty;
@ -206,8 +209,8 @@ private:
int64_t mDataSize;
nsCString mKey;
RefPtr<CacheFileHandle> mHandle;
RefPtr<CacheFileMetadata> mMetadata;
RefPtr<CacheFileHandle> mHandle;
RefPtr<CacheFileMetadata> mMetadata;
nsCOMPtr<CacheFileListener> mListener;
nsCOMPtr<CacheFileIOListener> mDoomAfterOpenListener;

Просмотреть файл

@ -44,7 +44,7 @@ public:
protected:
nsCOMPtr<CacheFileChunkListener> mCallback;
RefPtr<CacheFileChunk> mChunk;
RefPtr<CacheFileChunk> mChunk;
};
bool

Просмотреть файл

@ -150,7 +150,7 @@ private:
uint32_t mRWBufSize;
CacheHash::Hash16_t mReadHash;
RefPtr<CacheFile> mFile; // is null if chunk is cached to
RefPtr<CacheFile> mFile; // is null if chunk is cached to
// prevent reference cycles
nsCOMPtr<CacheFileChunkListener> mListener;
nsTArray<ChunkListenerItem *> mUpdateListeners;

Просмотреть файл

@ -81,32 +81,49 @@ CacheFileContextEvictor::ContextsCount()
}
nsresult
CacheFileContextEvictor::AddContext(nsILoadContextInfo *aLoadContextInfo)
CacheFileContextEvictor::AddContext(nsILoadContextInfo *aLoadContextInfo,
bool aPinned)
{
LOG(("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p]",
this, aLoadContextInfo));
LOG(("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p, pinned=%d]",
this, aLoadContextInfo, aPinned));
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 (aLoadContextInfo) {
for (uint32_t i = 0; i < mEntries.Length(); ++i) {
if (mEntries[i]->mInfo &&
mEntries[i]->mInfo->Equals(aLoadContextInfo) &&
mEntries[i]->mPinned == aPinned) {
entry = mEntries[i];
break;
}
}
} else {
// Not providing load context info means we want to delete everything,
// so let's not bother with any currently running context cleanups
// for the same pinning state.
for (uint32_t i = mEntries.Length(); i > 0;) {
--i;
if (mEntries[i]->mInfo && mEntries[i]->mPinned == aPinned) {
RemoveEvictInfoFromDisk(mEntries[i]->mInfo, mEntries[i]->mPinned);
mEntries.RemoveElementAt(i);
}
}
}
if (!entry) {
entry = new CacheFileContextEvictorEntry();
entry->mInfo = aLoadContextInfo;
entry->mPinned = aPinned;
mEntries.AppendElement(entry);
}
entry->mTimeStamp = PR_Now() / PR_USEC_PER_MSEC;
PersistEvictionInfoToDisk(aLoadContextInfo);
PersistEvictionInfoToDisk(aLoadContextInfo, aPinned);
if (mIndexIsUpToDate) {
// Already existing context could be added again, in this case the iterator
@ -180,58 +197,62 @@ CacheFileContextEvictor::CacheIndexStateChanged()
nsresult
CacheFileContextEvictor::WasEvicted(const nsACString &aKey, nsIFile *aFile,
bool *_retval)
bool *aEvictedAsPinned, bool *aEvictedAsNonPinned)
{
LOG(("CacheFileContextEvictor::WasEvicted() [key=%s]",
PromiseFlatCString(aKey).get()));
nsresult rv;
*aEvictedAsPinned = false;
*aEvictedAsNonPinned = false;
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;
CacheFileContextEvictorEntry *entry = mEntries[i];
if (entry->mInfo && !info->Equals(entry->mInfo)) {
continue;
}
PRTime lastModifiedTime;
rv = aFile->GetLastModifiedTime(&lastModifiedTime);
if (NS_FAILED(rv)) {
LOG(("CacheFileContextEvictor::WasEvicted() - Cannot get last modified time"
", returning false."));
return NS_OK;
}
if (lastModifiedTime > entry->mTimeStamp) {
// File has been modified since context eviction.
continue;
}
LOG(("CacheFileContextEvictor::WasEvicted() - evicted [pinning=%d, "
"mTimeStamp=%lld, lastModifiedTime=%lld]",
entry->mPinned, entry->mTimeStamp, lastModifiedTime));
if (entry->mPinned) {
*aEvictedAsPinned = true;
} else {
*aEvictedAsNonPinned = true;
}
}
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)
nsILoadContextInfo *aLoadContextInfo, bool aPinned)
{
LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() [this=%p, "
"loadContextInfo=%p]", this, aLoadContextInfo));
@ -241,7 +262,7 @@ CacheFileContextEvictor::PersistEvictionInfoToDisk(
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
nsCOMPtr<nsIFile> file;
rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
rv = GetContextFile(aLoadContextInfo, aPinned, getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -268,7 +289,7 @@ CacheFileContextEvictor::PersistEvictionInfoToDisk(
nsresult
CacheFileContextEvictor::RemoveEvictInfoFromDisk(
nsILoadContextInfo *aLoadContextInfo)
nsILoadContextInfo *aLoadContextInfo, bool aPinned)
{
LOG(("CacheFileContextEvictor::RemoveEvictInfoFromDisk() [this=%p, "
"loadContextInfo=%p]", this, aLoadContextInfo));
@ -278,7 +299,7 @@ CacheFileContextEvictor::RemoveEvictInfoFromDisk(
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
nsCOMPtr<nsIFile> file;
rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
rv = GetContextFile(aLoadContextInfo, aPinned, getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -363,16 +384,26 @@ CacheFileContextEvictor::LoadEvictInfoFromDisk()
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;
bool pinned = decoded[0] == '\t';
if (pinned) {
decoded = Substring(decoded, 1);
}
nsCOMPtr<nsILoadContextInfo> info;
if (!NS_LITERAL_CSTRING("*").Equals(decoded)) {
// "*" is indication of 'delete all', info left null will pass
// to CacheFileContextEvictor::AddContext and clear all the cache data.
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)) {
@ -381,6 +412,7 @@ CacheFileContextEvictor::LoadEvictInfoFromDisk()
CacheFileContextEvictorEntry *entry = new CacheFileContextEvictorEntry();
entry->mInfo = info;
entry->mPinned = pinned;
entry->mTimeStamp = lastModifiedTime;
mEntries.AppendElement(entry);
}
@ -390,6 +422,7 @@ CacheFileContextEvictor::LoadEvictInfoFromDisk()
nsresult
CacheFileContextEvictor::GetContextFile(nsILoadContextInfo *aLoadContextInfo,
bool aPinned,
nsIFile **_retval)
{
nsresult rv;
@ -398,7 +431,16 @@ CacheFileContextEvictor::GetContextFile(nsILoadContextInfo *aLoadContextInfo,
leafName.AssignLiteral(CONTEXT_EVICTION_PREFIX);
nsAutoCString keyPrefix;
CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
if (aPinned) {
// Mark pinned context files with a tab char at the start.
// Tab is chosen because it can never be used as a context key tag.
keyPrefix.Append('\t');
}
if (aLoadContextInfo) {
CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
} else {
keyPrefix.Append('*');
}
nsAutoCString data64;
rv = Base64Encode(keyPrefix, data64);
@ -530,7 +572,7 @@ CacheFileContextEvictor::EvictEntries()
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);
RemoveEvictInfoFromDisk(mEntries[0]->mInfo, mEntries[0]->mPinned);
mEntries.RemoveElementAt(0);
continue;
} else if (NS_FAILED(rv)) {
@ -557,6 +599,20 @@ CacheFileContextEvictor::EvictEntries()
continue;
}
CacheIndex::EntryStatus status;
bool pinned;
rv = CacheIndex::HasEntry(hash, &status, &pinned);
// This must never fail, since eviction (this code) happens only when the index
// is up-to-date and thus the informatin is known.
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (pinned != mEntries[0]->mPinned) {
LOG(("CacheFileContextEvictor::EvictEntries() - Skipping entry since pinning "
"doesn't match [evicting pinned=%d, entry pinned=%d]",
mEntries[0]->mPinned, pinned));
continue;
}
nsAutoCString leafName;
CacheFileIOManager::HashToStr(&hash, leafName);

Просмотреть файл

@ -20,6 +20,7 @@ class CacheIndexIterator;
struct CacheFileContextEvictorEntry
{
nsCOMPtr<nsILoadContextInfo> mInfo;
bool mPinned;
PRTime mTimeStamp; // in milliseconds
RefPtr<CacheIndexIterator> mIterator;
};
@ -40,7 +41,7 @@ public:
// Returns number of contexts that are being evicted.
uint32_t ContextsCount();
// Start evicting given context.
nsresult AddContext(nsILoadContextInfo *aLoadContextInfo);
nsresult AddContext(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
// 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.
@ -50,21 +51,22 @@ public:
// 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);
nsresult WasEvicted(const nsACString &aKey, nsIFile *aFile,
bool *aEvictedAsPinned, bool *aEvictedAsNonPinned);
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);
nsresult PersistEvictionInfoToDisk(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
// Once we are done with eviction for the given context, the eviction info is
// removed from the disk.
nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo *aLoadContextInfo);
nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
// Tries to load all contexts from the disk. This method is called just once
// after startup.
nsresult LoadEvictInfoFromDisk();
nsresult GetContextFile(nsILoadContextInfo *aLoadContextInfo,
nsresult GetContextFile(nsILoadContextInfo *aLoadContextInfo, bool aPinned,
nsIFile **_retval);
void CreateIterators();

Просмотреть файл

@ -108,13 +108,16 @@ NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END_THREADSAFE
CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority)
CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority, PinningStatus aPinning)
: mHash(aHash)
, mPriority(aPriority)
, mClosed(false)
, mSpecialFile(false)
, mInvalid(false)
, mFileExists(false)
, mPinning(aPinning)
, mDoomWhenFoundPinned(false)
, mDoomWhenFoundNonPinned(false)
, mFileSize(-1)
, mFD(nullptr)
{
@ -127,13 +130,16 @@ CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority)
, this, LOGSHA1(aHash)));
}
CacheFileHandle::CacheFileHandle(const nsACString &aKey, bool aPriority)
CacheFileHandle::CacheFileHandle(const nsACString &aKey, bool aPriority, PinningStatus aPinning)
: mHash(nullptr)
, mPriority(aPriority)
, mClosed(false)
, mSpecialFile(true)
, mInvalid(false)
, mFileExists(false)
, mPinning(aPinning)
, mDoomWhenFoundPinned(false)
, mDoomWhenFoundNonPinned(false)
, mFileSize(-1)
, mFD(nullptr)
, mKey(aKey)
@ -199,6 +205,32 @@ CacheFileHandle::FileSizeInK() const
return size;
}
bool
CacheFileHandle::SetPinned(bool aPinned)
{
LOG(("CacheFileHandle::SetPinned [this=%p, pinned=%d]", this, aPinned));
MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
mPinning = aPinned
? PinningStatus::PINNED
: PinningStatus::NON_PINNED;
if ((MOZ_UNLIKELY(mDoomWhenFoundPinned) && aPinned) ||
(MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && !aPinned)) {
LOG((" dooming, when: pinned=%d, non-pinned=%d, found: pinned=%d",
bool(mDoomWhenFoundPinned), bool(mDoomWhenFoundNonPinned), aPinned));
mDoomWhenFoundPinned = false;
mDoomWhenFoundNonPinned = false;
return false;
}
return true;
}
// Memory reporting
size_t
@ -360,7 +392,7 @@ CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash,
nsresult
CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash,
bool aPriority,
bool aPriority, CacheFileHandle::PinningStatus aPinning,
CacheFileHandle **_retval)
{
MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
@ -381,7 +413,7 @@ CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash,
entry->AssertHandlesState();
#endif
RefPtr<CacheFileHandle> handle = new CacheFileHandle(entry->Hash(), aPriority);
RefPtr<CacheFileHandle> handle = new CacheFileHandle(entry->Hash(), aPriority, aPinning);
entry->AddHandle(handle);
LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
@ -576,8 +608,8 @@ public:
}
}
}
mCallback->OnFileOpened(mHandle, rv);
mCallback->OnFileOpened(mHandle, rv);
return NS_OK;
}
@ -585,8 +617,8 @@ protected:
SHA1Sum::Hash mHash;
uint32_t mFlags;
nsCOMPtr<CacheFileIOListener> mCallback;
RefPtr<CacheFileIOManager> mIOMan;
RefPtr<CacheFileHandle> mHandle;
RefPtr<CacheFileIOManager> mIOMan;
RefPtr<CacheFileHandle> mHandle;
nsCString mKey;
};
@ -626,7 +658,7 @@ public:
}
protected:
RefPtr<CacheFileHandle> mHandle;
RefPtr<CacheFileHandle> mHandle;
int64_t mOffset;
char *mBuf;
int32_t mCount;
@ -685,7 +717,7 @@ public:
}
protected:
RefPtr<CacheFileHandle> mHandle;
RefPtr<CacheFileHandle> mHandle;
int64_t mOffset;
const char *mBuf;
int32_t mCount;
@ -729,9 +761,9 @@ public:
}
protected:
nsCOMPtr<CacheFileIOListener> mCallback;
nsCOMPtr<nsIEventTarget> mTarget;
RefPtr<CacheFileHandle> mHandle;
nsCOMPtr<CacheFileIOListener> mCallback;
nsCOMPtr<nsIEventTarget> mTarget;
RefPtr<CacheFileHandle> mHandle;
};
class DoomFileByKeyEvent : public nsRunnable {
@ -777,7 +809,7 @@ public:
protected:
SHA1Sum::Hash mHash;
nsCOMPtr<CacheFileIOListener> mCallback;
RefPtr<CacheFileIOManager> mIOMan;
RefPtr<CacheFileIOManager> mIOMan;
};
class ReleaseNSPRHandleEvent : public nsRunnable {
@ -805,7 +837,7 @@ public:
}
protected:
RefPtr<CacheFileHandle> mHandle;
RefPtr<CacheFileHandle> mHandle;
};
class TruncateSeekSetEOFEvent : public nsRunnable {
@ -846,7 +878,7 @@ public:
}
protected:
RefPtr<CacheFileHandle> mHandle;
RefPtr<CacheFileHandle> mHandle;
int64_t mTruncatePos;
int64_t mEOFPos;
nsCOMPtr<CacheFileIOListener> mCallback;
@ -889,7 +921,7 @@ public:
}
protected:
RefPtr<CacheFileHandle> mHandle;
RefPtr<CacheFileHandle> mHandle;
nsCString mNewName;
nsCOMPtr<CacheFileIOListener> mCallback;
};
@ -897,11 +929,12 @@ protected:
class InitIndexEntryEvent : public nsRunnable {
public:
InitIndexEntryEvent(CacheFileHandle *aHandle, uint32_t aAppId,
bool aAnonymous, bool aInBrowser)
bool aAnonymous, bool aInBrowser, bool aPinning)
: mHandle(aHandle)
, mAppId(aAppId)
, mAnonymous(aAnonymous)
, mInBrowser(aInBrowser)
, mPinning(aPinning)
{
MOZ_COUNT_CTOR(InitIndexEntryEvent);
}
@ -919,7 +952,7 @@ public:
return NS_OK;
}
CacheIndex::InitEntry(mHandle->Hash(), mAppId, mAnonymous, mInBrowser);
CacheIndex::InitEntry(mHandle->Hash(), mAppId, mAnonymous, mInBrowser, mPinning);
// We cannot set the filesize before we init the entry. If we're opening
// an existing entry file, frecency and expiration time will be set after
@ -936,6 +969,7 @@ protected:
uint32_t mAppId;
bool mAnonymous;
bool mInBrowser;
bool mPinning;
};
class UpdateIndexEntryEvent : public nsRunnable {
@ -1523,6 +1557,10 @@ CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
if (NS_FAILED(rv)) return rv;
}
CacheFileHandle::PinningStatus pinning = aFlags & PINNED
? CacheFileHandle::PinningStatus::PINNED
: CacheFileHandle::PinningStatus::NON_PINNED;
nsCOMPtr<nsIFile> file;
rv = GetFile(aHash, getter_AddRefs(file));
NS_ENSURE_SUCCESS(rv, rv);
@ -1537,7 +1575,7 @@ CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
handle = nullptr;
}
rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning, getter_AddRefs(handle));
NS_ENSURE_SUCCESS(rv, rv);
bool exists;
@ -1567,7 +1605,7 @@ CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
return NS_OK;
}
bool exists;
bool exists, evictedAsPinned = false, evictedAsNonPinned = false;
rv = file->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
@ -1575,15 +1613,7 @@ CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
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);
}
mContextEvictor->WasEvicted(aKey, file, &evictedAsPinned, &evictedAsNonPinned);
}
}
@ -1591,10 +1621,29 @@ CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
return NS_ERROR_NOT_AVAILABLE;
}
rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
if (exists) {
// For existing files we determine the pinning status later, after the metadata gets parsed.
pinning = CacheFileHandle::PinningStatus::UNKNOWN;
}
rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning, getter_AddRefs(handle));
NS_ENSURE_SUCCESS(rv, rv);
if (exists) {
// If this file has been found evicted through the context file evictor above for
// any of pinned or non-pinned state, these calls ensure we doom the handle ASAP
// we know the real pinning state after metadta has been parsed. DoomFileInternal
// on the |handle| doesn't doom right now, since the pinning state is unknown
// and we pass down a pinning restriction.
if (evictedAsPinned) {
rv = DoomFileInternal(handle, DOOM_WHEN_PINNED);
MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
}
if (evictedAsNonPinned) {
rv = DoomFileInternal(handle, DOOM_WHEN_NON_PINNED);
MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
}
rv = file->GetFileSize(&handle->mFileSize);
NS_ENSURE_SUCCESS(rv, rv);
@ -1652,7 +1701,7 @@ CacheFileIOManager::OpenSpecialFileInternal(const nsACString &aKey,
handle = nullptr;
}
handle = new CacheFileHandle(aKey, aFlags & PRIORITY);
handle = new CacheFileHandle(aKey, aFlags & PRIORITY, CacheFileHandle::PinningStatus::NON_PINNED);
mSpecialHandles.AppendElement(handle);
bool exists;
@ -1687,7 +1736,7 @@ CacheFileIOManager::OpenSpecialFileInternal(const nsACString &aKey,
return NS_ERROR_NOT_AVAILABLE;
}
handle = new CacheFileHandle(aKey, aFlags & PRIORITY);
handle = new CacheFileHandle(aKey, aFlags & PRIORITY, CacheFileHandle::PinningStatus::NON_PINNED);
mSpecialHandles.AppendElement(handle);
if (exists) {
@ -1984,17 +2033,52 @@ CacheFileIOManager::DoomFile(CacheFileHandle *aHandle,
}
nsresult
CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle)
CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle,
PinningDoomRestriction aPinningDoomRestriction)
{
LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle));
aHandle->Log();
MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
nsresult rv;
if (aHandle->IsDoomed()) {
return NS_OK;
}
if (aPinningDoomRestriction > NO_RESTRICTION) {
switch (aHandle->mPinning) {
case CacheFileHandle::PinningStatus::NON_PINNED:
if (MOZ_LIKELY(aPinningDoomRestriction != DOOM_WHEN_NON_PINNED)) {
LOG((" not dooming, it's a non-pinned handle"));
return NS_OK;
}
// Doom now
break;
case CacheFileHandle::PinningStatus::PINNED:
if (MOZ_UNLIKELY(aPinningDoomRestriction != DOOM_WHEN_PINNED)) {
LOG((" not dooming, it's a pinned handle"));
return NS_OK;
}
// Doom now
break;
case CacheFileHandle::PinningStatus::UNKNOWN:
if (MOZ_LIKELY(aPinningDoomRestriction == DOOM_WHEN_NON_PINNED)) {
LOG((" doom when non-pinned set"));
aHandle->mDoomWhenFoundNonPinned = true;
} else if (MOZ_UNLIKELY(aPinningDoomRestriction == DOOM_WHEN_PINNED)) {
LOG((" doom when pinned set"));
aHandle->mDoomWhenFoundPinned = true;
}
LOG((" pinning status not known, deferring doom decision"));
return NS_OK;
}
}
if (aHandle->mFileExists) {
// we need to move the current file to the doomed directory
if (aHandle->mFD) {
@ -2784,7 +2868,7 @@ CacheFileIOManager::EvictAllInternal()
// static
nsresult
CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo)
CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo, bool aPinned)
{
LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
aLoadContextInfo));
@ -2797,8 +2881,8 @@ CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo)
}
nsCOMPtr<nsIRunnable> ev;
ev = NS_NewRunnableMethodWithArg<nsCOMPtr<nsILoadContextInfo> >
(ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo);
ev = NS_NewRunnableMethodWithArgs<nsCOMPtr<nsILoadContextInfo>, bool>
(ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo, aPinned);
rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -2809,24 +2893,35 @@ CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo)
}
nsresult
CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo)
CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo, bool aPinned)
{
nsAutoCString suffix;
aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(suffix);
LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, "
"anonymous=%u, suffix=%s]", aLoadContextInfo, aLoadContextInfo->IsAnonymous(),
suffix.get()));
LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, pinned=%d]",
aLoadContextInfo, aPinned));
nsresult rv;
MOZ_ASSERT(mIOThread->IsCurrentThread());
if (aLoadContextInfo) {
nsAutoCString suffix;
aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(suffix);
LOG((" anonymous=%u, suffix=%s]", aLoadContextInfo->IsAnonymous(), suffix.get()));
MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
if (aLoadContextInfo->IsPrivate()) {
return NS_ERROR_INVALID_ARG;
MOZ_ASSERT(mIOThread->IsCurrentThread());
MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
if (aLoadContextInfo->IsPrivate()) {
return NS_ERROR_INVALID_ARG;
}
}
if (!mCacheDirectory) {
// This is a kind of hack. Somebody called EvictAll() without a profile.
// This happens in xpcshell tests that use cache without profile. We need
// to notify observers in this case since the tests are waiting for it.
// Also notify for aPinned == true, those are interested as well.
if (!aLoadContextInfo) {
RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
NS_DispatchToMainThread(r);
}
return NS_ERROR_FILE_INVALID_PATH;
}
@ -2846,24 +2941,38 @@ CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo)
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!");
}
CacheFileHandle* handle = handles[i];
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 (aLoadContextInfo) {
bool equals;
rv = CacheFileUtils::KeyMatchesLoadContextInfo(handle->Key(),
aLoadContextInfo,
&equals);
if (NS_FAILED(rv)) {
LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
"handle! [handle=%p, key=%s]", handle, handle->Key().get()));
MOZ_CRASH("Unexpected error!");
}
if (!equals) {
continue;
}
}
// handle will be doomed only when pinning status is known and equal or
// doom decision will be deferred until pinning status is determined.
rv = DoomFileInternal(handle, aPinned
? CacheFileIOManager::DOOM_WHEN_PINNED
: CacheFileIOManager::DOOM_WHEN_NON_PINNED);
if (NS_WARN_IF(NS_FAILED(rv))) {
LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
" [handle=%p]", handle));
}
}
if (!aLoadContextInfo) {
RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
NS_DispatchToMainThread(r);
}
if (!mContextEvictor) {
@ -2871,7 +2980,7 @@ CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo)
mContextEvictor->Init(mCacheDirectory);
}
mContextEvictor->AddContext(aLoadContextInfo);
mContextEvictor->AddContext(aLoadContextInfo, aPinned);
return NS_OK;
}
@ -3243,10 +3352,11 @@ nsresult
CacheFileIOManager::InitIndexEntry(CacheFileHandle *aHandle,
uint32_t aAppId,
bool aAnonymous,
bool aInBrowser)
bool aInBrowser,
bool aPinning)
{
LOG(("CacheFileIOManager::InitIndexEntry() [handle=%p, appId=%u, anonymous=%d"
", inBrowser=%d]", aHandle, aAppId, aAnonymous, aInBrowser));
", inBrowser=%d, pinned=%d]", aHandle, aAppId, aAnonymous, aInBrowser, aPinning));
nsresult rv;
RefPtr<CacheFileIOManager> ioMan = gInstance;
@ -3260,7 +3370,7 @@ CacheFileIOManager::InitIndexEntry(CacheFileHandle *aHandle,
}
RefPtr<InitIndexEntryEvent> ev =
new InitIndexEntryEvent(aHandle, aAppId, aAnonymous, aInBrowser);
new InitIndexEntryEvent(aHandle, aAppId, aAnonymous, aInBrowser, aPinning);
rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
NS_ENSURE_SUCCESS(rv, rv);

Просмотреть файл

@ -29,6 +29,8 @@ namespace mozilla {
namespace net {
class CacheFile;
class CacheFileIOListener;
#ifdef DEBUG_HANDLES
class CacheFileHandlesEntry;
#endif
@ -41,12 +43,17 @@ class CacheFileHandlesEntry;
class CacheFileHandle : public nsISupports
{
public:
enum class PinningStatus : uint32_t {
UNKNOWN,
NON_PINNED,
PINNED
};
NS_DECL_THREADSAFE_ISUPPORTS
bool DispatchRelease();
CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority);
CacheFileHandle(const nsACString &aKey, bool aPriority);
CacheFileHandle(const CacheFileHandle &aOther);
CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority, PinningStatus aPinning);
CacheFileHandle(const nsACString &aKey, bool aPriority, PinningStatus aPinning);
void Log();
bool IsDoomed() const { return mIsDoomed; }
const SHA1Sum::Hash *Hash() const { return mHash; }
@ -58,6 +65,9 @@ public:
bool IsSpecialFile() const { return mSpecialFile; }
nsCString & Key() { return mKey; }
// Returns false when this handle has been doomed based on the pinning state update.
bool SetPinned(bool aPinned);
// Memory reporting
size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
@ -79,6 +89,22 @@ private:
// but it can be still deleted by OS/user
// and then a subsequent OpenNSPRFileDesc()
// will fail.
// For existing files this is always pre-set to UNKNOWN. The status is udpated accordingly
// after the matadata has been parsed.
// For new files the flag is set according to which storage kind is opening
// the cache entry and remains so for the handle's lifetime.
// The status can only change from UNKNOWN (if set so initially) to one of PINNED or NON_PINNED
// and it stays unchanged afterwards.
// This status is only accessed on the IO thread.
PinningStatus mPinning;
// Both initially false. Can be raised to true only when this handle is to be doomed
// during the period when the pinning status is unknown. After the pinning status
// determination we check these flags and possibly doom.
// These flags are only accessed on the IO thread.
bool mDoomWhenFoundPinned : 1;
bool mDoomWhenFoundNonPinned : 1;
nsCOMPtr<nsIFile> mFile;
int64_t mFileSize;
PRFileDesc *mFD; // if null then the file doesn't exists on the disk
@ -91,7 +117,8 @@ public:
~CacheFileHandles();
nsresult GetHandle(const SHA1Sum::Hash *aHash, CacheFileHandle **_retval);
nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority, CacheFileHandle **_retval);
nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority,
CacheFileHandle::PinningStatus aPinning, CacheFileHandle **_retval);
void RemoveHandle(CacheFileHandle *aHandlle);
void GetAllHandles(nsTArray<RefPtr<CacheFileHandle> > *_retval);
void GetActiveHandles(nsTArray<RefPtr<CacheFileHandle> > *_retval);
@ -212,11 +239,12 @@ public:
NS_DECL_NSITIMERCALLBACK
enum {
OPEN = 0U,
CREATE = 1U,
CREATE_NEW = 2U,
PRIORITY = 4U,
SPECIAL_FILE = 8U
OPEN = 0U,
CREATE = 1U,
CREATE_NEW = 2U,
PRIORITY = 4U,
SPECIAL_FILE = 8U,
PINNED = 16U
};
CacheFileIOManager();
@ -247,6 +275,19 @@ public:
static nsresult Write(CacheFileHandle *aHandle, int64_t aOffset,
const char *aBuf, int32_t aCount, bool aValidate,
bool aTruncate, CacheFileIOListener *aCallback);
// PinningDoomRestriction:
// NO_RESTRICTION
// no restriction is checked, the file is simply always doomed
// DOOM_WHEN_(NON)_PINNED, we branch based on the pinning status of the handle:
// UNKNOWN: the handle is marked to be doomed when later found (non)pinned
// PINNED/NON_PINNED: doom only when the restriction matches the pin status
// and the handle has not yet been required to doom during the UNKNOWN
// period
enum PinningDoomRestriction {
NO_RESTRICTION,
DOOM_WHEN_NON_PINNED,
DOOM_WHEN_PINNED
};
static nsresult DoomFile(CacheFileHandle *aHandle,
CacheFileIOListener *aCallback);
static nsresult DoomFileByKey(const nsACString &aKey,
@ -260,12 +301,14 @@ public:
CacheFileIOListener *aCallback);
static nsresult EvictIfOverLimit();
static nsresult EvictAll();
static nsresult EvictByContext(nsILoadContextInfo *aLoadContextInfo);
static nsresult EvictByContext(nsILoadContextInfo *aLoadContextInfo,
bool aPinning);
static nsresult InitIndexEntry(CacheFileHandle *aHandle,
uint32_t aAppId,
bool aAnonymous,
bool aInBrowser);
bool aInBrowser,
bool aPinning);
static nsresult UpdateIndexEntry(CacheFileHandle *aHandle,
const uint32_t *aFrecency,
const uint32_t *aExpirationTime);
@ -329,7 +372,8 @@ private:
nsresult WriteInternal(CacheFileHandle *aHandle, int64_t aOffset,
const char *aBuf, int32_t aCount, bool aValidate,
bool aTruncate);
nsresult DoomFileInternal(CacheFileHandle *aHandle);
nsresult DoomFileInternal(CacheFileHandle *aHandle,
PinningDoomRestriction aPinningStatusRestriction = NO_RESTRICTION);
nsresult DoomFileByKeyInternal(const SHA1Sum::Hash *aHash);
nsresult ReleaseNSPRHandleInternal(CacheFileHandle *aHandle);
nsresult TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
@ -339,7 +383,8 @@ private:
nsresult EvictIfOverLimitInternal();
nsresult OverLimitEvictionInternal();
nsresult EvictAllInternal();
nsresult EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo);
nsresult EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo,
bool aPinning);
nsresult TrashDirectory(nsIFile *aFile);
static void OnTrashTimer(nsITimer *aTimer, void *aClosure);
@ -383,7 +428,7 @@ private:
static CacheFileIOManager *gInstance;
TimeStamp mStartTime;
bool mShuttingDown;
RefPtr<CacheIOThread> mIOThread;
RefPtr<CacheIOThread> mIOThread;
nsCOMPtr<nsIFile> mCacheDirectory;
#if defined(MOZ_WIDGET_ANDROID)
// On Android we add the active profile directory name between the path
@ -396,7 +441,7 @@ private:
CacheFileHandles mHandles;
nsTArray<CacheFileHandle *> mHandlesByLastUsed;
nsTArray<CacheFileHandle *> mSpecialHandles;
nsTArray<RefPtr<CacheFile> > mScheduledMetadataWrites;
nsTArray<RefPtr<CacheFile> > mScheduledMetadataWrites;
nsCOMPtr<nsITimer> mMetadataWritesTimer;
bool mOverLimitEvicting;
bool mRemovingTrashDirs;
@ -404,7 +449,7 @@ private:
nsCOMPtr<nsIFile> mTrashDir;
nsCOMPtr<nsIDirectoryEnumerator> mTrashDirEnumerator;
nsTArray<nsCString> mFailedTrashDirs;
RefPtr<CacheFileContextEvictor> mContextEvictor;
RefPtr<CacheFileContextEvictor> mContextEvictor;
TimeStamp mLastSmartSizeTime;
};

Просмотреть файл

@ -53,7 +53,7 @@ private:
void NotifyListener();
void MaybeNotifyListener();
RefPtr<CacheFile> mFile;
RefPtr<CacheFile> mFile;
RefPtr<CacheFileChunk> mChunk;
int64_t mPos;
bool mClosed;

Просмотреть файл

@ -35,8 +35,6 @@ namespace net {
// Max size of elements in bytes.
#define kMaxElementsSize 64*1024
#define kCacheEntryVersion 1
#define NOW_SECONDS() (uint32_t(PR_Now() / PR_USEC_PER_SEC))
NS_IMPL_ISUPPORTS(CacheFileMetadata, CacheFileIOListener)
@ -71,7 +69,7 @@ CacheFileMetadata::CacheFileMetadata(CacheFileHandle *aHandle, const nsACString
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, const nsACString &aKey)
CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, bool aPinned, const nsACString &aKey)
: CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL)
, mHandle(nullptr)
, mHashArray(nullptr)
@ -93,6 +91,9 @@ CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, const nsACString &aKey)
MOZ_COUNT_CTOR(CacheFileMetadata);
memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
mMetaHdr.mVersion = kCacheEntryVersion;
if (aPinned) {
AddFlags(kCacheEntryIsPinned);
}
mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
mKey = aKey;
mMetaHdr.mKeySize = mKey.Length();
@ -534,6 +535,29 @@ CacheFileMetadata::SetHash(uint32_t aIndex, CacheHash::Hash16_t aHash)
return NS_OK;
}
nsresult
CacheFileMetadata::AddFlags(uint32_t aFlags)
{
MarkDirty(false);
mMetaHdr.mFlags |= aFlags;
return NS_OK;
}
nsresult
CacheFileMetadata::RemoveFlags(uint32_t aFlags)
{
MarkDirty(false);
mMetaHdr.mFlags &= ~aFlags;
return NS_OK;
}
nsresult
CacheFileMetadata::GetFlags(uint32_t *_retval)
{
*_retval = mMetaHdr.mFlags;
return NS_OK;
}
nsresult
CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime)
{
@ -814,11 +838,16 @@ CacheFileMetadata::InitEmptyMetadata()
mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
mMetaHdr.mKeySize = mKey.Length();
// Deliberately not touching the "kCacheEntryIsPinned" flag.
DoMemoryReport(MemoryUsage());
// We're creating a new entry. If there is any old data truncate it.
if (mHandle && mHandle->FileExists() && mHandle->FileSize()) {
CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
if (mHandle) {
mHandle->SetPinned(Pinned());
if (mHandle->FileExists() && mHandle->FileSize()) {
CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
}
}
}
@ -853,12 +882,19 @@ CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset,
mMetaHdr.ReadFromBuf(mBuf + hdrOffset);
if (mMetaHdr.mVersion != kCacheEntryVersion) {
if (mMetaHdr.mVersion == 1) {
// Backward compatibility before we've added flags to the header
keyOffset -= sizeof(uint32_t);
} else if (mMetaHdr.mVersion != kCacheEntryVersion) {
LOG(("CacheFileMetadata::ParseMetadata() - Not a version we understand to. "
"[version=0x%x, this=%p]", mMetaHdr.mVersion, this));
return NS_ERROR_UNEXPECTED;
}
// Update the version stored in the header to make writes
// store the header in the current version form.
mMetaHdr.mVersion = kCacheEntryVersion;
uint32_t elementsOffset = mMetaHdr.mKeySize + keyOffset + 1;
if (elementsOffset > metaposOffset) {
@ -917,6 +953,14 @@ CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset,
if (NS_FAILED(rv))
return rv;
if (mHandle) {
if (!mHandle->SetPinned(Pinned())) {
LOG(("CacheFileMetadata::ParseMetadata() - handle was doomed for this "
"pinning state, truncate the file [this=%p, pinned=%d]", this, Pinned()));
return NS_ERROR_FILE_CORRUPTED;
}
}
mHashArraySize = hashesLen;
mHashCount = hashCount;
if (mHashArraySize) {

Просмотреть файл

@ -19,6 +19,12 @@ class nsICacheEntryMetaDataVisitor;
namespace mozilla {
namespace net {
// Flags stored in CacheFileMetadataHeader.mFlags
// Whether an entry is a pinned entry (created with
// nsICacheStorageService.pinningCacheStorage.)
static const uint32_t kCacheEntryIsPinned = 1 << 0;
// By multiplying with the current half-life we convert the frecency
// to time independent of half-life value. The range fits 32bits.
// When decay time changes on next run of the browser, we convert
@ -30,6 +36,9 @@ namespace net {
((double)(aInt) / (double)CacheObserver::HalfLifeSeconds())
#define kCacheEntryVersion 2
#pragma pack(push)
#pragma pack(1)
@ -42,19 +51,22 @@ public:
uint32_t mFrecency;
uint32_t mExpirationTime;
uint32_t mKeySize;
uint32_t mFlags;
void WriteToBuf(void *aBuf)
{
EnsureCorrectClassSize();
uint8_t* ptr = static_cast<uint8_t*>(aBuf);
MOZ_ASSERT(mVersion == kCacheEntryVersion);
NetworkEndian::writeUint32(ptr, mVersion); ptr += sizeof(uint32_t);
NetworkEndian::writeUint32(ptr, mFetchCount); ptr += sizeof(uint32_t);
NetworkEndian::writeUint32(ptr, mLastFetched); ptr += sizeof(uint32_t);
NetworkEndian::writeUint32(ptr, mLastModified); ptr += sizeof(uint32_t);
NetworkEndian::writeUint32(ptr, mFrecency); ptr += sizeof(uint32_t);
NetworkEndian::writeUint32(ptr, mExpirationTime); ptr += sizeof(uint32_t);
NetworkEndian::writeUint32(ptr, mKeySize);
NetworkEndian::writeUint32(ptr, mKeySize); ptr += sizeof(uint32_t);
NetworkEndian::writeUint32(ptr, mFlags);
}
void ReadFromBuf(const void *aBuf)
@ -68,14 +80,19 @@ public:
mLastModified = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
mFrecency = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
mExpirationTime = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
mKeySize = BigEndian::readUint32(ptr);
mKeySize = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
if (mVersion >= kCacheEntryVersion) {
mFlags = BigEndian::readUint32(ptr);
} else {
mFlags = 0;
}
}
inline void EnsureCorrectClassSize()
{
static_assert((sizeof(mVersion) + sizeof(mFetchCount) +
sizeof(mLastFetched) + sizeof(mLastModified) + sizeof(mFrecency) +
sizeof(mExpirationTime) + sizeof(mKeySize)) ==
sizeof(mExpirationTime) + sizeof(mKeySize)) + sizeof(mFlags) ==
sizeof(CacheFileMetadataHeader),
"Unexpected sizeof(CacheFileMetadataHeader)!");
}
@ -114,6 +131,7 @@ public:
CacheFileMetadata(CacheFileHandle *aHandle,
const nsACString &aKey);
CacheFileMetadata(bool aMemoryOnly,
bool aPinned,
const nsACString &aKey);
CacheFileMetadata();
@ -127,8 +145,9 @@ public:
CacheFileMetadataListener *aListener);
nsresult SyncReadMetadata(nsIFile *aFile);
bool IsAnonymous() { return mAnonymous; }
bool IsAnonymous() const { return mAnonymous; }
mozilla::OriginAttributes const & OriginAttributes() const { return mOriginAttributes; }
bool Pinned() const { return !!(mMetaHdr.mFlags & kCacheEntryIsPinned); }
const char * GetElement(const char *aKey);
nsresult SetElement(const char *aKey, const char *aValue);
@ -137,6 +156,9 @@ public:
CacheHash::Hash16_t GetHash(uint32_t aIndex);
nsresult SetHash(uint32_t aIndex, CacheHash::Hash16_t aHash);
nsresult AddFlags(uint32_t aFlags);
nsresult RemoveFlags(uint32_t aFlags);
nsresult GetFlags(uint32_t *_retval);
nsresult SetExpirationTime(uint32_t aExpirationTime);
nsresult GetExpirationTime(uint32_t *_retval);
nsresult SetFrecency(uint32_t aFrecency);
@ -175,7 +197,7 @@ private:
nsresult EnsureBuffer(uint32_t aSize);
nsresult ParseKey(const nsACString &aKey);
RefPtr<CacheFileHandle> mHandle;
RefPtr<CacheFileHandle> mHandle;
nsCString mKey;
CacheHash::Hash16_t *mHashArray;
uint32_t mHashArraySize;

Просмотреть файл

@ -50,7 +50,7 @@ private:
void FillHole();
void NotifyListener();
RefPtr<CacheFile> mFile;
RefPtr<CacheFile> mFile;
RefPtr<CacheFileChunk> mChunk;
RefPtr<CacheOutputCloseListener> mCloseListener;
int64_t mPos;

Просмотреть файл

@ -705,11 +705,12 @@ nsresult
CacheIndex::InitEntry(const SHA1Sum::Hash *aHash,
uint32_t aAppId,
bool aAnonymous,
bool aInBrowser)
bool aInBrowser,
bool aPinned)
{
LOG(("CacheIndex::InitEntry() [hash=%08x%08x%08x%08x%08x, appId=%u, "
"anonymous=%d, inBrowser=%d]", LOGSHA1(aHash), aAppId, aAnonymous,
aInBrowser));
"anonymous=%d, inBrowser=%d, pinned=%d]", LOGSHA1(aHash), aAppId,
aAnonymous, aInBrowser, aPinned));
RefPtr<CacheIndex> index = gInstance;
@ -799,10 +800,10 @@ CacheIndex::InitEntry(const SHA1Sum::Hash *aHash,
}
if (updated) {
updated->Init(aAppId, aAnonymous, aInBrowser);
updated->Init(aAppId, aAnonymous, aInBrowser, aPinned);
updated->MarkDirty();
} else {
entry->Init(aAppId, aAnonymous, aInBrowser);
entry->Init(aAppId, aAnonymous, aInBrowser, aPinned);
entry->MarkDirty();
}
}
@ -1108,10 +1109,22 @@ CacheIndex::RemoveAll()
// static
nsresult
CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval)
CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval, bool *_pinned)
{
LOG(("CacheIndex::HasEntry() [key=%s]", PromiseFlatCString(aKey).get()));
SHA1Sum sum;
SHA1Sum::Hash hash;
sum.update(aKey.BeginReading(), aKey.Length());
sum.finish(hash);
return HasEntry(hash, _retval, _pinned);
}
// static
nsresult
CacheIndex::HasEntry(const SHA1Sum::Hash &hash, EntryStatus *_retval, bool *_pinned)
{
RefPtr<CacheIndex> index = gInstance;
if (!index) {
@ -1124,10 +1137,9 @@ CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval)
return NS_ERROR_NOT_AVAILABLE;
}
SHA1Sum sum;
SHA1Sum::Hash hash;
sum.update(aKey.BeginReading(), aKey.Length());
sum.finish(hash);
if (_pinned) {
*_pinned = false;
}
const CacheIndexEntry *entry = nullptr;
@ -1163,6 +1175,9 @@ CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval)
}
} else {
*_retval = EXISTS;
if (_pinned && entry->IsPinned()) {
*_pinned = true;
}
}
}
@ -1193,7 +1208,7 @@ CacheIndex::GetEntryForEviction(bool aIgnoreEmptyEntries, SHA1Sum::Hash *aHash,
bool foundEntry = false;
uint32_t i;
// find first non-forced valid entry with the lowest frecency
// find first non-forced valid and unpinned entry with the lowest frecency
index->mFrecencyArray.Sort(FrecencyComparator());
for (i = 0; i < index->mFrecencyArray.Length(); ++i) {
memcpy(&hash, &index->mFrecencyArray[i]->mHash, sizeof(SHA1Sum::Hash));
@ -1202,6 +1217,10 @@ CacheIndex::GetEntryForEviction(bool aIgnoreEmptyEntries, SHA1Sum::Hash *aHash,
continue;
}
if (CacheIndexEntry::IsPinned(index->mFrecencyArray[i])) {
continue;
}
if (aIgnoreEmptyEntries &&
!CacheIndexEntry::GetFileSize(index->mFrecencyArray[i])) {
continue;
@ -2575,7 +2594,8 @@ CacheIndex::InitEntryFromDiskData(CacheIndexEntry *aEntry,
// Bug 1201042 - will pass OriginAttributes directly.
aEntry->Init(aMetaData->OriginAttributes().mAppId,
aMetaData->IsAnonymous(),
aMetaData->OriginAttributes().mInBrowser);
aMetaData->OriginAttributes().mInBrowser,
aMetaData->Pinned());
uint32_t expirationTime;
aMetaData->GetExpirationTime(&expirationTime);

Просмотреть файл

@ -148,7 +148,7 @@ public:
mRec->mFlags = 0;
}
void Init(uint32_t aAppId, bool aAnonymous, bool aInBrowser)
void Init(uint32_t aAppId, bool aAnonymous, bool aInBrowser, bool aPinned)
{
MOZ_ASSERT(mRec->mFrecency == 0);
MOZ_ASSERT(mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME);
@ -164,6 +164,9 @@ public:
if (aInBrowser) {
mRec->mFlags |= kInBrowserMask;
}
if (aPinned) {
mRec->mFlags |= kPinnedMask;
}
}
const SHA1Sum::Hash * Hash() const { return &mRec->mHash; }
@ -184,6 +187,8 @@ public:
bool IsFresh() const { return !!(mRec->mFlags & kFreshMask); }
void MarkFresh() { mRec->mFlags |= kFreshMask; }
bool IsPinned() const { return !!(mRec->mFlags & kPinnedMask); }
void SetFrecency(uint32_t aFrecency) { mRec->mFrecency = aFrecency; }
uint32_t GetFrecency() const { return mRec->mFrecency; }
@ -210,6 +215,10 @@ public:
{
return aRec->mFlags & kFileSizeMask;
}
static uint32_t IsPinned(CacheIndexRecord *aRec)
{
return aRec->mFlags & kPinnedMask;
}
bool IsFileEmpty() const { return GetFileSize() == 0; }
void WriteToBuf(void *aBuf)
@ -301,7 +310,10 @@ private:
// this entry during update or build process.
static const uint32_t kFreshMask = 0x04000000;
static const uint32_t kReservedMask = 0x03000000;
// Indicates a pinned entry.
static const uint32_t kPinnedMask = 0x02000000;
static const uint32_t kReservedMask = 0x01000000;
// FileSize in kilobytes
static const uint32_t kFileSizeMask = 0x00FFFFFF;
@ -610,7 +622,8 @@ public:
static nsresult InitEntry(const SHA1Sum::Hash *aHash,
uint32_t aAppId,
bool aAnonymous,
bool aInBrowser);
bool aInBrowser,
bool aPinned);
// Remove entry from index. The entry should be present in index.
static nsresult RemoveEntry(const SHA1Sum::Hash *aHash);
@ -635,12 +648,16 @@ public:
// Returns status of the entry in index for the given key. It can be called
// on any thread.
static nsresult HasEntry(const nsACString &aKey, EntryStatus *_retval);
// If _pinned is non-null, it's filled with pinning status of the entry.
static nsresult HasEntry(const nsACString &aKey, EntryStatus *_retval,
bool *_pinned = nullptr);
static nsresult HasEntry(const SHA1Sum::Hash &hash, EntryStatus *_retval,
bool *_pinned = nullptr);
// Returns a hash of the least important entry that should be evicted if the
// cache size is over limit and also returns a total number of all entries in
// the index minus the number of forced valid entries that we encounter
// when searching (see below)
// the index minus the number of forced valid entries and unpinned entries
// that we encounter when searching (see below)
static nsresult GetEntryForEviction(bool aIgnoreEmptyEntries, SHA1Sum::Hash *aHash, uint32_t *aCnt);
// Checks if a cache entry is currently forced valid. Used to prevent an entry
@ -969,7 +986,7 @@ private:
char *mRWBuf;
uint32_t mRWBufSize;
uint32_t mRWBufPos;
RefPtr<CacheHash> mRWHash;
RefPtr<CacheHash> mRWHash;
// Reading of journal succeeded if true.
bool mJournalReadSuccessfully;
@ -981,9 +998,9 @@ private:
// Used to check the existence of the file during reading process.
RefPtr<CacheFileHandle> mTmpHandle;
RefPtr<FileOpenHelper> mIndexFileOpener;
RefPtr<FileOpenHelper> mJournalFileOpener;
RefPtr<FileOpenHelper> mTmpFileOpener;
RefPtr<FileOpenHelper> mIndexFileOpener;
RefPtr<FileOpenHelper> mJournalFileOpener;
RefPtr<FileOpenHelper> mTmpFileOpener;
// Directory enumerator used when building and updating index.
nsCOMPtr<nsIDirectoryEnumerator> mDirEnumerator;

Просмотреть файл

@ -49,7 +49,7 @@ protected:
CacheIndexRecord *aNewRecord);
nsresult mStatus;
RefPtr<CacheIndex> mIndex;
RefPtr<CacheIndex> mIndex;
nsTArray<CacheIndexRecord *> mRecords;
bool mAddNew;
};

Просмотреть файл

@ -26,11 +26,13 @@ NS_IMPL_ISUPPORTS(CacheStorage, nsICacheStorage)
CacheStorage::CacheStorage(nsILoadContextInfo* aInfo,
bool aAllowDisk,
bool aLookupAppCache,
bool aSkipSizeCheck)
bool aSkipSizeCheck,
bool aPinning)
: mLoadContextInfo(GetLoadContextInfo(aInfo))
, mWriteToDisk(aAllowDisk)
, mLookupAppCache(aLookupAppCache)
, mSkipSizeCheck(aSkipSizeCheck)
, mPinning(aPinning)
{
}

Просмотреть файл

@ -53,7 +53,8 @@ public:
CacheStorage(nsILoadContextInfo* aInfo,
bool aAllowDisk,
bool aLookupAppCache,
bool aSkipSizeCheck);
bool aSkipSizeCheck,
bool aPinning);
protected:
virtual ~CacheStorage();
@ -64,12 +65,14 @@ protected:
bool mWriteToDisk : 1;
bool mLookupAppCache : 1;
bool mSkipSizeCheck: 1;
bool mPinning : 1;
public:
nsILoadContextInfo* LoadInfo() const { return mLoadContextInfo; }
bool WriteToDisk() const { return mWriteToDisk && !mLoadContextInfo->IsPrivate(); }
bool LookupAppCache() const { return mLookupAppCache; }
bool SkipSizeCheck() const { return mSkipSizeCheck; }
bool Pinning() const { return mPinning; }
};
} // namespace net

Просмотреть файл

@ -103,7 +103,8 @@ CacheStorageService::MemoryPool::Limit() const
NS_IMPL_ISUPPORTS(CacheStorageService,
nsICacheStorageService,
nsIMemoryReporter,
nsITimerCallback)
nsITimerCallback,
nsICacheTesting)
CacheStorageService* CacheStorageService::sSelf = nullptr;
@ -536,7 +537,7 @@ void CacheStorageService::DropPrivateBrowsingEntries()
sGlobalEntryTables->EnumerateRead(&CollectPrivateContexts, &keys);
for (uint32_t i = 0; i < keys.Length(); ++i)
DoomStorageEntries(keys[i], nullptr, true, nullptr);
DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
}
namespace {
@ -681,7 +682,7 @@ NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(nsILoadContextInfo *aLoadC
nsCOMPtr<nsICacheStorage> storage;
if (CacheObserver::UseNewCache()) {
storage = new CacheStorage(aLoadContextInfo, false, false, false);
storage = new CacheStorage(aLoadContextInfo, false, false, false, false);
}
else {
storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
@ -706,7 +707,7 @@ NS_IMETHODIMP CacheStorageService::DiskCacheStorage(nsILoadContextInfo *aLoadCon
nsCOMPtr<nsICacheStorage> storage;
if (CacheObserver::UseNewCache()) {
storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache, false);
storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache, false /* size limit */, false /* don't pin */);
}
else {
storage = new _OldStorage(aLoadContextInfo, useDisk, aLookupAppCache, false, nullptr);
@ -716,6 +717,27 @@ NS_IMETHODIMP CacheStorageService::DiskCacheStorage(nsILoadContextInfo *aLoadCon
return NS_OK;
}
NS_IMETHODIMP CacheStorageService::PinningCacheStorage(nsILoadContextInfo *aLoadContextInfo,
nsICacheStorage * *_retval)
{
NS_ENSURE_ARG(aLoadContextInfo);
NS_ENSURE_ARG(_retval);
if (!CacheObserver::UseNewCache()) {
return NS_ERROR_NOT_IMPLEMENTED;
}
// When disk cache is disabled don't pretend we cache.
if (!CacheObserver::UseDiskCache()) {
return NS_ERROR_NOT_AVAILABLE;
}
nsCOMPtr<nsICacheStorage> storage = new CacheStorage(
aLoadContextInfo, true /* use disk */, false /* no appcache */, true /* ignore size checks */, true /* pin */);
storage.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP CacheStorageService::AppCacheStorage(nsILoadContextInfo *aLoadContextInfo,
nsIApplicationCache *aApplicationCache,
nsICacheStorage * *_retval)
@ -745,7 +767,7 @@ NS_IMETHODIMP CacheStorageService::SynthesizedCacheStorage(nsILoadContextInfo *a
nsCOMPtr<nsICacheStorage> storage;
if (CacheObserver::UseNewCache()) {
storage = new CacheStorage(aLoadContextInfo, false, false, true /* skip size checks for synthesized cache */);
storage = new CacheStorage(aLoadContextInfo, false, false, true /* skip size checks for synthesized cache */, false /* no pinning */);
}
else {
storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
@ -768,12 +790,15 @@ NS_IMETHODIMP CacheStorageService::Clear()
nsTArray<nsCString> keys;
sGlobalEntryTables->EnumerateRead(&CollectContexts, &keys);
for (uint32_t i = 0; i < keys.Length(); ++i)
DoomStorageEntries(keys[i], nullptr, true, nullptr);
}
for (uint32_t i = 0; i < keys.Length(); ++i) {
DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
}
rv = CacheFileIOManager::EvictAll();
NS_ENSURE_SUCCESS(rv, rv);
// Passing null as a load info means to evict all contexts.
// EvictByContext() respects the entry pinning. EvictAll() does not.
rv = CacheFileIOManager::EvictByContext(nullptr, false);
NS_ENSURE_SUCCESS(rv, rv);
}
} else {
nsCOMPtr<nsICacheService> serv =
do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
@ -813,6 +838,29 @@ NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat)
return Dispatch(event);
}
NS_IMETHODIMP CacheStorageService::PurgeFromMemoryRunnable::Run()
{
if (NS_IsMainThread()) {
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService) {
observerService->NotifyObservers(nullptr, "cacheservice:purge-memory-pools", nullptr);
}
return NS_OK;
}
if (mService) {
// TODO not all flags apply to both pools
mService->Pool(true).PurgeAll(mWhat);
mService->Pool(false).PurgeAll(mWhat);
mService = nullptr;
}
NS_DispatchToMainThread(this);
return NS_OK;
}
NS_IMETHODIMP CacheStorageService::AsyncGetDiskConsumption(
nsICacheStorageConsumptionObserver* aObserver)
{
@ -924,7 +972,7 @@ CacheStorageService::UnregisterEntry(CacheEntry* aEntry)
static bool
AddExactEntry(CacheEntryTable* aEntries,
nsCString const& aKey,
nsACString const& aKey,
CacheEntry* aEntry,
bool aOverwrite)
{
@ -942,7 +990,7 @@ AddExactEntry(CacheEntryTable* aEntries,
static bool
RemoveExactEntry(CacheEntryTable* aEntries,
nsCString const& aKey,
nsACString const& aKey,
CacheEntry* aEntry,
bool aOverwrite)
{
@ -1357,7 +1405,9 @@ CacheStorageService::AddStorageEntry(CacheStorage const* aStorage,
CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
return AddStorageEntry(contextKey, aURI, aIdExtension,
aStorage->WriteToDisk(), aStorage->SkipSizeCheck(),
aStorage->WriteToDisk(),
aStorage->SkipSizeCheck(),
aStorage->Pinning(),
aCreateIfNotExist, aReplace,
aResult);
}
@ -1368,6 +1418,7 @@ CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey,
const nsACString & aIdExtension,
bool aWriteToDisk,
bool aSkipSizeCheck,
bool aPin,
bool aCreateIfNotExist,
bool aReplace,
CacheEntryHandle** aResult)
@ -1402,12 +1453,8 @@ CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey,
bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
if (entryExists && !aReplace) {
// check whether the file is already doomed or we want to turn this entry
// to a memory-only.
if (MOZ_UNLIKELY(entry->IsFileDoomed())) {
LOG((" file already doomed, replacing the entry"));
aReplace = true;
} else if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) {
// check whether we want to turn this entry to a memory-only.
if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) {
LOG((" entry is persistnet but we want mem-only, replacing it"));
aReplace = true;
}
@ -1430,7 +1477,7 @@ CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey,
// 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, aSkipSizeCheck);
entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk, aSkipSizeCheck, aPin);
entries->Put(entryKey, entry);
LOG((" new entry %p for %s", entry.get(), entryKey.get()));
}
@ -1668,15 +1715,19 @@ CacheStorageService::DoomStorageEntries(CacheStorage const* aStorage,
mozilla::MutexAutoLock lock(mLock);
return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
aStorage->WriteToDisk(), aCallback);
aStorage->WriteToDisk(), aStorage->Pinning(),
aCallback);
}
nsresult
CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
nsILoadContextInfo* aContext,
bool aDiskStorage,
bool aPinned,
nsICacheEntryDoomCallback* aCallback)
{
LOG(("CacheStorageService::DoomStorageEntries [context=%s]", aContextKey.BeginReading()));
mLock.AssertCurrentThreadOwns();
NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
@ -1687,30 +1738,31 @@ CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
if (aDiskStorage) {
LOG((" dooming disk+memory storage of %s", aContextKey.BeginReading()));
// Just remove all entries, CacheFileIOManager will take care of the files.
sGlobalEntryTables->Remove(aContextKey);
sGlobalEntryTables->Remove(memoryStorageID);
// Walk one by one and remove entries according their pin status
CacheEntryTable *diskEntries, *memoryEntries;
if (sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
sGlobalEntryTables->Get(memoryStorageID, &memoryEntries);
for (auto iter = diskEntries->Iter(); !iter.Done(); iter.Next()) {
auto entry = iter.Data();
if (entry->DeferOrBypassRemovalOnPinStatus(aPinned)) {
continue;
}
if (memoryEntries) {
RemoveExactEntry(memoryEntries, iter.Key(), entry, false);
}
iter.Remove();
}
}
if (aContext && !aContext->IsPrivate()) {
LOG((" dooming disk entries"));
CacheFileIOManager::EvictByContext(aContext);
CacheFileIOManager::EvictByContext(aContext, aPinned);
}
} else {
LOG((" dooming memory-only storage of %s", aContextKey.BeginReading()));
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
@ -1718,10 +1770,14 @@ CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
nsAutoPtr<CacheEntryTable> memoryEntries;
sGlobalEntryTables->RemoveAndForget(memoryStorageID, memoryEntries);
CacheEntryTable* entries;
sGlobalEntryTables->Get(aContextKey, &entries);
if (memoryEntries && entries)
memoryEntries->EnumerateRead(&MemoryEntriesRemoval::EvictEntry, entries);
CacheEntryTable* diskEntries;
sGlobalEntryTables->Get(aContextKey, &diskEntries);
if (memoryEntries && diskEntries) {
for (auto iter = memoryEntries->Iter(); !iter.Done(); iter.Next()) {
auto entry = iter.Data();
RemoveExactEntry(diskEntries, iter.Key(), entry, false);
}
}
}
// An artificial callback. This is a candidate for removal tho. In the new
@ -1798,9 +1854,6 @@ CacheStorageService::CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
if (!entry->IsFileDoomed())
return;
if (entry->IsReferenced())
return;
// Need to remove under the lock to avoid possible race leading
// to duplication of the entry per its key.
RemoveExactEntry(entries, entryKey, entry, false);
@ -2110,5 +2163,46 @@ CacheStorageService::CollectReports(nsIMemoryReporterCallback* aHandleReport,
return NS_OK;
}
// nsICacheTesting
NS_IMETHODIMP
CacheStorageService::IOThreadSuspender::Run()
{
MonitorAutoLock mon(mMon);
mon.Wait();
return NS_OK;
}
void
CacheStorageService::IOThreadSuspender::Notify()
{
MonitorAutoLock mon(mMon);
mon.Notify();
}
NS_IMETHODIMP
CacheStorageService::SuspendCacheIOThread(uint32_t aLevel)
{
RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
if (!thread) {
return NS_ERROR_NOT_AVAILABLE;
}
MOZ_ASSERT(!mActiveIOSuspender);
mActiveIOSuspender = new IOThreadSuspender();
return thread->Dispatch(mActiveIOSuspender, aLevel);
}
NS_IMETHODIMP
CacheStorageService::ResumeCacheIOThread()
{
MOZ_ASSERT(mActiveIOSuspender);
RefPtr<IOThreadSuspender> suspender;
suspender.swap(mActiveIOSuspender);
suspender->Notify();
return NS_OK;
}
} // namespace net
} // namespace mozilla

Просмотреть файл

@ -7,8 +7,9 @@
#include "nsICacheStorageService.h"
#include "nsIMemoryReporter.h"
#include "nsITimer.h"
#include "nsICacheTesting.h"
#include "nsClassHashtable.h"
#include "nsDataHashtable.h"
#include "nsString.h"
@ -65,12 +66,14 @@ protected:
class CacheStorageService final : public nsICacheStorageService
, public nsIMemoryReporter
, public nsITimerCallback
, public nsICacheTesting
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSICACHESTORAGESERVICE
NS_DECL_NSIMEMORYREPORTER
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSICACHETESTING
CacheStorageService();
@ -273,12 +276,14 @@ private:
nsresult DoomStorageEntries(nsCSubstring const& aContextKey,
nsILoadContextInfo* aContext,
bool aDiskStorage,
bool aPin,
nsICacheEntryDoomCallback* aCallback);
nsresult AddStorageEntry(nsCSubstring const& aContextKey,
nsIURI* aURI,
const nsACString & aIdExtension,
bool aWriteToDisk,
bool aSkipSizeCheck,
bool aPin,
bool aCreateIfNotExist,
bool aReplace,
CacheEntryHandle** aResult);
@ -344,13 +349,7 @@ private:
private:
virtual ~PurgeFromMemoryRunnable() { }
NS_IMETHOD Run()
{
// TODO not all flags apply to both pools
mService->Pool(true).PurgeAll(mWhat);
mService->Pool(false).PurgeAll(mWhat);
return NS_OK;
}
NS_IMETHOD Run() override;
RefPtr<CacheStorageService> mService;
uint32_t mWhat;
@ -361,6 +360,21 @@ private:
// and also would be complicated to report since reporting happens on the main
// thread but this table is manipulated on the management thread.
nsDataHashtable<nsCStringHashKey, mozilla::TimeStamp> mPurgeTimeStamps;
// nsICacheTesting
class IOThreadSuspender : public nsRunnable
{
public:
IOThreadSuspender() : mMon("IOThreadSuspender") { }
void Notify();
private:
virtual ~IOThreadSuspender() { }
NS_IMETHOD Run() override;
Monitor mMon;
};
RefPtr<IOThreadSuspender> mActiveIOSuspender;
};
template<class T>

Просмотреть файл

@ -11,6 +11,7 @@ XPIDL_SOURCES += [
'nsICacheStorage.idl',
'nsICacheStorageService.idl',
'nsICacheStorageVisitor.idl',
'nsICacheTesting.idl',
]
XPIDL_MODULE = 'necko_cache2'

Просмотреть файл

@ -13,7 +13,7 @@ interface nsICacheStorageConsumptionObserver;
/**
* Provides access to particual cache storages of the network URI cache.
*/
[scriptable, uuid(9c9dc1d6-533e-4716-9ad8-11e08c3763b3)]
[scriptable, uuid(ae29c44b-fbc3-4552-afaf-0a157ce771e7)]
interface nsICacheStorageService : nsISupports
{
/**
@ -42,6 +42,13 @@ interface nsICacheStorageService : nsISupports
nsICacheStorage diskCacheStorage(in nsILoadContextInfo aLoadContextInfo,
in bool aLookupAppCache);
/**
* Get storage where entries will be written to disk and marked as pinned.
* These pinned entries are immune to over limit eviction and call of clear()
* on this service.
*/
nsICacheStorage pinningCacheStorage(in nsILoadContextInfo aLoadContextInfo);
/**
* Get storage for a specified application cache obtained using some different
* mechanism.

Просмотреть файл

@ -0,0 +1,17 @@
/* 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 "nsISupports.idl"
/**
* This is an internal interface used only for testing purposes.
*
* THIS IS NOT AN API TO BE USED BY EXTENSIONS! ONLY USED BY MOZILLA TESTS.
*/
[scriptable, builtinclass, uuid(4e8ba935-92e1-4a74-944b-b1a2f02a7480)]
interface nsICacheTesting : nsISupports
{
void suspendCacheIOThread(in uint32_t aLevel);
void resumeCacheIOThread();
};

Просмотреть файл

@ -262,6 +262,7 @@ nsHttpChannel::nsHttpChannel()
, mConcurentCacheAccess(0)
, mIsPartialRequest(0)
, mHasAutoRedirectVetoNotifier(0)
, mPinCacheContent(0)
, mIsPackagedAppResource(0)
, mIsCorsPreflightDone(0)
, mPushedStream(nullptr)
@ -2984,6 +2985,10 @@ nsHttpChannel::OpenCacheEntry(bool isHttps)
rv = cacheStorageService->MemoryCacheStorage(info, // ? choose app cache as well...
getter_AddRefs(cacheStorage));
}
else if (mPinCacheContent) {
rv = cacheStorageService->PinningCacheStorage(info,
getter_AddRefs(cacheStorage));
}
else {
rv = cacheStorageService->DiskCacheStorage(info,
!mPostID && (mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE)),
@ -6434,6 +6439,26 @@ nsHttpChannel::SetCacheOnlyMetadata(bool aOnlyMetadata)
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetPin(bool *aPin)
{
NS_ENSURE_ARG(aPin);
*aPin = mPinCacheContent;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetPin(bool aPin)
{
LOG(("nsHttpChannel::SetPin [this=%p pin=%d]\n",
this, aPin));
ENSURE_CALLED_BEFORE_CONNECT();
mPinCacheContent = aPin;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIResumableChannel
//-----------------------------------------------------------------------------

Просмотреть файл

@ -497,6 +497,9 @@ private:
uint32_t mIsPartialRequest : 1;
// true iff there is AutoRedirectVetoNotifier on the stack
uint32_t mHasAutoRedirectVetoNotifier : 1;
// consumers set this to true to use cache pinning, this has effect
// only when the channel is in an app context (load context has an appid)
uint32_t mPinCacheContent : 1;
// Whether fetching the content is meant to be handled by the
// packaged app service, which behaves like a caching layer.
// Upon successfully fetching the package, the resource will be placed in

Просмотреть файл

@ -50,6 +50,7 @@ function getCacheStorage(where, lci, appcache)
case "disk": return svc.diskCacheStorage(lci, false);
case "memory": return svc.memoryCacheStorage(lci);
case "appcache": return svc.appCacheStorage(lci, appcache);
case "pin": return svc.pinningCacheStorage(lci);
}
return null;
}

Просмотреть файл

@ -49,6 +49,8 @@ const DONTFILL = 1 << 13;
const DONTSETVALID = 1 << 14;
// Notify before checking the data, useful for proper callback ordering checks
const NOTIFYBEFOREREAD = 1 << 15;
// It's allowed to not get an existing entry (result of opening is undetermined)
const MAYBE_NEW = 1 << 16;
var log_c2 = true;
function LOG_C2(o, m)
@ -150,6 +152,10 @@ OpenCallback.prototype =
},
onCacheEntryAvailable: function(entry, isnew, appCache, status)
{
if ((this.behavior & MAYBE_NEW) && isnew) {
this.behavior |= NEW;
}
LOG_C2(this, "onCacheEntryAvailable, " + this.behavior);
do_check_true(!this.onAvailPassed);
this.onAvailPassed = true;
@ -207,9 +213,13 @@ OpenCallback.prototype =
}
do_execute_soon(function() { // emulate more network latency
if (self.behavior & DOOMED) {
LOG_C2(self, "checking doom state");
try {
var os = entry.openOutputStream(0);
do_check_true(false);
// Unfortunately, in the undetermined state we cannot even check whether the entry
// is actually doomed or not.
os.close();
do_check_true(!!(self.behavior & MAYBE_NEW));
} catch (ex) {
do_check_true(true);
}
@ -258,9 +268,9 @@ OpenCallback.prototype =
{
LOG_C2(this, "selfCheck");
do_check_true(this.onCheckPassed);
do_check_true(this.onCheckPassed || (this.behavior & MAYBE_NEW));
do_check_true(this.onAvailPassed);
do_check_true(this.onDataCheckPassed);
do_check_true(this.onDataCheckPassed || (this.behavior & MAYBE_NEW));
},
throwAndNotify: function(entry)
{
@ -386,6 +396,10 @@ MultipleCallbacks.prototype =
else
this.goon();
}
},
add: function()
{
++this.pending;
}
}

Просмотреть файл

@ -0,0 +1,28 @@
function run_test()
{
do_get_profile();
// Open for write, write
asyncOpenCacheEntry("http://a/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, LoadContextInfo.default,
new OpenCallback(NEW|WAITFORWRITE, "a1m", "a1d", function(entry) {
// Open for read and check
asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
// Now clear the whole cache
get_cache_service().clear();
// The pinned entry should be intact
asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
finish_cache2_test();
})
);
})
);
})
);
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,34 @@
function run_test()
{
do_get_profile();
var lci = LoadContextInfo.default;
// Open a pinned entry for write, write
asyncOpenCacheEntry("http://a/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
new OpenCallback(NEW|WAITFORWRITE, "a1m", "a1d", function(entry) {
// Now clear the disk storage, that should leave the pinned entry in the cache
var diskStorage = getCacheStorage("disk", lci);
diskStorage.asyncEvictStorage(null);
// Open for read and check, it should still be there
asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
// Now clear the pinning storage, entry should be gone
var pinningStorage = getCacheStorage("pin", lci);
pinningStorage.asyncEvictStorage(null);
asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
new OpenCallback(NEW, "", "", function(entry) {
finish_cache2_test();
})
);
})
);
})
);
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,129 @@
/*
This is a complex test checking the internal "deferred doom" functionality in both CacheEntry and CacheFileHandle.
- We create a batch of 10 non-pinned and 10 pinned entries, write something to them.
- Then we purge them from memory, so they have to reload from disk.
- After that the IO thread is suspended not to process events on the READ (3) level. This forces opening operation and eviction
sync operations happen before we know actual pinning status of already cached entries.
- We async-open the same batch of the 10+10 entries again, all should open as existing with the expected, previously stored
content
- After all these entries are made to open, we clear the cache. This does some synchronous operations on the entries
being open and also on the handles being in an already open state (but before the entry metadata has started to be read.)
Expected is to leave the pinned entries only.
- Now, we resume the IO thread, so it start reading. One could say this is a hack, but this can very well happen in reality
on slow disk or when a large number of entries is about to be open at once. Suspending the IO thread is just doing this
simulation is a fully deterministic way and actually very easily and elegantly.
- After the resume we want to open all those 10+10 entries once again (no purgin involved this time.). It is expected
to open all the pinning entries intact and loose all the non-pinned entries (get them as new and empty again.)
*/
const kENTRYCOUNT = 10;
function log_(msg) { if (true) dump(">>>>>>>>>>>>> " + msg + "\n"); }
function run_test()
{
do_get_profile();
var lci = LoadContextInfo.default;
var testingInterface = get_cache_service().QueryInterface(Ci.nsICacheTesting);
do_check_true(testingInterface);
var mc = new MultipleCallbacks(1, function() {
// (2)
mc = new MultipleCallbacks(1, finish_cache2_test);
// Release all references to cache entries so that they can be purged
// Calling gc() four times is needed to force it to actually release
// entries that are obviously unreferenced. Yeah, I know, this is wacky...
gc();
gc();
do_execute_soon(() => {
gc();
gc();
log_("purging");
// Invokes cacheservice:purge-memory-pools when done.
get_cache_service().purgeFromMemory(Ci.nsICacheStorageService.PURGE_EVERYTHING); // goes to (3)
});
}, true);
// (1), here we start
var i;
for (i = 0; i < kENTRYCOUNT; ++i) {
log_("first set of opens");
// Callbacks 1-20
mc.add();
asyncOpenCacheEntry("http://pinned" + i + "/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
new OpenCallback(NEW|WAITFORWRITE, "m" + i, "p" + i, function(entry) { mc.fired(); }));
mc.add();
asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
new OpenCallback(NEW|WAITFORWRITE, "m" + i, "d" + i, function(entry) { mc.fired(); }));
}
mc.fired(); // Goes to (2)
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
os.addObserver({
observe: function(subject, topic, data)
{
// (3)
log_("after purge, second set of opens");
// Prevent the I/O thread from reading the data. We first want to schedule clear of the cache.
// This deterministically emulates a slow hard drive.
testingInterface.suspendCacheIOThread(3);
// All entries should load
// Callbacks 21-40
for (i = 0; i < kENTRYCOUNT; ++i) {
mc.add();
asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
// Unfortunately we cannot ensure that entries existing in the cache will be delivered to the consumer
// when soon after are evicted by some cache API call. It's better to not ensure getting an entry
// than allowing to get an entry that was just evicted from the cache. Entries may be delievered
// as new, but are already doomed. Output stream cannot be openned, or the file handle is already
// writing to a doomed file.
//
// The API now just ensures that entries removed by any of the cache eviction APIs are never more
// available to consumers.
mc.add();
asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
new OpenCallback(MAYBE_NEW|DOOMED, "m" + i, "d" + i, function(entry) { mc.fired(); }));
}
log_("clearing");
// Now clear everything except pinned, all entries are in state of reading
get_cache_service().clear();
log_("cleared");
// Resume reading the cache data, only now the pinning status on entries will be discovered,
// the deferred dooming code will trigger.
testingInterface.resumeCacheIOThread();
log_("third set of opens");
// Now open again. Pinned entries should be there, disk entries should be the renewed entries.
// Callbacks 41-60
for (i = 0; i < kENTRYCOUNT; ++i) {
mc.add();
asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
mc.add();
asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
new OpenCallback(NEW, "m2" + i, "d2" + i, function(entry) { mc.fired(); }));
}
mc.fired(); // Finishes this test
}
}, "cacheservice:purge-memory-pools", false);
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,107 @@
/*
This test exercises the CacheFileContextEvictor::WasEvicted API and code using it.
- We store 10+10 (pinned and non-pinned) entries to the cache, wait for them being written.
- Then we purge the memory pools.
- Now the IO thread is suspended on the EVICT (8) level to prevent actual deletion of the files.
- Index is disabled.
- We do clear() of the cache, this creates the "ce_*" file and posts to the EVICT level
the eviction loop mechanics.
- We open again those 10+10 entries previously stored.
- IO is resumed
- We expect to get all the pinned and
loose all the non-pinned (common) entries.
*/
const kENTRYCOUNT = 10;
function log_(msg) { if (true) dump(">>>>>>>>>>>>> " + msg + "\n"); }
function run_test()
{
do_get_profile();
var lci = LoadContextInfo.default;
var testingInterface = get_cache_service().QueryInterface(Ci.nsICacheTesting);
do_check_true(testingInterface);
var mc = new MultipleCallbacks(1, function() {
// (2)
mc = new MultipleCallbacks(1, finish_cache2_test);
// Release all references to cache entries so that they can be purged
// Calling gc() four times is needed to force it to actually release
// entries that are obviously unreferenced. Yeah, I know, this is wacky...
gc();
gc();
do_execute_soon(() => {
gc();
gc();
log_("purging");
// Invokes cacheservice:purge-memory-pools when done.
get_cache_service().purgeFromMemory(Ci.nsICacheStorageService.PURGE_EVERYTHING); // goes to (3)
});
}, true);
// (1), here we start
var i;
for (i = 0; i < kENTRYCOUNT; ++i) {
log_("first set of opens");
// Callbacks 1-20
mc.add();
asyncOpenCacheEntry("http://pinned" + i + "/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
new OpenCallback(NEW|WAITFORWRITE, "m" + i, "p" + i, function(entry) { mc.fired(); }));
mc.add();
asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
new OpenCallback(NEW|WAITFORWRITE, "m" + i, "d" + i, function(entry) { mc.fired(); }));
}
mc.fired(); // Goes to (2)
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
os.addObserver({
observe: function(subject, topic, data)
{
// (3)
log_("after purge");
// Prevent the I/O thread from evicting physically the data. We first want to re-open the entries.
// This deterministically emulates a slow hard drive.
testingInterface.suspendCacheIOThread(8);
log_("clearing");
// Now clear everything except pinned. Stores the "ce_*" file and schedules background eviction.
get_cache_service().clear();
log_("cleared");
log_("second set of opens");
// Now open again. Pinned entries should be there, disk entries should be the renewed entries.
// Callbacks 21-40
for (i = 0; i < kENTRYCOUNT; ++i) {
mc.add();
asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
mc.add();
asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
new OpenCallback(NEW, "m2" + i, "d2" + i, function(entry) { mc.fired(); }));
}
// Resume IO, this will just pop-off the CacheFileContextEvictor::EvictEntries() because of
// an early check on CacheIOThread::YieldAndRerun() in that method.
// CacheFileIOManager::OpenFileInternal should now run and CacheFileContextEvictor::WasEvicted
// should be checked on.
testingInterface.resumeCacheIOThread();
mc.fired(); // Finishes this test
}
}, "cacheservice:purge-memory-pools", false);
do_test_pending();
}

Просмотреть файл

@ -99,7 +99,8 @@ function run_test() {
function doneFirstLoad(req, buffer, expected) {
// Load it again, make sure it hits the cache
var chan = makeChan(URL, 0, false);
var nc = req.notificationCallbacks.getInterface(Ci.nsILoadContext);
var chan = makeChan(URL, nc.appId, nc.isInBrowserElement);
chan.asyncOpen(new ChannelListener(doneSecondLoad, expected), null);
}

Просмотреть файл

@ -70,8 +70,12 @@ skip-if = true
[test_cache2-28a-OPEN_SECRETLY.js]
# This test will be fixed in bug 1067931
skip-if = true
[test_cache2-28-concurrent_read_resumable_entry_size_zero.js]
[test_cache2-29-concurrent_read_non-resumable_entry_size_zero.js]
[test_cache2-29a-concurrent_read_resumable_entry_size_zero.js]
[test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js]
[test_cache2-30a-entry-pinning.js]
[test_cache2-30b-pinning-storage-clear.js]
[test_cache2-30c-pinning-deferred-doom.js]
[test_cache2-30d-pinning-WasEvicted-API.js]
[test_partial_response_entry_size_smart_shrink.js]
[test_304_responses.js]
[test_421.js]