/* 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/. */
class FrecencyComparator { public: bool Equals(const RefPtr<CacheIndexRecordWrapper>& a, const RefPtr<CacheIndexRecordWrapper>& b) const { if (!a || !b) { returnfalse;
}
return a->Get()->mFrecency == b->Get()->mFrecency;
} bool LessThan(const RefPtr<CacheIndexRecordWrapper>& a, const RefPtr<CacheIndexRecordWrapper>& b) const { // Removed (=null) entries must be at the end of the array. if (!a) { returnfalse;
} if (!b) { returntrue;
}
// Place entries with frecency 0 at the end of the non-removed entries. if (a->Get()->mFrecency == 0) { returnfalse;
} if (b->Get()->mFrecency == 0) { returntrue;
}
// used to dispatch a wrapper deletion the caller's thread // cannot be used on IOThread after shutdown begins class DeleteCacheIndexRecordWrapper : public Runnable {
CacheIndexRecordWrapper* mWrapper;
// if somehow the item is still in the frecency array, remove it
RefPtr<CacheIndex> index = CacheIndex::gInstance; if (index) { bool found = index->mFrecencyArray.RecordExistedUnlocked(mWrapper); if (found) {
LOG(
("DeleteCacheIndexRecordWrapper::Run() - \
record wrapper found in frecency array during deletion"));
index->mFrecencyArray.RemoveRecord(mWrapper, lock);
}
}
delete mWrapper; return NS_OK;
}
};
void CacheIndexRecordWrapper::DispatchDeleteSelfToCurrentThread() { // Dispatch during shutdown will not trigger DeleteCacheIndexRecordWrapper
nsCOMPtr<nsIRunnable> event = new DeleteCacheIndexRecordWrapper(this);
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(event));
}
CacheIndexRecordWrapper::~CacheIndexRecordWrapper() { #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
CacheIndex::sLock.AssertCurrentThreadOwns();
RefPtr<CacheIndex> index = CacheIndex::gInstance; if (index) { bool found = index->mFrecencyArray.RecordExistedUnlocked(this);
MOZ_DIAGNOSTIC_ASSERT(!found);
} #endif
}
/** * This helper class is responsible for keeping CacheIndex::mIndexStats and * CacheIndex::mFrecencyArray up to date.
*/ class MOZ_RAII CacheIndexEntryAutoManage { public:
CacheIndexEntryAutoManage(const SHA1Sum::Hash* aHash, CacheIndex* aIndex, const StaticMutexAutoLock& aProofOfLock)
MOZ_REQUIRES(CacheIndex::sLock)
: mIndex(aIndex), mProofOfLock(aProofOfLock) {
mHash = aHash; const CacheIndexEntry* entry = FindEntry();
mIndex->mIndexStats.BeforeChange(entry); if (entry && entry->IsInitialized() && !entry->IsRemoved()) {
mOldRecord = entry->mRec;
mOldFrecency = entry->mRec->Get()->mFrecency;
}
}
if (entry && !mOldRecord) {
mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
mIndex->AddRecordToIterators(entry->mRec, mProofOfLock);
} elseif (!entry && mOldRecord) {
mIndex->mFrecencyArray.RemoveRecord(mOldRecord, mProofOfLock);
mIndex->RemoveRecordFromIterators(mOldRecord, mProofOfLock);
} elseif (entry && mOldRecord) { if (entry->mRec != mOldRecord) { // record has a different address, we have to replace it
mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec, mProofOfLock);
if (entry->mRec->Get()->mFrecency == mOldFrecency) { // If frecency hasn't changed simply replace the pointer
mIndex->mFrecencyArray.ReplaceRecord(mOldRecord, entry->mRec,
mProofOfLock);
} else { // Remove old pointer and insert the new one at the end of the array
mIndex->mFrecencyArray.RemoveRecord(mOldRecord, mProofOfLock);
mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
}
} elseif (entry->mRec->Get()->mFrecency != mOldFrecency) { // Move the element at the end of the array
mIndex->mFrecencyArray.RemoveRecord(entry->mRec, mProofOfLock);
mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
}
} else { // both entries were removed or not initialized, do nothing
}
}
// We cannot rely on nsTHashtable::GetEntry() in case we are removing entries // while iterating. Destructor is called before the entry is removed. Caller // must call one of following methods to skip lookup in the hashtable. void DoNotSearchInIndex() { mDoNotSearchInIndex = true; } void DoNotSearchInUpdates() { mDoNotSearchInUpdates = true; }
switch (mIndex->mState) { case CacheIndex::READING: case CacheIndex::WRITING: if (!mDoNotSearchInUpdates) {
entry = mIndex->mPendingUpdates.GetEntry(*mHash);
}
[[fallthrough]]; case CacheIndex::BUILDING: case CacheIndex::UPDATING: case CacheIndex::READY: if (!entry && !mDoNotSearchInIndex) {
entry = mIndex->mIndex.GetEntry(*mHash);
} break; case CacheIndex::INITIAL: case CacheIndex::SHUTDOWN: default:
MOZ_ASSERT(false, "Unexpected state!");
}
// PreShutdownInternal() will be executed before any queued event on INDEX // level. That's OK since we don't want to wait for any operation in progess.
rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); if (NS_FAILED(rv)) {
NS_WARNING("CacheIndex::PreShutdown() - Can't dispatch event");
LOG(("CacheIndex::PreShutdown() - Can't dispatch event")); return rv;
}
if (mUpdateTimer) {
mUpdateTimer->Cancel();
mUpdateTimer = nullptr;
}
switch (mState) { case WRITING:
FinishWrite(false, lock); break; case READY: // nothing to do, write the journal in Shutdown() break; case READING:
FinishRead(false, lock); break; case BUILDING: case UPDATING:
FinishUpdate(false, lock); break; default:
MOZ_ASSERT(false, "Implement me!");
}
// We should end up in READY state
MOZ_ASSERT(mState == READY);
}
if (!index->IsIndexUsable()) { return NS_ERROR_NOT_AVAILABLE;
}
// Getters in CacheIndexStats assert when mStateLogged is true since the // information is incomplete between calls to BeforeChange() and AfterChange() // (i.e. while CacheIndexEntryAutoManage exists). We need to check whether // non-fresh entries exists outside the scope of CacheIndexEntryAutoManage. bool updateIfNonFreshEntriesExist = false;
if (entry && !entryRemoved) { // Found entry in index that shouldn't exist.
if (entry->IsFresh()) { // Someone removed the file on disk while FF is running. Update // process can fix only non-fresh entries (i.e. entries that were not // added within this session). Start update only if we have such // entries. // // TODO: This should be very rare problem. If it turns out not to be // true, change the update process so that it also iterates all // initialized non-empty entries and checks whether the file exists.
updateIfNonFreshEntriesExist = true;
} elseif (index->mState == READY) { // Index is outdated, update it.
LOG(
("CacheIndex::AddEntry() - Found entry that shouldn't exist, " "update is needed"));
index->mIndexNeedsUpdate = true;
} else { // We cannot be here when building index since all entries are fresh // during building.
MOZ_ASSERT(index->mState == UPDATING);
}
}
if ((updated && !updatedRemoved) ||
(!updated && entry && !entryRemoved && entry->IsFresh())) { // Fresh entry found, so the file was removed outside FF
LOG(
("CacheIndex::AddEntry() - Cache file was removed outside FF " "process!"));
updateIfNonFreshEntriesExist = true;
} elseif (!updated && entry && !entryRemoved) { if (index->mState == WRITING) {
LOG(
("CacheIndex::AddEntry() - Found entry that shouldn't exist, " "update is needed"));
index->mIndexNeedsUpdate = true;
} // Ignore if state is READING since the index information is partial
}
if (!entry || entryRemoved) { if (entryRemoved && entry->IsFresh()) { // This could happen only if somebody copies files to the entries // directory while FF is running.
LOG(
("CacheIndex::EnsureEntryExists() - Cache file was added outside " "FF process! Update is needed."));
index->mIndexNeedsUpdate = true;
} elseif (index->mState == READY ||
(entryRemoved && !entry->IsFresh())) { // Removed non-fresh entries can be present as a result of // MergeJournal()
LOG(
("CacheIndex::EnsureEntryExists() - Didn't find entry that should" " exist, update is needed"));
index->mIndexNeedsUpdate = true;
}
if (updatedRemoved || (!updated && entryRemoved && entry->IsFresh())) { // Fresh information about missing entry found. This could happen only // if somebody copies files to the entries directory while FF is // running.
LOG(
("CacheIndex::EnsureEntryExists() - Cache file was added outside " "FF process! Update is needed."));
index->mIndexNeedsUpdate = true;
} elseif (!updated && (!entry || entryRemoved)) { if (index->mState == WRITING) {
LOG(
("CacheIndex::EnsureEntryExists() - Didn't find entry that should" " exist, update is needed"));
index->mIndexNeedsUpdate = true;
} // Ignore if state is READING since the index information is partial
}
// We don't need entryRemoved and updatedRemoved info anymore if (entryRemoved) entry = nullptr; if (updatedRemoved) updated = nullptr;
if (updated) {
updated->MarkFresh();
} else { if (!entry) { // Create a new entry
updated = index->mPendingUpdates.PutEntry(*aHash);
updated->InitNew();
updated->MarkFresh();
updated->MarkDirty();
} else { if (!entry->IsFresh()) { // To mark the entry fresh we must make a copy of index entry // since the index is read-only.
updated = index->mPendingUpdates.PutEntry(*aHash);
*updated = *entry;
updated->MarkFresh();
}
}
}
}
}
if (!entry) {
LOG(("CacheIndex::InitEntry() - Entry was not found in mIndex!"));
NS_WARNING(
("CacheIndex::InitEntry() - Entry was not found in mIndex!")); return NS_ERROR_UNEXPECTED;
}
if (IsCollision(entry, aOriginAttrsHash, aAnonymous)) {
index->mIndexNeedsUpdate = true; // TODO Does this really help in case of collision?
reinitEntry = true;
} else { if (entry->IsInitialized()) { return NS_OK;
}
}
} else {
updated = index->mPendingUpdates.GetEntry(*aHash);
DebugOnly<bool> removed = updated && updated->IsRemoved();
if (!updated && !entry) {
LOG(
("CacheIndex::InitEntry() - Entry was found neither in mIndex nor " "in mPendingUpdates!"));
NS_WARNING(
("CacheIndex::InitEntry() - Entry was found neither in " "mIndex nor in mPendingUpdates!")); return NS_ERROR_UNEXPECTED;
}
// make a copy of a read-only entry
updated = index->mPendingUpdates.PutEntry(*aHash);
*updated = *entry;
}
}
if (reinitEntry) { // There is a collision and we are going to rewrite this entry. Initialize // it as a new entry. if (updated) {
updated->InitNew();
updated->MarkFresh();
} else {
entry->InitNew();
entry->MarkFresh();
}
}
if (!entry || entryRemoved) { if (entryRemoved && entry->IsFresh()) { // This could happen only if somebody copies files to the entries // directory while FF is running.
LOG(
("CacheIndex::RemoveEntry() - Cache file was added outside FF " "process! Update is needed."));
index->mIndexNeedsUpdate = true;
} elseif (index->mState == READY ||
(entryRemoved && !entry->IsFresh())) { // Removed non-fresh entries can be present as a result of // MergeJournal()
LOG(
("CacheIndex::RemoveEntry() - Didn't find entry that should exist" ", update is needed"));
index->mIndexNeedsUpdate = true;
}
} else { if (entry) { if (!entry->IsDirty() && entry->IsFileEmpty()) {
index->mIndex.RemoveEntry(entry);
entry = nullptr;
} else {
entry->MarkRemoved();
entry->MarkDirty();
entry->MarkFresh();
}
}
}
} else { // WRITING, READING
CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash); bool updatedRemoved = updated && updated->IsRemoved();
if (updatedRemoved || (!updated && entryRemoved && entry->IsFresh())) { // Fresh information about missing entry found. This could happen only // if somebody copies files to the entries directory while FF is // running.
LOG(
("CacheIndex::RemoveEntry() - Cache file was added outside FF " "process! Update is needed."));
index->mIndexNeedsUpdate = true;
} elseif (!updated && (!entry || entryRemoved)) { if (index->mState == WRITING) {
LOG(
("CacheIndex::RemoveEntry() - Didn't find entry that should exist" ", update is needed"));
index->mIndexNeedsUpdate = true;
} // Ignore if state is READING since the index information is partial
}
if (!updated) {
updated = index->mPendingUpdates.PutEntry(*aHash);
updated->InitNew();
}
if (!entry) {
LOG(("CacheIndex::UpdateEntry() - Entry was not found in mIndex!"));
NS_WARNING(
("CacheIndex::UpdateEntry() - Entry was not found in mIndex!")); return NS_ERROR_UNEXPECTED;
}
if (!updated) { if (!entry) {
LOG(
("CacheIndex::UpdateEntry() - Entry was found neither in mIndex " "nor in mPendingUpdates!"));
NS_WARNING(
("CacheIndex::UpdateEntry() - Entry was found neither in " "mIndex nor in mPendingUpdates!")); return NS_ERROR_UNEXPECTED;
}
// make a copy of a read-only entry
updated = index->mPendingUpdates.PutEntry(*aHash);
*updated = *entry;
}
// Doom index and journal handles but don't null them out since this will be // done in FinishWrite/FinishRead methods. if (index->mIndexHandle) {
CacheFileIOManager::DoomFile(index->mIndexHandle, nullptr);
} else { // We don't have a handle to index file, so get the file here, but delete // it outside the lock. Ignore the result since this is not fatal.
index->GetFile(nsLiteralCString(INDEX_NAME), getter_AddRefs(file));
}
if (index->mJournalHandle) {
CacheFileIOManager::DoomFile(index->mJournalHandle, nullptr);
}
switch (index->mState) { case WRITING:
index->FinishWrite(false, lock); break; case READY: // nothing to do break; case READING:
index->FinishRead(false, lock); break; case BUILDING: case UPDATING:
index->FinishUpdate(false, lock); break; default:
MOZ_ASSERT(false, "Unexpected state!");
}
// We should end up in READY state
MOZ_ASSERT(index->mState == READY);
// There should not be any handle
MOZ_ASSERT(!index->mIndexHandle);
MOZ_ASSERT(!index->mJournalHandle);
if (!index->IsIndexUsable()) { return NS_ERROR_NOT_AVAILABLE;
}
const CacheIndexEntry* entry = nullptr;
switch (index->mState) { case READING: case WRITING:
entry = index->mPendingUpdates.GetEntry(hash);
[[fallthrough]]; case BUILDING: case UPDATING: case READY: if (!entry) {
entry = index->mIndex.GetEntry(hash);
} break; case INITIAL: case SHUTDOWN:
MOZ_ASSERT(false, "Unexpected state!");
}
if ((index->mState == READY || index->mState == WRITING) &&
!index->mAsyncGetDiskConsumptionBlocked) {
LOG(("CacheIndex::AsyncGetDiskConsumption - calling immediately")); // Safe to call the callback under the lock, // we always post to the main thread.
observer->OnDiskConsumption(index->mIndexStats.Size() << 10); return NS_OK;
}
LOG(("CacheIndex::AsyncGetDiskConsumption - remembering callback")); // Will be called when the index get to the READY state.
index->mDiskConsumptionObservers.AppendElement(observer);
// Move forward with index re/building if it is pending
RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread(); if (ioThread) {
ioThread->Dispatch(
NS_NewRunnableFunction("net::CacheIndex::AsyncGetDiskConsumption",
[]() -> void {
StaticMutexAutoLock lock(sLock);
RefPtr<CacheIndex> index = gInstance; if (index && index->mUpdateTimer) {
index->mUpdateTimer->Cancel();
index->DelayedUpdateLocked(lock);
}
}),
CacheIOThread::INDEX);
}
if (update->IsRemoved()) { if (entry) { if (entry->IsRemoved()) {
MOZ_ASSERT(entry->IsFresh());
MOZ_ASSERT(entry->IsDirty());
} elseif (!entry->IsDirty() && entry->IsFileEmpty()) { // Entries with empty file are not stored in index on disk. Just // remove the entry, but only in case the entry is not dirty, i.e. // the entry file was empty when we wrote the index.
mIndex.RemoveEntry(entry);
entry = nullptr;
} else {
entry->MarkRemoved();
entry->MarkDirty();
entry->MarkFresh();
}
}
} elseif (entry) { // Some information in mIndex can be newer than in mPendingUpdates (see // bug 1074832). This will copy just those values that were really // updated.
update->ApplyUpdate(entry);
} else { // There is no entry in mIndex, copy all information from // mPendingUpdates to mIndex.
entry = mIndex.PutEntry(*update->Hash());
*entry = *update;
}
}
iter.Remove();
}
mIndexFileOpener = new FileOpenHelper(this);
rv = CacheFileIOManager::OpenFile(
nsLiteralCString(TEMP_INDEX_NAME),
CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::CREATE,
mIndexFileOpener); if (NS_FAILED(rv)) {
LOG(("CacheIndex::WriteIndexToDisk() - Can't open file [rv=0x%08" PRIx32 "]", static_cast<uint32_t>(rv)));
FinishWrite(false, aProofOfLock); return;
}
// Write index header to a buffer, it will be written to disk together with // records in WriteRecords() once we open the file successfully.
AllocBuffer();
mRWHash = new CacheHash();
mRWBufPos = 0; // index version
NetworkEndian::writeUint32(mRWBuf + mRWBufPos, kIndexVersion);
mRWBufPos += sizeof(uint32_t); // timestamp
NetworkEndian::writeUint32(mRWBuf + mRWBufPos, static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC));
mRWBufPos += sizeof(uint32_t); // dirty flag
NetworkEndian::writeUint32(mRWBuf + mRWBufPos, 1);
mRWBufPos += sizeof(uint32_t); // amount of data written to the cache
NetworkEndian::writeUint32(mRWBuf + mRWBufPos, static_cast<uint32_t>(mTotalBytesWritten >> 10));
mRWBufPos += sizeof(uint32_t);
// If there is write operation pending we must be cancelling writing of the // index when shutting down or removing the whole index.
MOZ_ASSERT(!mRWPending || (!aSucceeded && (mShuttingDown || mRemovingAll)));
if (entry->IsRemoved()) {
emng.DoNotSearchInIndex();
remove = true;
} elseif (entry->IsDirty()) {
entry->ClearDirty();
}
} if (remove) {
iter.Remove();
}
}
mIndexOnDiskIsValid = true;
} else { if (mIndexFileOpener) { // If opening of the file is still in progress (e.g. WRITE process was // canceled by RemoveAll()) then we need to cancel the opener to make sure // that OnFileOpenedInternal() won't be called.
mIndexFileOpener->Cancel();
mIndexFileOpener = nullptr;
}
}
// Seek to dirty flag in the index header and clear it.
static_assert(2 * sizeof(uint32_t) == offsetof(CacheIndexHeader, mIsDirty), "Unexpected offset of CacheIndexHeader::mIsDirty");
int64_t offset = PR_Seek64(fd, 2 * sizeof(uint32_t), PR_SEEK_SET); if (offset == -1) {
PR_Close(fd); return NS_ERROR_FAILURE;
}
// Mark index dirty. The buffer will be freed by CacheFileIOManager.
CacheFileIOManager::WriteWithoutCallback(
mIndexHandle, 2 * sizeof(uint32_t), reinterpret_cast<char*>(isDirty), sizeof(uint32_t), true, false);
}
pos += sizeof(uint32_t);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.