michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "CacheIndex.h" michael@0: michael@0: #include "CacheLog.h" michael@0: #include "CacheFileIOManager.h" michael@0: #include "CacheFileMetadata.h" michael@0: #include "CacheIndexIterator.h" michael@0: #include "CacheIndexContextIterator.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsIDirectoryEnumerator.h" michael@0: #include "nsISizeOf.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "prinrval.h" michael@0: #include "nsIFile.h" michael@0: #include "nsITimer.h" michael@0: #include "mozilla/AutoRestore.h" michael@0: #include michael@0: michael@0: michael@0: #define kMinUnwrittenChanges 300 michael@0: #define kMinDumpInterval 20000 // in milliseconds michael@0: #define kMaxBufSize 16384 michael@0: #define kIndexVersion 0x00000001 michael@0: #define kUpdateIndexStartDelay 50000 // in milliseconds michael@0: michael@0: const char kIndexName[] = "index"; michael@0: const char kTempIndexName[] = "index.tmp"; michael@0: const char kJournalName[] = "index.log"; michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: /** michael@0: * This helper class is responsible for keeping CacheIndex::mIndexStats, michael@0: * CacheIndex::mFrecencyArray and CacheIndex::mExpirationArray up to date. michael@0: */ michael@0: class CacheIndexEntryAutoManage michael@0: { michael@0: public: michael@0: CacheIndexEntryAutoManage(const SHA1Sum::Hash *aHash, CacheIndex *aIndex) michael@0: : mIndex(aIndex) michael@0: , mOldRecord(nullptr) michael@0: , mOldFrecency(0) michael@0: , mOldExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME) michael@0: , mDoNotSearchInIndex(false) michael@0: , mDoNotSearchInUpdates(false) michael@0: { michael@0: mIndex->AssertOwnsLock(); michael@0: michael@0: mHash = aHash; michael@0: CacheIndexEntry *entry = FindEntry(); michael@0: mIndex->mIndexStats.BeforeChange(entry); michael@0: if (entry && entry->IsInitialized() && !entry->IsRemoved()) { michael@0: mOldRecord = entry->mRec; michael@0: mOldFrecency = entry->mRec->mFrecency; michael@0: mOldExpirationTime = entry->mRec->mExpirationTime; michael@0: } michael@0: } michael@0: michael@0: ~CacheIndexEntryAutoManage() michael@0: { michael@0: mIndex->AssertOwnsLock(); michael@0: michael@0: CacheIndexEntry *entry = FindEntry(); michael@0: mIndex->mIndexStats.AfterChange(entry); michael@0: if (!entry || !entry->IsInitialized() || entry->IsRemoved()) { michael@0: entry = nullptr; michael@0: } michael@0: michael@0: if (entry && !mOldRecord) { michael@0: mIndex->InsertRecordToFrecencyArray(entry->mRec); michael@0: mIndex->InsertRecordToExpirationArray(entry->mRec); michael@0: mIndex->AddRecordToIterators(entry->mRec); michael@0: } else if (!entry && mOldRecord) { michael@0: mIndex->RemoveRecordFromFrecencyArray(mOldRecord); michael@0: mIndex->RemoveRecordFromExpirationArray(mOldRecord); michael@0: mIndex->RemoveRecordFromIterators(mOldRecord); michael@0: } else if (entry && mOldRecord) { michael@0: bool replaceFrecency = false; michael@0: bool replaceExpiration = false; michael@0: michael@0: if (entry->mRec != mOldRecord) { michael@0: // record has a different address, we have to replace it michael@0: replaceFrecency = replaceExpiration = true; michael@0: mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec); michael@0: } else { michael@0: if (entry->mRec->mFrecency == 0 && michael@0: entry->mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME) { michael@0: // This is a special case when we want to make sure that the entry is michael@0: // placed at the end of the lists even when the values didn't change. michael@0: replaceFrecency = replaceExpiration = true; michael@0: } else { michael@0: if (entry->mRec->mFrecency != mOldFrecency) { michael@0: replaceFrecency = true; michael@0: } michael@0: if (entry->mRec->mExpirationTime != mOldExpirationTime) { michael@0: replaceExpiration = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (replaceFrecency) { michael@0: mIndex->RemoveRecordFromFrecencyArray(mOldRecord); michael@0: mIndex->InsertRecordToFrecencyArray(entry->mRec); michael@0: } michael@0: if (replaceExpiration) { michael@0: mIndex->RemoveRecordFromExpirationArray(mOldRecord); michael@0: mIndex->InsertRecordToExpirationArray(entry->mRec); michael@0: } michael@0: } else { michael@0: // both entries were removed or not initialized, do nothing michael@0: } michael@0: } michael@0: michael@0: // We cannot rely on nsTHashtable::GetEntry() in case we are enumerating the michael@0: // entries and returning PL_DHASH_REMOVE. Destructor is called before the michael@0: // entry is removed. Caller must call one of following methods to skip michael@0: // lookup in the hashtable. michael@0: void DoNotSearchInIndex() { mDoNotSearchInIndex = true; } michael@0: void DoNotSearchInUpdates() { mDoNotSearchInUpdates = true; } michael@0: michael@0: private: michael@0: CacheIndexEntry * FindEntry() michael@0: { michael@0: CacheIndexEntry *entry = nullptr; michael@0: michael@0: switch (mIndex->mState) { michael@0: case CacheIndex::READING: michael@0: case CacheIndex::WRITING: michael@0: if (!mDoNotSearchInUpdates) { michael@0: entry = mIndex->mPendingUpdates.GetEntry(*mHash); michael@0: } michael@0: // no break michael@0: case CacheIndex::BUILDING: michael@0: case CacheIndex::UPDATING: michael@0: case CacheIndex::READY: michael@0: if (!entry && !mDoNotSearchInIndex) { michael@0: entry = mIndex->mIndex.GetEntry(*mHash); michael@0: } michael@0: break; michael@0: case CacheIndex::INITIAL: michael@0: case CacheIndex::SHUTDOWN: michael@0: default: michael@0: MOZ_ASSERT(false, "Unexpected state!"); michael@0: } michael@0: michael@0: return entry; michael@0: } michael@0: michael@0: const SHA1Sum::Hash *mHash; michael@0: nsRefPtr mIndex; michael@0: CacheIndexRecord *mOldRecord; michael@0: uint32_t mOldFrecency; michael@0: uint32_t mOldExpirationTime; michael@0: bool mDoNotSearchInIndex; michael@0: bool mDoNotSearchInUpdates; michael@0: }; michael@0: michael@0: class FileOpenHelper : public CacheFileIOListener michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: FileOpenHelper(CacheIndex* aIndex) michael@0: : mIndex(aIndex) michael@0: , mCanceled(false) michael@0: {} michael@0: michael@0: virtual ~FileOpenHelper() {} michael@0: michael@0: void Cancel() { michael@0: mIndex->AssertOwnsLock(); michael@0: mCanceled = true; michael@0: } michael@0: michael@0: private: michael@0: NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult); michael@0: NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, michael@0: nsresult aResult) { michael@0: MOZ_CRASH("FileOpenHelper::OnDataWritten should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, michael@0: nsresult aResult) { michael@0: MOZ_CRASH("FileOpenHelper::OnDataRead should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) { michael@0: MOZ_CRASH("FileOpenHelper::OnFileDoomed should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) { michael@0: MOZ_CRASH("FileOpenHelper::OnEOFSet should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) { michael@0: MOZ_CRASH("FileOpenHelper::OnFileRenamed should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsRefPtr mIndex; michael@0: bool mCanceled; michael@0: }; michael@0: michael@0: NS_IMETHODIMP FileOpenHelper::OnFileOpened(CacheFileHandle *aHandle, michael@0: nsresult aResult) michael@0: { michael@0: CacheIndexAutoLock lock(mIndex); michael@0: michael@0: if (mCanceled) { michael@0: if (aHandle) { michael@0: CacheFileIOManager::DoomFile(aHandle, nullptr); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: mIndex->OnFileOpenedInternal(this, aHandle, aResult); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(FileOpenHelper, CacheFileIOListener); michael@0: michael@0: michael@0: CacheIndex * CacheIndex::gInstance = nullptr; michael@0: michael@0: michael@0: NS_IMPL_ADDREF(CacheIndex) michael@0: NS_IMPL_RELEASE(CacheIndex) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(CacheIndex) michael@0: NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRunnable) michael@0: NS_INTERFACE_MAP_END_THREADSAFE michael@0: michael@0: michael@0: CacheIndex::CacheIndex() michael@0: : mLock("CacheFile.mLock") michael@0: , mState(INITIAL) michael@0: , mShuttingDown(false) michael@0: , mIndexNeedsUpdate(false) michael@0: , mRemovingAll(false) michael@0: , mIndexOnDiskIsValid(false) michael@0: , mDontMarkIndexClean(false) michael@0: , mIndexTimeStamp(0) michael@0: , mUpdateEventPending(false) michael@0: , mSkipEntries(0) michael@0: , mProcessEntries(0) michael@0: , mRWBuf(nullptr) michael@0: , mRWBufSize(0) michael@0: , mRWBufPos(0) michael@0: , mJournalReadSuccessfully(false) michael@0: { michael@0: LOG(("CacheIndex::CacheIndex [this=%p]", this)); michael@0: MOZ_COUNT_CTOR(CacheIndex); michael@0: MOZ_ASSERT(!gInstance, "multiple CacheIndex instances!"); michael@0: } michael@0: michael@0: CacheIndex::~CacheIndex() michael@0: { michael@0: LOG(("CacheIndex::~CacheIndex [this=%p]", this)); michael@0: MOZ_COUNT_DTOR(CacheIndex); michael@0: michael@0: ReleaseBuffer(); michael@0: } michael@0: michael@0: void michael@0: CacheIndex::Lock() michael@0: { michael@0: mLock.Lock(); michael@0: michael@0: MOZ_ASSERT(!mIndexStats.StateLogged()); michael@0: } michael@0: michael@0: void michael@0: CacheIndex::Unlock() michael@0: { michael@0: MOZ_ASSERT(!mIndexStats.StateLogged()); michael@0: michael@0: mLock.Unlock(); michael@0: } michael@0: michael@0: inline void michael@0: CacheIndex::AssertOwnsLock() michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::Init(nsIFile *aCacheDirectory) michael@0: { michael@0: LOG(("CacheIndex::Init()")); michael@0: michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (gInstance) { michael@0: return NS_ERROR_ALREADY_INITIALIZED; michael@0: } michael@0: michael@0: nsRefPtr idx = new CacheIndex(); michael@0: michael@0: CacheIndexAutoLock lock(idx); michael@0: michael@0: nsresult rv = idx->InitInternal(aCacheDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: idx.swap(gInstance); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::InitInternal(nsIFile *aCacheDirectory) michael@0: { michael@0: nsresult rv; michael@0: michael@0: rv = aCacheDirectory->Clone(getter_AddRefs(mCacheDirectory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mStartTime = TimeStamp::NowLoRes(); michael@0: michael@0: ReadIndexFromDisk(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::PreShutdown() michael@0: { michael@0: LOG(("CacheIndex::PreShutdown() [gInstance=%p]", gInstance)); michael@0: michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: LOG(("CacheIndex::PreShutdown() - [state=%d, indexOnDiskIsValid=%d, " michael@0: "dontMarkIndexClean=%d]", index->mState, index->mIndexOnDiskIsValid, michael@0: index->mDontMarkIndexClean)); michael@0: michael@0: LOG(("CacheIndex::PreShutdown() - Closing iterators.")); michael@0: for (uint32_t i = 0; i < index->mIterators.Length(); ) { michael@0: rv = index->mIterators[i]->CloseInternal(NS_ERROR_FAILURE); michael@0: if (NS_FAILED(rv)) { michael@0: // CacheIndexIterator::CloseInternal() removes itself from mIteratos iff michael@0: // it returns success. michael@0: LOG(("CacheIndex::PreShutdown() - Failed to remove iterator %p. " michael@0: "[rv=0x%08x]", rv)); michael@0: i++; michael@0: } michael@0: } michael@0: michael@0: index->mShuttingDown = true; michael@0: michael@0: if (index->mState == READY) { michael@0: return NS_OK; // nothing to do michael@0: } michael@0: michael@0: nsCOMPtr event; michael@0: event = NS_NewRunnableMethod(index, &CacheIndex::PreShutdownInternal); michael@0: michael@0: nsCOMPtr ioTarget = CacheFileIOManager::IOTarget(); michael@0: MOZ_ASSERT(ioTarget); michael@0: michael@0: // PreShutdownInternal() will be executed before any queued event on INDEX michael@0: // level. That's OK since we don't want to wait for any operation in progess. michael@0: // We need to interrupt it and save journal as quickly as possible. michael@0: rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("CacheIndex::PreShutdown() - Can't dispatch event"); michael@0: LOG(("CacheIndex::PreShutdown() - Can't dispatch event" )); michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CacheIndex::PreShutdownInternal() michael@0: { michael@0: CacheIndexAutoLock lock(this); michael@0: michael@0: LOG(("CacheIndex::PreShutdownInternal() - [state=%d, indexOnDiskIsValid=%d, " michael@0: "dontMarkIndexClean=%d]", mState, mIndexOnDiskIsValid, michael@0: mDontMarkIndexClean)); michael@0: michael@0: MOZ_ASSERT(mShuttingDown); michael@0: michael@0: if (mUpdateTimer) { michael@0: mUpdateTimer = nullptr; michael@0: } michael@0: michael@0: switch (mState) { michael@0: case WRITING: michael@0: FinishWrite(false); michael@0: break; michael@0: case READY: michael@0: // nothing to do, write the journal in Shutdown() michael@0: break; michael@0: case READING: michael@0: FinishRead(false); michael@0: break; michael@0: case BUILDING: michael@0: case UPDATING: michael@0: FinishUpdate(false); michael@0: break; michael@0: default: michael@0: MOZ_ASSERT(false, "Implement me!"); michael@0: } michael@0: michael@0: // We should end up in READY state michael@0: MOZ_ASSERT(mState == READY); michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::Shutdown() michael@0: { michael@0: LOG(("CacheIndex::Shutdown() [gInstance=%p]", gInstance)); michael@0: michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsRefPtr index; michael@0: index.swap(gInstance); michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: bool sanitize = CacheObserver::ClearCacheOnShutdown(); michael@0: michael@0: LOG(("CacheIndex::Shutdown() - [state=%d, indexOnDiskIsValid=%d, " michael@0: "dontMarkIndexClean=%d, sanitize=%d]", index->mState, michael@0: index->mIndexOnDiskIsValid, index->mDontMarkIndexClean, sanitize)); michael@0: michael@0: MOZ_ASSERT(index->mShuttingDown); michael@0: michael@0: EState oldState = index->mState; michael@0: index->ChangeState(SHUTDOWN); michael@0: michael@0: if (oldState != READY) { michael@0: LOG(("CacheIndex::Shutdown() - Unexpected state. Did posting of " michael@0: "PreShutdownInternal() fail?")); michael@0: } michael@0: michael@0: switch (oldState) { michael@0: case WRITING: michael@0: index->FinishWrite(false); michael@0: // no break michael@0: case READY: michael@0: if (index->mIndexOnDiskIsValid && !index->mDontMarkIndexClean) { michael@0: if (!sanitize && NS_FAILED(index->WriteLogToDisk())) { michael@0: index->RemoveIndexFromDisk(); michael@0: } michael@0: } else { michael@0: index->RemoveIndexFromDisk(); michael@0: } michael@0: break; michael@0: case READING: michael@0: index->FinishRead(false); michael@0: break; michael@0: case BUILDING: michael@0: case UPDATING: michael@0: index->FinishUpdate(false); michael@0: break; michael@0: default: michael@0: MOZ_ASSERT(false, "Unexpected state!"); michael@0: } michael@0: michael@0: if (sanitize) { michael@0: index->RemoveIndexFromDisk(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::AddEntry(const SHA1Sum::Hash *aHash) michael@0: { michael@0: LOG(("CacheIndex::AddEntry() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash))); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // Getters in CacheIndexStats assert when mStateLogged is true since the michael@0: // information is incomplete between calls to BeforeChange() and AfterChange() michael@0: // (i.e. while CacheIndexEntryAutoManage exists). We need to check whether michael@0: // non-fresh entries exists outside the scope of CacheIndexEntryAutoManage. michael@0: bool updateIfNonFreshEntriesExist = false; michael@0: michael@0: { michael@0: CacheIndexEntryAutoManage entryMng(aHash, index); michael@0: michael@0: CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash); michael@0: bool entryRemoved = entry && entry->IsRemoved(); michael@0: michael@0: if (index->mState == READY || index->mState == UPDATING || michael@0: index->mState == BUILDING) { michael@0: MOZ_ASSERT(index->mPendingUpdates.Count() == 0); michael@0: michael@0: if (entry && !entryRemoved) { michael@0: // Found entry in index that shouldn't exist. michael@0: michael@0: if (entry->IsFresh()) { michael@0: // Someone removed the file on disk while FF is running. Update michael@0: // process can fix only non-fresh entries (i.e. entries that were not michael@0: // added within this session). Start update only if we have such michael@0: // entries. michael@0: // michael@0: // TODO: This should be very rare problem. If it turns out not to be michael@0: // true, change the update process so that it also iterates all michael@0: // initialized non-empty entries and checks whether the file exists. michael@0: michael@0: LOG(("CacheIndex::AddEntry() - Cache file was removed outside FF " michael@0: "process!")); michael@0: michael@0: updateIfNonFreshEntriesExist = true; michael@0: } else if (index->mState == READY) { michael@0: // Index is outdated, update it. michael@0: LOG(("CacheIndex::AddEntry() - Found entry that shouldn't exist, " michael@0: "update is needed")); michael@0: index->mIndexNeedsUpdate = true; michael@0: } else { michael@0: // We cannot be here when building index since all entries are fresh michael@0: // during building. michael@0: MOZ_ASSERT(index->mState == UPDATING); michael@0: } michael@0: } michael@0: michael@0: if (!entry) { michael@0: entry = index->mIndex.PutEntry(*aHash); michael@0: } michael@0: } else { // WRITING, READING michael@0: CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash); michael@0: bool updatedRemoved = updated && updated->IsRemoved(); michael@0: michael@0: if ((updated && !updatedRemoved) || michael@0: (!updated && entry && !entryRemoved && entry->IsFresh())) { michael@0: // Fresh entry found, so the file was removed outside FF michael@0: LOG(("CacheIndex::AddEntry() - Cache file was removed outside FF " michael@0: "process!")); michael@0: michael@0: updateIfNonFreshEntriesExist = true; michael@0: } else if (!updated && entry && !entryRemoved) { michael@0: if (index->mState == WRITING) { michael@0: LOG(("CacheIndex::AddEntry() - Found entry that shouldn't exist, " michael@0: "update is needed")); michael@0: index->mIndexNeedsUpdate = true; michael@0: } michael@0: // Ignore if state is READING since the index information is partial michael@0: } michael@0: michael@0: updated = index->mPendingUpdates.PutEntry(*aHash); michael@0: entry = updated; michael@0: } michael@0: michael@0: entry->InitNew(); michael@0: entry->MarkDirty(); michael@0: entry->MarkFresh(); michael@0: } michael@0: michael@0: if (updateIfNonFreshEntriesExist && michael@0: index->mIndexStats.Count() != index->mIndexStats.Fresh()) { michael@0: index->mIndexNeedsUpdate = true; michael@0: } michael@0: michael@0: index->StartUpdatingIndexIfNeeded(); michael@0: index->WriteIndexToDiskIfNeeded(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::EnsureEntryExists(const SHA1Sum::Hash *aHash) michael@0: { michael@0: LOG(("CacheIndex::EnsureEntryExists() [hash=%08x%08x%08x%08x%08x]", michael@0: LOGSHA1(aHash))); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: { michael@0: CacheIndexEntryAutoManage entryMng(aHash, index); michael@0: michael@0: CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash); michael@0: bool entryRemoved = entry && entry->IsRemoved(); michael@0: michael@0: if (index->mState == READY || index->mState == UPDATING || michael@0: index->mState == BUILDING) { michael@0: MOZ_ASSERT(index->mPendingUpdates.Count() == 0); michael@0: michael@0: if (!entry || entryRemoved) { michael@0: if (entryRemoved && entry->IsFresh()) { michael@0: // This could happen only if somebody copies files to the entries michael@0: // directory while FF is running. michael@0: LOG(("CacheIndex::EnsureEntryExists() - Cache file was added outside " michael@0: "FF process! Update is needed.")); michael@0: index->mIndexNeedsUpdate = true; michael@0: } else if (index->mState == READY || michael@0: (entryRemoved && !entry->IsFresh())) { michael@0: // Removed non-fresh entries can be present as a result of michael@0: // ProcessJournalEntry() michael@0: LOG(("CacheIndex::EnsureEntryExists() - Didn't find entry that should" michael@0: " exist, update is needed")); michael@0: index->mIndexNeedsUpdate = true; michael@0: } michael@0: michael@0: if (!entry) { michael@0: entry = index->mIndex.PutEntry(*aHash); michael@0: } michael@0: entry->InitNew(); michael@0: entry->MarkDirty(); michael@0: } michael@0: entry->MarkFresh(); michael@0: } else { // WRITING, READING michael@0: CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash); michael@0: bool updatedRemoved = updated && updated->IsRemoved(); michael@0: michael@0: if (updatedRemoved || michael@0: (!updated && entryRemoved && entry->IsFresh())) { michael@0: // Fresh information about missing entry found. This could happen only michael@0: // if somebody copies files to the entries directory while FF is running. michael@0: LOG(("CacheIndex::EnsureEntryExists() - Cache file was added outside " michael@0: "FF process! Update is needed.")); michael@0: index->mIndexNeedsUpdate = true; michael@0: } else if (!updated && (!entry || entryRemoved)) { michael@0: if (index->mState == WRITING) { michael@0: LOG(("CacheIndex::EnsureEntryExists() - Didn't find entry that should" michael@0: " exist, update is needed")); michael@0: index->mIndexNeedsUpdate = true; michael@0: } michael@0: // Ignore if state is READING since the index information is partial michael@0: } michael@0: michael@0: // We don't need entryRemoved and updatedRemoved info anymore michael@0: if (entryRemoved) entry = nullptr; michael@0: if (updatedRemoved) updated = nullptr; michael@0: michael@0: if (updated) { michael@0: updated->MarkFresh(); michael@0: } else { michael@0: if (!entry) { michael@0: // Create a new entry michael@0: updated = index->mPendingUpdates.PutEntry(*aHash); michael@0: updated->InitNew(); michael@0: updated->MarkFresh(); michael@0: updated->MarkDirty(); michael@0: } else { michael@0: if (!entry->IsFresh()) { michael@0: // To mark the entry fresh we must make a copy of index entry michael@0: // since the index is read-only. michael@0: updated = index->mPendingUpdates.PutEntry(*aHash); michael@0: *updated = *entry; michael@0: updated->MarkFresh(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: index->StartUpdatingIndexIfNeeded(); michael@0: index->WriteIndexToDiskIfNeeded(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::InitEntry(const SHA1Sum::Hash *aHash, michael@0: uint32_t aAppId, michael@0: bool aAnonymous, michael@0: bool aInBrowser) michael@0: { michael@0: LOG(("CacheIndex::InitEntry() [hash=%08x%08x%08x%08x%08x, appId=%u, " michael@0: "anonymous=%d, inBrowser=%d]", LOGSHA1(aHash), aAppId, aAnonymous, michael@0: aInBrowser)); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: { michael@0: CacheIndexEntryAutoManage entryMng(aHash, index); michael@0: michael@0: CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash); michael@0: bool reinitEntry = false; michael@0: michael@0: if (entry && entry->IsRemoved()) { michael@0: entry = nullptr; michael@0: } michael@0: michael@0: if (index->mState == READY || index->mState == UPDATING || michael@0: index->mState == BUILDING) { michael@0: MOZ_ASSERT(index->mPendingUpdates.Count() == 0); michael@0: MOZ_ASSERT(entry); michael@0: MOZ_ASSERT(entry->IsFresh()); michael@0: michael@0: if (IsCollision(entry, aAppId, aAnonymous, aInBrowser)) { michael@0: index->mIndexNeedsUpdate = true; // TODO Does this really help in case of collision? michael@0: reinitEntry = true; michael@0: } else { michael@0: if (entry->IsInitialized()) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } else { michael@0: CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash); michael@0: DebugOnly removed = updated && updated->IsRemoved(); michael@0: michael@0: MOZ_ASSERT(updated || !removed); michael@0: MOZ_ASSERT(updated || entry); michael@0: michael@0: if (updated) { michael@0: MOZ_ASSERT(updated->IsFresh()); michael@0: michael@0: if (IsCollision(updated, aAppId, aAnonymous, aInBrowser)) { michael@0: index->mIndexNeedsUpdate = true; michael@0: reinitEntry = true; michael@0: } else { michael@0: if (updated->IsInitialized()) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: entry = updated; michael@0: } else { michael@0: MOZ_ASSERT(entry->IsFresh()); michael@0: michael@0: if (IsCollision(entry, aAppId, aAnonymous, aInBrowser)) { michael@0: index->mIndexNeedsUpdate = true; michael@0: reinitEntry = true; michael@0: } else { michael@0: if (entry->IsInitialized()) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // make a copy of a read-only entry michael@0: updated = index->mPendingUpdates.PutEntry(*aHash); michael@0: *updated = *entry; michael@0: entry = updated; michael@0: } michael@0: } michael@0: michael@0: if (reinitEntry) { michael@0: // There is a collision and we are going to rewrite this entry. Initialize michael@0: // it as a new entry. michael@0: entry->InitNew(); michael@0: entry->MarkFresh(); michael@0: } michael@0: entry->Init(aAppId, aAnonymous, aInBrowser); michael@0: entry->MarkDirty(); michael@0: } michael@0: michael@0: index->StartUpdatingIndexIfNeeded(); michael@0: index->WriteIndexToDiskIfNeeded(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::RemoveEntry(const SHA1Sum::Hash *aHash) michael@0: { michael@0: LOG(("CacheIndex::RemoveEntry() [hash=%08x%08x%08x%08x%08x]", michael@0: LOGSHA1(aHash))); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: { michael@0: CacheIndexEntryAutoManage entryMng(aHash, index); michael@0: michael@0: CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash); michael@0: bool entryRemoved = entry && entry->IsRemoved(); michael@0: michael@0: if (index->mState == READY || index->mState == UPDATING || michael@0: index->mState == BUILDING) { michael@0: MOZ_ASSERT(index->mPendingUpdates.Count() == 0); michael@0: michael@0: if (!entry || entryRemoved) { michael@0: if (entryRemoved && entry->IsFresh()) { michael@0: // This could happen only if somebody copies files to the entries michael@0: // directory while FF is running. michael@0: LOG(("CacheIndex::RemoveEntry() - Cache file was added outside FF " michael@0: "process! Update is needed.")); michael@0: index->mIndexNeedsUpdate = true; michael@0: } else if (index->mState == READY || michael@0: (entryRemoved && !entry->IsFresh())) { michael@0: // Removed non-fresh entries can be present as a result of michael@0: // ProcessJournalEntry() michael@0: LOG(("CacheIndex::RemoveEntry() - Didn't find entry that should exist" michael@0: ", update is needed")); michael@0: index->mIndexNeedsUpdate = true; michael@0: } michael@0: } else { michael@0: if (entry) { michael@0: if (!entry->IsDirty() && entry->IsFileEmpty()) { michael@0: index->mIndex.RemoveEntry(*aHash); michael@0: entry = nullptr; michael@0: } else { michael@0: entry->MarkRemoved(); michael@0: entry->MarkDirty(); michael@0: entry->MarkFresh(); michael@0: } michael@0: } michael@0: } michael@0: } else { // WRITING, READING michael@0: CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash); michael@0: bool updatedRemoved = updated && updated->IsRemoved(); michael@0: michael@0: if (updatedRemoved || michael@0: (!updated && entryRemoved && entry->IsFresh())) { michael@0: // Fresh information about missing entry found. This could happen only michael@0: // if somebody copies files to the entries directory while FF is running. michael@0: LOG(("CacheIndex::RemoveEntry() - Cache file was added outside FF " michael@0: "process! Update is needed.")); michael@0: index->mIndexNeedsUpdate = true; michael@0: } else if (!updated && (!entry || entryRemoved)) { michael@0: if (index->mState == WRITING) { michael@0: LOG(("CacheIndex::RemoveEntry() - Didn't find entry that should exist" michael@0: ", update is needed")); michael@0: index->mIndexNeedsUpdate = true; michael@0: } michael@0: // Ignore if state is READING since the index information is partial michael@0: } michael@0: michael@0: if (!updated) { michael@0: updated = index->mPendingUpdates.PutEntry(*aHash); michael@0: updated->InitNew(); michael@0: } michael@0: michael@0: updated->MarkRemoved(); michael@0: updated->MarkDirty(); michael@0: updated->MarkFresh(); michael@0: } michael@0: } michael@0: michael@0: index->StartUpdatingIndexIfNeeded(); michael@0: index->WriteIndexToDiskIfNeeded(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::UpdateEntry(const SHA1Sum::Hash *aHash, michael@0: const uint32_t *aFrecency, michael@0: const uint32_t *aExpirationTime, michael@0: const uint32_t *aSize) michael@0: { michael@0: LOG(("CacheIndex::UpdateEntry() [hash=%08x%08x%08x%08x%08x, " michael@0: "frecency=%s, expirationTime=%s, size=%s]", LOGSHA1(aHash), michael@0: aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "", michael@0: aExpirationTime ? nsPrintfCString("%u", *aExpirationTime).get() : "", michael@0: aSize ? nsPrintfCString("%u", *aSize).get() : "")); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: { michael@0: CacheIndexEntryAutoManage entryMng(aHash, index); michael@0: michael@0: CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash); michael@0: michael@0: if (entry && entry->IsRemoved()) { michael@0: entry = nullptr; michael@0: } michael@0: michael@0: if (index->mState == READY || index->mState == UPDATING || michael@0: index->mState == BUILDING) { michael@0: MOZ_ASSERT(index->mPendingUpdates.Count() == 0); michael@0: MOZ_ASSERT(entry); michael@0: michael@0: if (!HasEntryChanged(entry, aFrecency, aExpirationTime, aSize)) { michael@0: return NS_OK; michael@0: } michael@0: } else { michael@0: CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash); michael@0: DebugOnly removed = updated && updated->IsRemoved(); michael@0: michael@0: MOZ_ASSERT(updated || !removed); michael@0: MOZ_ASSERT(updated || entry); michael@0: michael@0: if (!updated) { michael@0: if (entry && michael@0: HasEntryChanged(entry, aFrecency, aExpirationTime, aSize)) { michael@0: // make a copy of a read-only entry michael@0: updated = index->mPendingUpdates.PutEntry(*aHash); michael@0: *updated = *entry; michael@0: entry = updated; michael@0: } else { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } else { michael@0: entry = updated; michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSERT(entry->IsFresh()); michael@0: MOZ_ASSERT(entry->IsInitialized()); michael@0: entry->MarkDirty(); michael@0: michael@0: if (aFrecency) { michael@0: entry->SetFrecency(*aFrecency); michael@0: } michael@0: michael@0: if (aExpirationTime) { michael@0: entry->SetExpirationTime(*aExpirationTime); michael@0: } michael@0: michael@0: if (aSize) { michael@0: entry->SetFileSize(*aSize); michael@0: } michael@0: } michael@0: michael@0: index->WriteIndexToDiskIfNeeded(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::RemoveAll() michael@0: { michael@0: LOG(("CacheIndex::RemoveAll()")); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); michael@0: michael@0: nsCOMPtr file; michael@0: michael@0: { michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: MOZ_ASSERT(!index->mRemovingAll); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: AutoRestore saveRemovingAll(index->mRemovingAll); michael@0: index->mRemovingAll = true; michael@0: michael@0: // Doom index and journal handles but don't null them out since this will be michael@0: // done in FinishWrite/FinishRead methods. michael@0: if (index->mIndexHandle) { michael@0: CacheFileIOManager::DoomFile(index->mIndexHandle, nullptr); michael@0: } else { michael@0: // We don't have a handle to index file, so get the file here, but delete michael@0: // it outside the lock. Ignore the result since this is not fatal. michael@0: index->GetFile(NS_LITERAL_CSTRING(kIndexName), getter_AddRefs(file)); michael@0: } michael@0: michael@0: if (index->mJournalHandle) { michael@0: CacheFileIOManager::DoomFile(index->mJournalHandle, nullptr); michael@0: } michael@0: michael@0: switch (index->mState) { michael@0: case WRITING: michael@0: index->FinishWrite(false); michael@0: break; michael@0: case READY: michael@0: // nothing to do michael@0: break; michael@0: case READING: michael@0: index->FinishRead(false); michael@0: break; michael@0: case BUILDING: michael@0: case UPDATING: michael@0: index->FinishUpdate(false); michael@0: break; michael@0: default: michael@0: MOZ_ASSERT(false, "Unexpected state!"); michael@0: } michael@0: michael@0: // We should end up in READY state michael@0: MOZ_ASSERT(index->mState == READY); michael@0: michael@0: // There should not be any handle michael@0: MOZ_ASSERT(!index->mIndexHandle); michael@0: MOZ_ASSERT(!index->mJournalHandle); michael@0: michael@0: index->mIndexOnDiskIsValid = false; michael@0: index->mIndexNeedsUpdate = false; michael@0: michael@0: index->mIndexStats.Clear(); michael@0: index->mFrecencyArray.Clear(); michael@0: index->mExpirationArray.Clear(); michael@0: index->mIndex.Clear(); michael@0: } michael@0: michael@0: if (file) { michael@0: // Ignore the result. The file might not exist and the failure is not fatal. michael@0: file->Remove(false); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval) michael@0: { michael@0: LOG(("CacheIndex::HasEntry() [key=%s]", PromiseFlatCString(aKey).get())); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: SHA1Sum sum; michael@0: SHA1Sum::Hash hash; michael@0: sum.update(aKey.BeginReading(), aKey.Length()); michael@0: sum.finish(hash); michael@0: michael@0: CacheIndexEntry *entry = nullptr; michael@0: michael@0: switch (index->mState) { michael@0: case READING: michael@0: case WRITING: michael@0: entry = index->mPendingUpdates.GetEntry(hash); michael@0: // no break michael@0: case BUILDING: michael@0: case UPDATING: michael@0: case READY: michael@0: if (!entry) { michael@0: entry = index->mIndex.GetEntry(hash); michael@0: } michael@0: break; michael@0: case INITIAL: michael@0: case SHUTDOWN: michael@0: MOZ_ASSERT(false, "Unexpected state!"); michael@0: } michael@0: michael@0: if (!entry) { michael@0: if (index->mState == READY || index->mState == WRITING) { michael@0: *_retval = DOES_NOT_EXIST; michael@0: } else { michael@0: *_retval = DO_NOT_KNOW; michael@0: } michael@0: } else { michael@0: if (entry->IsRemoved()) { michael@0: if (entry->IsFresh()) { michael@0: *_retval = DOES_NOT_EXIST; michael@0: } else { michael@0: *_retval = DO_NOT_KNOW; michael@0: } michael@0: } else { michael@0: *_retval = EXISTS; michael@0: } michael@0: } michael@0: michael@0: LOG(("CacheIndex::HasEntry() - result is %u", *_retval)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::GetEntryForEviction(SHA1Sum::Hash *aHash, uint32_t *aCnt) michael@0: { michael@0: LOG(("CacheIndex::GetEntryForEviction()")); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: MOZ_ASSERT(index->mFrecencyArray.Length() == michael@0: index->mExpirationArray.Length()); michael@0: michael@0: if (index->mExpirationArray.Length() == 0) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: uint32_t now = PR_Now() / PR_USEC_PER_SEC; michael@0: if (index->mExpirationArray[0]->mExpirationTime < now) { michael@0: memcpy(aHash, &index->mExpirationArray[0]->mHash, sizeof(SHA1Sum::Hash)); michael@0: *aCnt = index->mExpirationArray.Length(); michael@0: LOG(("CacheIndex::GetEntryForEviction() - returning entry from expiration " michael@0: "array [hash=%08x%08x%08x%08x%08x, cnt=%u, expTime=%u, now=%u, " michael@0: "frecency=%u]", LOGSHA1(aHash), *aCnt, michael@0: index->mExpirationArray[0]->mExpirationTime, now, michael@0: index->mExpirationArray[0]->mFrecency)); michael@0: } michael@0: else { michael@0: memcpy(aHash, &index->mFrecencyArray[0]->mHash, sizeof(SHA1Sum::Hash)); michael@0: *aCnt = index->mFrecencyArray.Length(); michael@0: LOG(("CacheIndex::GetEntryForEviction() - returning entry from frecency " michael@0: "array [hash=%08x%08x%08x%08x%08x, cnt=%u, expTime=%u, now=%u, " michael@0: "frecency=%u]", LOGSHA1(aHash), *aCnt, michael@0: index->mExpirationArray[0]->mExpirationTime, now, michael@0: index->mExpirationArray[0]->mFrecency)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::GetCacheSize(uint32_t *_retval) michael@0: { michael@0: LOG(("CacheIndex::GetCacheSize()")); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: *_retval = index->mIndexStats.Size(); michael@0: LOG(("CacheIndex::GetCacheSize() - returning %u", *_retval)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserver) michael@0: { michael@0: LOG(("CacheIndex::AsyncGetDiskConsumption()")); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsRefPtr observer = michael@0: DiskConsumptionObserver::Init(aObserver); michael@0: michael@0: NS_ENSURE_ARG(observer); michael@0: michael@0: if (index->mState == READY || index->mState == WRITING) { michael@0: LOG(("CacheIndex::AsyncGetDiskConsumption - calling immediately")); michael@0: // Safe to call the callback under the lock, michael@0: // we always post to the main thread. michael@0: observer->OnDiskConsumption(index->mIndexStats.Size() << 10); michael@0: return NS_OK; michael@0: } michael@0: michael@0: LOG(("CacheIndex::AsyncGetDiskConsumption - remembering callback")); michael@0: // Will be called when the index get to the READY state. michael@0: index->mDiskConsumptionObservers.AppendElement(observer); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::GetIterator(nsILoadContextInfo *aInfo, bool aAddNew, michael@0: CacheIndexIterator **_retval) michael@0: { michael@0: LOG(("CacheIndex::GetIterator() [info=%p, addNew=%d]", aInfo, aAddNew)); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsRefPtr iter; michael@0: if (aInfo) { michael@0: iter = new CacheIndexContextIterator(index, aAddNew, aInfo); michael@0: } else { michael@0: iter = new CacheIndexIterator(index, aAddNew); michael@0: } michael@0: michael@0: iter->AddRecords(index->mFrecencyArray); michael@0: michael@0: index->mIterators.AppendElement(iter); michael@0: iter.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheIndex::IsUpToDate(bool *_retval) michael@0: { michael@0: LOG(("CacheIndex::IsUpToDate()")); michael@0: michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: *_retval = (index->mState == READY || index->mState == WRITING) && michael@0: !index->mIndexNeedsUpdate && !index->mShuttingDown; michael@0: michael@0: LOG(("CacheIndex::IsUpToDate() - returning %p", *_retval)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: CacheIndex::IsIndexUsable() michael@0: { michael@0: MOZ_ASSERT(mState != INITIAL); michael@0: michael@0: switch (mState) { michael@0: case INITIAL: michael@0: case SHUTDOWN: michael@0: return false; michael@0: michael@0: case READING: michael@0: case WRITING: michael@0: case BUILDING: michael@0: case UPDATING: michael@0: case READY: michael@0: break; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: CacheIndex::IsCollision(CacheIndexEntry *aEntry, michael@0: uint32_t aAppId, michael@0: bool aAnonymous, michael@0: bool aInBrowser) michael@0: { michael@0: if (!aEntry->IsInitialized()) { michael@0: return false; michael@0: } michael@0: michael@0: if (aEntry->AppId() != aAppId || aEntry->Anonymous() != aAnonymous || michael@0: aEntry->InBrowser() != aInBrowser) { michael@0: LOG(("CacheIndex::IsCollision() - Collision detected for entry hash=%08x" michael@0: "%08x%08x%08x%08x, expected values: appId=%u, anonymous=%d, " michael@0: "inBrowser=%d; actual values: appId=%u, anonymous=%d, inBrowser=%d]", michael@0: LOGSHA1(aEntry->Hash()), aAppId, aAnonymous, aInBrowser, michael@0: aEntry->AppId(), aEntry->Anonymous(), aEntry->InBrowser())); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: CacheIndex::HasEntryChanged(CacheIndexEntry *aEntry, michael@0: const uint32_t *aFrecency, michael@0: const uint32_t *aExpirationTime, michael@0: const uint32_t *aSize) michael@0: { michael@0: if (aFrecency && *aFrecency != aEntry->GetFrecency()) { michael@0: return true; michael@0: } michael@0: michael@0: if (aExpirationTime && *aExpirationTime != aEntry->GetExpirationTime()) { michael@0: return true; michael@0: } michael@0: michael@0: if (aSize && michael@0: (*aSize & CacheIndexEntry::kFileSizeMask) != aEntry->GetFileSize()) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: CacheIndex::ProcessPendingOperations() michael@0: { michael@0: LOG(("CacheIndex::ProcessPendingOperations()")); michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: mPendingUpdates.EnumerateEntries(&CacheIndex::UpdateEntryInIndex, this); michael@0: michael@0: MOZ_ASSERT(mPendingUpdates.Count() == 0); michael@0: michael@0: EnsureCorrectStats(); michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: CacheIndex::UpdateEntryInIndex(CacheIndexEntry *aEntry, void* aClosure) michael@0: { michael@0: CacheIndex *index = static_cast(aClosure); michael@0: michael@0: LOG(("CacheFile::UpdateEntryInIndex() [hash=%08x%08x%08x%08x%08x]", michael@0: LOGSHA1(aEntry->Hash()))); michael@0: michael@0: MOZ_ASSERT(aEntry->IsFresh()); michael@0: MOZ_ASSERT(aEntry->IsDirty()); michael@0: michael@0: CacheIndexEntry *entry = index->mIndex.GetEntry(*aEntry->Hash()); michael@0: michael@0: CacheIndexEntryAutoManage emng(aEntry->Hash(), index); michael@0: emng.DoNotSearchInUpdates(); michael@0: michael@0: if (aEntry->IsRemoved()) { michael@0: if (entry) { michael@0: if (entry->IsRemoved()) { michael@0: MOZ_ASSERT(entry->IsFresh()); michael@0: MOZ_ASSERT(entry->IsDirty()); michael@0: } else if (!entry->IsDirty() && entry->IsFileEmpty()) { michael@0: // Entries with empty file are not stored in index on disk. Just remove michael@0: // the entry, but only in case the entry is not dirty, i.e. the entry michael@0: // file was empty when we wrote the index. michael@0: index->mIndex.RemoveEntry(*aEntry->Hash()); michael@0: entry = nullptr; michael@0: } else { michael@0: entry->MarkRemoved(); michael@0: entry->MarkDirty(); michael@0: entry->MarkFresh(); michael@0: } michael@0: } michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: entry = index->mIndex.PutEntry(*aEntry->Hash()); michael@0: *entry = *aEntry; michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: bool michael@0: CacheIndex::WriteIndexToDiskIfNeeded() michael@0: { michael@0: if (mState != READY || mShuttingDown) { michael@0: return false; michael@0: } michael@0: michael@0: if (!mLastDumpTime.IsNull() && michael@0: (TimeStamp::NowLoRes() - mLastDumpTime).ToMilliseconds() < michael@0: kMinDumpInterval) { michael@0: return false; michael@0: } michael@0: michael@0: if (mIndexStats.Dirty() < kMinUnwrittenChanges) { michael@0: return false; michael@0: } michael@0: michael@0: WriteIndexToDisk(); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: CacheIndex::WriteIndexToDisk() michael@0: { michael@0: LOG(("CacheIndex::WriteIndexToDisk()")); michael@0: mIndexStats.Log(); michael@0: michael@0: nsresult rv; michael@0: michael@0: AssertOwnsLock(); michael@0: MOZ_ASSERT(mState == READY); michael@0: MOZ_ASSERT(!mRWBuf); michael@0: MOZ_ASSERT(!mRWHash); michael@0: michael@0: ChangeState(WRITING); michael@0: michael@0: mProcessEntries = mIndexStats.ActiveEntriesCount(); michael@0: michael@0: mIndexFileOpener = new FileOpenHelper(this); michael@0: rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kTempIndexName), michael@0: CacheFileIOManager::SPECIAL_FILE | michael@0: CacheFileIOManager::CREATE, michael@0: true, michael@0: mIndexFileOpener); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::WriteIndexToDisk() - Can't open file [rv=0x%08x]", rv)); michael@0: FinishWrite(false); michael@0: return; michael@0: } michael@0: michael@0: // Write index header to a buffer, it will be written to disk together with michael@0: // records in WriteRecords() once we open the file successfully. michael@0: AllocBuffer(); michael@0: mRWHash = new CacheHash(); michael@0: michael@0: CacheIndexHeader *hdr = reinterpret_cast(mRWBuf); michael@0: NetworkEndian::writeUint32(&hdr->mVersion, kIndexVersion); michael@0: NetworkEndian::writeUint32(&hdr->mTimeStamp, michael@0: static_cast(PR_Now() / PR_USEC_PER_SEC)); michael@0: NetworkEndian::writeUint32(&hdr->mIsDirty, 1); michael@0: michael@0: mRWBufPos = sizeof(CacheIndexHeader); michael@0: mSkipEntries = 0; michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: struct WriteRecordsHelper michael@0: { michael@0: char *mBuf; michael@0: uint32_t mSkip; michael@0: uint32_t mProcessMax; michael@0: uint32_t mProcessed; michael@0: #ifdef DEBUG michael@0: bool mHasMore; michael@0: #endif michael@0: }; michael@0: michael@0: } // anon michael@0: michael@0: void michael@0: CacheIndex::WriteRecords() michael@0: { michael@0: LOG(("CacheIndex::WriteRecords()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: AssertOwnsLock(); michael@0: MOZ_ASSERT(mState == WRITING); michael@0: michael@0: int64_t fileOffset; michael@0: michael@0: if (mSkipEntries) { michael@0: MOZ_ASSERT(mRWBufPos == 0); michael@0: fileOffset = sizeof(CacheIndexHeader); michael@0: fileOffset += sizeof(CacheIndexRecord) * mSkipEntries; michael@0: } else { michael@0: MOZ_ASSERT(mRWBufPos == sizeof(CacheIndexHeader)); michael@0: fileOffset = 0; michael@0: } michael@0: uint32_t hashOffset = mRWBufPos; michael@0: michael@0: WriteRecordsHelper data; michael@0: data.mBuf = mRWBuf + mRWBufPos; michael@0: data.mSkip = mSkipEntries; michael@0: data.mProcessMax = (mRWBufSize - mRWBufPos) / sizeof(CacheIndexRecord); michael@0: MOZ_ASSERT(data.mProcessMax != 0 || mProcessEntries == 0); // TODO make sure we can write an empty index michael@0: data.mProcessed = 0; michael@0: #ifdef DEBUG michael@0: data.mHasMore = false; michael@0: #endif michael@0: michael@0: mIndex.EnumerateEntries(&CacheIndex::CopyRecordsToRWBuf, &data); michael@0: MOZ_ASSERT(mRWBufPos != static_cast(data.mBuf - mRWBuf) || michael@0: mProcessEntries == 0); michael@0: mRWBufPos = data.mBuf - mRWBuf; michael@0: mSkipEntries += data.mProcessed; michael@0: MOZ_ASSERT(mSkipEntries <= mProcessEntries); michael@0: michael@0: mRWHash->Update(mRWBuf + hashOffset, mRWBufPos - hashOffset); michael@0: michael@0: if (mSkipEntries == mProcessEntries) { michael@0: MOZ_ASSERT(!data.mHasMore); michael@0: michael@0: // We've processed all records michael@0: if (mRWBufPos + sizeof(CacheHash::Hash32_t) > mRWBufSize) { michael@0: // realloc buffer to spare another write cycle michael@0: mRWBufSize = mRWBufPos + sizeof(CacheHash::Hash32_t); michael@0: mRWBuf = static_cast(moz_xrealloc(mRWBuf, mRWBufSize)); michael@0: } michael@0: michael@0: NetworkEndian::writeUint32(mRWBuf + mRWBufPos, mRWHash->GetHash()); michael@0: mRWBufPos += sizeof(CacheHash::Hash32_t); michael@0: } else { michael@0: MOZ_ASSERT(data.mHasMore); michael@0: } michael@0: michael@0: rv = CacheFileIOManager::Write(mIndexHandle, fileOffset, mRWBuf, mRWBufPos, michael@0: mSkipEntries == mProcessEntries, this); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::WriteRecords() - CacheFileIOManager::Write() failed " michael@0: "synchronously [rv=0x%08x]", rv)); michael@0: FinishWrite(false); michael@0: } michael@0: michael@0: mRWBufPos = 0; michael@0: } michael@0: michael@0: void michael@0: CacheIndex::FinishWrite(bool aSucceeded) michael@0: { michael@0: LOG(("CacheIndex::FinishWrite() [succeeded=%d]", aSucceeded)); michael@0: michael@0: MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == WRITING); michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: mIndexHandle = nullptr; michael@0: mRWHash = nullptr; michael@0: ReleaseBuffer(); michael@0: michael@0: if (aSucceeded) { michael@0: // Opening of the file must not be in progress if writing succeeded. michael@0: MOZ_ASSERT(!mIndexFileOpener); michael@0: michael@0: mIndex.EnumerateEntries(&CacheIndex::ApplyIndexChanges, this); michael@0: mIndexOnDiskIsValid = true; michael@0: } else { michael@0: if (mIndexFileOpener) { michael@0: // If opening of the file is still in progress (e.g. WRITE process was michael@0: // canceled by RemoveAll()) then we need to cancel the opener to make sure michael@0: // that OnFileOpenedInternal() won't be called. michael@0: mIndexFileOpener->Cancel(); michael@0: mIndexFileOpener = nullptr; michael@0: } michael@0: } michael@0: michael@0: ProcessPendingOperations(); michael@0: mIndexStats.Log(); michael@0: michael@0: if (mState == WRITING) { michael@0: ChangeState(READY); michael@0: mLastDumpTime = TimeStamp::NowLoRes(); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: CacheIndex::CopyRecordsToRWBuf(CacheIndexEntry *aEntry, void* aClosure) michael@0: { michael@0: if (aEntry->IsRemoved()) { michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: if (!aEntry->IsInitialized()) { michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: if (aEntry->IsFileEmpty()) { michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: WriteRecordsHelper *data = static_cast(aClosure); michael@0: if (data->mSkip) { michael@0: data->mSkip--; michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: if (data->mProcessed == data->mProcessMax) { michael@0: #ifdef DEBUG michael@0: data->mHasMore = true; michael@0: #endif michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: aEntry->WriteToBuf(data->mBuf); michael@0: data->mBuf += sizeof(CacheIndexRecord); michael@0: data->mProcessed++; michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: CacheIndex::ApplyIndexChanges(CacheIndexEntry *aEntry, void* aClosure) michael@0: { michael@0: CacheIndex *index = static_cast(aClosure); michael@0: michael@0: CacheIndexEntryAutoManage emng(aEntry->Hash(), index); michael@0: michael@0: if (aEntry->IsRemoved()) { michael@0: emng.DoNotSearchInIndex(); michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: if (aEntry->IsDirty()) { michael@0: aEntry->ClearDirty(); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::GetFile(const nsACString &aName, nsIFile **_retval) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr file; michael@0: rv = mCacheDirectory->Clone(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = file->AppendNative(aName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: file.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::RemoveFile(const nsACString &aName) michael@0: { michael@0: MOZ_ASSERT(mState == SHUTDOWN); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr file; michael@0: rv = GetFile(aName, getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool exists; michael@0: rv = file->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists) { michael@0: rv = file->Remove(false); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::RemoveFile() - Cannot remove old entry file from disk." michael@0: "[name=%s]", PromiseFlatCString(aName).get())); michael@0: NS_WARNING("Cannot remove old entry file from the disk"); michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CacheIndex::RemoveIndexFromDisk() michael@0: { michael@0: LOG(("CacheIndex::RemoveIndexFromDisk()")); michael@0: michael@0: RemoveFile(NS_LITERAL_CSTRING(kIndexName)); michael@0: RemoveFile(NS_LITERAL_CSTRING(kTempIndexName)); michael@0: RemoveFile(NS_LITERAL_CSTRING(kJournalName)); michael@0: } michael@0: michael@0: class WriteLogHelper michael@0: { michael@0: public: michael@0: WriteLogHelper(PRFileDesc *aFD) michael@0: : mStatus(NS_OK) michael@0: , mFD(aFD) michael@0: , mBufSize(kMaxBufSize) michael@0: , mBufPos(0) michael@0: { michael@0: mHash = new CacheHash(); michael@0: mBuf = static_cast(moz_xmalloc(mBufSize)); michael@0: } michael@0: michael@0: ~WriteLogHelper() { michael@0: free(mBuf); michael@0: } michael@0: michael@0: nsresult AddEntry(CacheIndexEntry *aEntry); michael@0: nsresult Finish(); michael@0: michael@0: private: michael@0: michael@0: nsresult FlushBuffer(); michael@0: michael@0: nsresult mStatus; michael@0: PRFileDesc *mFD; michael@0: char *mBuf; michael@0: uint32_t mBufSize; michael@0: int32_t mBufPos; michael@0: nsRefPtr mHash; michael@0: }; michael@0: michael@0: nsresult michael@0: WriteLogHelper::AddEntry(CacheIndexEntry *aEntry) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (NS_FAILED(mStatus)) { michael@0: return mStatus; michael@0: } michael@0: michael@0: if (mBufPos + sizeof(CacheIndexRecord) > mBufSize) { michael@0: mHash->Update(mBuf, mBufPos); michael@0: michael@0: rv = FlushBuffer(); michael@0: if (NS_FAILED(rv)) { michael@0: mStatus = rv; michael@0: return rv; michael@0: } michael@0: MOZ_ASSERT(mBufPos + sizeof(CacheIndexRecord) <= mBufSize); michael@0: } michael@0: michael@0: aEntry->WriteToBuf(mBuf + mBufPos); michael@0: mBufPos += sizeof(CacheIndexRecord); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: WriteLogHelper::Finish() michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (NS_FAILED(mStatus)) { michael@0: return mStatus; michael@0: } michael@0: michael@0: mHash->Update(mBuf, mBufPos); michael@0: if (mBufPos + sizeof(CacheHash::Hash32_t) > mBufSize) { michael@0: rv = FlushBuffer(); michael@0: if (NS_FAILED(rv)) { michael@0: mStatus = rv; michael@0: return rv; michael@0: } michael@0: MOZ_ASSERT(mBufPos + sizeof(CacheHash::Hash32_t) <= mBufSize); michael@0: } michael@0: michael@0: NetworkEndian::writeUint32(mBuf + mBufPos, mHash->GetHash()); michael@0: mBufPos += sizeof(CacheHash::Hash32_t); michael@0: michael@0: rv = FlushBuffer(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mStatus = NS_ERROR_UNEXPECTED; // Don't allow any other operation michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: WriteLogHelper::FlushBuffer() michael@0: { michael@0: MOZ_ASSERT(NS_SUCCEEDED(mStatus)); michael@0: michael@0: int32_t bytesWritten = PR_Write(mFD, mBuf, mBufPos); michael@0: michael@0: if (bytesWritten != mBufPos) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mBufPos = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::WriteLogToDisk() michael@0: { michael@0: LOG(("CacheIndex::WriteLogToDisk()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: MOZ_ASSERT(mPendingUpdates.Count() == 0); michael@0: MOZ_ASSERT(mState == SHUTDOWN); michael@0: michael@0: RemoveFile(NS_LITERAL_CSTRING(kTempIndexName)); michael@0: michael@0: nsCOMPtr indexFile; michael@0: rv = GetFile(NS_LITERAL_CSTRING(kIndexName), getter_AddRefs(indexFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr logFile; michael@0: rv = GetFile(NS_LITERAL_CSTRING(kJournalName), getter_AddRefs(logFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mIndexStats.Log(); michael@0: michael@0: PRFileDesc *fd = nullptr; michael@0: rv = logFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, michael@0: 0600, &fd); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: WriteLogHelper wlh(fd); michael@0: mIndex.EnumerateEntries(&CacheIndex::WriteEntryToLog, &wlh); michael@0: michael@0: rv = wlh.Finish(); michael@0: PR_Close(fd); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = indexFile->OpenNSPRFileDesc(PR_RDWR, 0600, &fd); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: CacheIndexHeader header; michael@0: int32_t bytesRead = PR_Read(fd, &header, sizeof(CacheIndexHeader)); michael@0: if (bytesRead != sizeof(CacheIndexHeader)) { michael@0: PR_Close(fd); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NetworkEndian::writeUint32(&header.mIsDirty, 0); michael@0: michael@0: int64_t offset = PR_Seek64(fd, 0, PR_SEEK_SET); michael@0: if (offset == -1) { michael@0: PR_Close(fd); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: int32_t bytesWritten = PR_Write(fd, &header, sizeof(CacheIndexHeader)); michael@0: PR_Close(fd); michael@0: if (bytesWritten != sizeof(CacheIndexHeader)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: CacheIndex::WriteEntryToLog(CacheIndexEntry *aEntry, void* aClosure) michael@0: { michael@0: WriteLogHelper *wlh = static_cast(aClosure); michael@0: michael@0: if (aEntry->IsRemoved() || aEntry->IsDirty()) { michael@0: wlh->AddEntry(aEntry); michael@0: } michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: void michael@0: CacheIndex::ReadIndexFromDisk() michael@0: { michael@0: LOG(("CacheIndex::ReadIndexFromDisk()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: AssertOwnsLock(); michael@0: MOZ_ASSERT(mState == INITIAL); michael@0: michael@0: ChangeState(READING); michael@0: michael@0: mIndexFileOpener = new FileOpenHelper(this); michael@0: rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kIndexName), michael@0: CacheFileIOManager::SPECIAL_FILE | michael@0: CacheFileIOManager::OPEN, michael@0: true, michael@0: mIndexFileOpener); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() " michael@0: "failed [rv=0x%08x, file=%s]", rv, kIndexName)); michael@0: FinishRead(false); michael@0: return; michael@0: } michael@0: michael@0: mJournalFileOpener = new FileOpenHelper(this); michael@0: rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kJournalName), michael@0: CacheFileIOManager::SPECIAL_FILE | michael@0: CacheFileIOManager::OPEN, michael@0: true, michael@0: mJournalFileOpener); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() " michael@0: "failed [rv=0x%08x, file=%s]", rv, kJournalName)); michael@0: FinishRead(false); michael@0: } michael@0: michael@0: mTmpFileOpener = new FileOpenHelper(this); michael@0: rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kTempIndexName), michael@0: CacheFileIOManager::SPECIAL_FILE | michael@0: CacheFileIOManager::OPEN, michael@0: true, michael@0: mTmpFileOpener); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() " michael@0: "failed [rv=0x%08x, file=%s]", rv, kTempIndexName)); michael@0: FinishRead(false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheIndex::StartReadingIndex() michael@0: { michael@0: LOG(("CacheIndex::StartReadingIndex()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: MOZ_ASSERT(mIndexHandle); michael@0: MOZ_ASSERT(mState == READING); michael@0: MOZ_ASSERT(!mIndexOnDiskIsValid); michael@0: MOZ_ASSERT(!mDontMarkIndexClean); michael@0: MOZ_ASSERT(!mJournalReadSuccessfully); michael@0: MOZ_ASSERT(mIndexHandle->FileSize() >= 0); michael@0: michael@0: int64_t entriesSize = mIndexHandle->FileSize() - sizeof(CacheIndexHeader) - michael@0: sizeof(CacheHash::Hash32_t); michael@0: michael@0: if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) { michael@0: LOG(("CacheIndex::StartReadingIndex() - Index is corrupted")); michael@0: FinishRead(false); michael@0: return; michael@0: } michael@0: michael@0: AllocBuffer(); michael@0: mSkipEntries = 0; michael@0: mRWHash = new CacheHash(); michael@0: michael@0: mRWBufPos = std::min(mRWBufSize, michael@0: static_cast(mIndexHandle->FileSize())); michael@0: michael@0: rv = CacheFileIOManager::Read(mIndexHandle, 0, mRWBuf, mRWBufPos, true, this); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::StartReadingIndex() - CacheFileIOManager::Read() failed " michael@0: "synchronously [rv=0x%08x]", rv)); michael@0: FinishRead(false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheIndex::ParseRecords() michael@0: { michael@0: LOG(("CacheIndex::ParseRecords()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: uint32_t entryCnt = (mIndexHandle->FileSize() - sizeof(CacheIndexHeader) - michael@0: sizeof(CacheHash::Hash32_t)) / sizeof(CacheIndexRecord); michael@0: uint32_t pos = 0; michael@0: michael@0: if (!mSkipEntries) { michael@0: CacheIndexHeader *hdr = reinterpret_cast( michael@0: moz_xmalloc(sizeof(CacheIndexHeader))); michael@0: memcpy(hdr, mRWBuf, sizeof(CacheIndexHeader)); michael@0: michael@0: if (NetworkEndian::readUint32(&hdr->mVersion) != kIndexVersion) { michael@0: free(hdr); michael@0: FinishRead(false); michael@0: return; michael@0: } michael@0: michael@0: mIndexTimeStamp = NetworkEndian::readUint32(&hdr->mTimeStamp); michael@0: michael@0: if (NetworkEndian::readUint32(&hdr->mIsDirty)) { michael@0: if (mJournalHandle) { michael@0: CacheFileIOManager::DoomFile(mJournalHandle, nullptr); michael@0: mJournalHandle = nullptr; michael@0: } michael@0: free(hdr); michael@0: } else { michael@0: NetworkEndian::writeUint32(&hdr->mIsDirty, 1); michael@0: michael@0: // Mark index dirty. The buffer is freed by CacheFileIOManager when michael@0: // nullptr is passed as the listener and the call doesn't fail michael@0: // synchronously. michael@0: rv = CacheFileIOManager::Write(mIndexHandle, 0, michael@0: reinterpret_cast(hdr), michael@0: sizeof(CacheIndexHeader), true, nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: // This is not fatal, just free the memory michael@0: free(hdr); michael@0: } michael@0: } michael@0: michael@0: pos += sizeof(CacheIndexHeader); michael@0: } michael@0: michael@0: uint32_t hashOffset = pos; michael@0: michael@0: while (pos + sizeof(CacheIndexRecord) <= mRWBufPos && michael@0: mSkipEntries != entryCnt) { michael@0: CacheIndexRecord *rec = reinterpret_cast(mRWBuf + pos); michael@0: CacheIndexEntry tmpEntry(&rec->mHash); michael@0: tmpEntry.ReadFromBuf(mRWBuf + pos); michael@0: michael@0: if (tmpEntry.IsDirty() || !tmpEntry.IsInitialized() || michael@0: tmpEntry.IsFileEmpty() || tmpEntry.IsFresh() || tmpEntry.IsRemoved()) { michael@0: LOG(("CacheIndex::ParseRecords() - Invalid entry found in index, removing" michael@0: " whole index [dirty=%d, initialized=%d, fileEmpty=%d, fresh=%d, " michael@0: "removed=%d]", tmpEntry.IsDirty(), tmpEntry.IsInitialized(), michael@0: tmpEntry.IsFileEmpty(), tmpEntry.IsFresh(), tmpEntry.IsRemoved())); michael@0: FinishRead(false); michael@0: return; michael@0: } michael@0: michael@0: CacheIndexEntryAutoManage emng(tmpEntry.Hash(), this); michael@0: michael@0: CacheIndexEntry *entry = mIndex.PutEntry(*tmpEntry.Hash()); michael@0: *entry = tmpEntry; michael@0: michael@0: pos += sizeof(CacheIndexRecord); michael@0: mSkipEntries++; michael@0: } michael@0: michael@0: mRWHash->Update(mRWBuf + hashOffset, pos - hashOffset); michael@0: michael@0: if (pos != mRWBufPos) { michael@0: memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos); michael@0: mRWBufPos -= pos; michael@0: pos = 0; michael@0: } michael@0: michael@0: int64_t fileOffset = sizeof(CacheIndexHeader) + michael@0: mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos; michael@0: michael@0: MOZ_ASSERT(fileOffset <= mIndexHandle->FileSize()); michael@0: if (fileOffset == mIndexHandle->FileSize()) { michael@0: if (mRWHash->GetHash() != NetworkEndian::readUint32(mRWBuf)) { michael@0: LOG(("CacheIndex::ParseRecords() - Hash mismatch, [is %x, should be %x]", michael@0: mRWHash->GetHash(), michael@0: NetworkEndian::readUint32(mRWBuf))); michael@0: FinishRead(false); michael@0: return; michael@0: } michael@0: michael@0: mIndexOnDiskIsValid = true; michael@0: mJournalReadSuccessfully = false; michael@0: michael@0: if (mJournalHandle) { michael@0: StartReadingJournal(); michael@0: } else { michael@0: FinishRead(false); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: pos = mRWBufPos; michael@0: uint32_t toRead = std::min(mRWBufSize - pos, michael@0: static_cast(mIndexHandle->FileSize() - michael@0: fileOffset)); michael@0: mRWBufPos = pos + toRead; michael@0: michael@0: rv = CacheFileIOManager::Read(mIndexHandle, fileOffset, mRWBuf + pos, toRead, michael@0: true, this); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::ParseRecords() - CacheFileIOManager::Read() failed " michael@0: "synchronously [rv=0x%08x]", rv)); michael@0: FinishRead(false); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheIndex::StartReadingJournal() michael@0: { michael@0: LOG(("CacheIndex::StartReadingJournal()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: MOZ_ASSERT(mJournalHandle); michael@0: MOZ_ASSERT(mIndexOnDiskIsValid); michael@0: MOZ_ASSERT(mTmpJournal.Count() == 0); michael@0: MOZ_ASSERT(mJournalHandle->FileSize() >= 0); michael@0: michael@0: int64_t entriesSize = mJournalHandle->FileSize() - michael@0: sizeof(CacheHash::Hash32_t); michael@0: michael@0: if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) { michael@0: LOG(("CacheIndex::StartReadingJournal() - Journal is corrupted")); michael@0: FinishRead(false); michael@0: return; michael@0: } michael@0: michael@0: mSkipEntries = 0; michael@0: mRWHash = new CacheHash(); michael@0: michael@0: mRWBufPos = std::min(mRWBufSize, michael@0: static_cast(mJournalHandle->FileSize())); michael@0: michael@0: rv = CacheFileIOManager::Read(mJournalHandle, 0, mRWBuf, mRWBufPos, true, this); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::StartReadingJournal() - CacheFileIOManager::Read() failed" michael@0: " synchronously [rv=0x%08x]", rv)); michael@0: FinishRead(false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheIndex::ParseJournal() michael@0: { michael@0: LOG(("CacheIndex::ParseRecords()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: uint32_t entryCnt = (mJournalHandle->FileSize() - michael@0: sizeof(CacheHash::Hash32_t)) / sizeof(CacheIndexRecord); michael@0: michael@0: uint32_t pos = 0; michael@0: michael@0: while (pos + sizeof(CacheIndexRecord) <= mRWBufPos && michael@0: mSkipEntries != entryCnt) { michael@0: CacheIndexRecord *rec = reinterpret_cast(mRWBuf + pos); michael@0: CacheIndexEntry tmpEntry(&rec->mHash); michael@0: tmpEntry.ReadFromBuf(mRWBuf + pos); michael@0: michael@0: CacheIndexEntry *entry = mTmpJournal.PutEntry(*tmpEntry.Hash()); michael@0: *entry = tmpEntry; michael@0: michael@0: if (entry->IsDirty() || entry->IsFresh()) { michael@0: LOG(("CacheIndex::ParseJournal() - Invalid entry found in journal, " michael@0: "ignoring whole journal [dirty=%d, fresh=%d]", entry->IsDirty(), michael@0: entry->IsFresh())); michael@0: FinishRead(false); michael@0: return; michael@0: } michael@0: michael@0: pos += sizeof(CacheIndexRecord); michael@0: mSkipEntries++; michael@0: } michael@0: michael@0: mRWHash->Update(mRWBuf, pos); michael@0: michael@0: if (pos != mRWBufPos) { michael@0: memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos); michael@0: mRWBufPos -= pos; michael@0: pos = 0; michael@0: } michael@0: michael@0: int64_t fileOffset = mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos; michael@0: michael@0: MOZ_ASSERT(fileOffset <= mJournalHandle->FileSize()); michael@0: if (fileOffset == mJournalHandle->FileSize()) { michael@0: if (mRWHash->GetHash() != NetworkEndian::readUint32(mRWBuf)) { michael@0: LOG(("CacheIndex::ParseJournal() - Hash mismatch, [is %x, should be %x]", michael@0: mRWHash->GetHash(), michael@0: NetworkEndian::readUint32(mRWBuf))); michael@0: FinishRead(false); michael@0: return; michael@0: } michael@0: michael@0: mJournalReadSuccessfully = true; michael@0: FinishRead(true); michael@0: return; michael@0: } michael@0: michael@0: pos = mRWBufPos; michael@0: uint32_t toRead = std::min(mRWBufSize - pos, michael@0: static_cast(mJournalHandle->FileSize() - michael@0: fileOffset)); michael@0: mRWBufPos = pos + toRead; michael@0: michael@0: rv = CacheFileIOManager::Read(mJournalHandle, fileOffset, mRWBuf + pos, michael@0: toRead, true, this); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::ParseJournal() - CacheFileIOManager::Read() failed " michael@0: "synchronously [rv=0x%08x]", rv)); michael@0: FinishRead(false); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheIndex::MergeJournal() michael@0: { michael@0: LOG(("CacheIndex::MergeJournal()")); michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: mTmpJournal.EnumerateEntries(&CacheIndex::ProcessJournalEntry, this); michael@0: michael@0: MOZ_ASSERT(mTmpJournal.Count() == 0); michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: CacheIndex::ProcessJournalEntry(CacheIndexEntry *aEntry, void* aClosure) michael@0: { michael@0: CacheIndex *index = static_cast(aClosure); michael@0: michael@0: LOG(("CacheFile::ProcessJournalEntry() [hash=%08x%08x%08x%08x%08x]", michael@0: LOGSHA1(aEntry->Hash()))); michael@0: michael@0: CacheIndexEntry *entry = index->mIndex.GetEntry(*aEntry->Hash()); michael@0: michael@0: CacheIndexEntryAutoManage emng(aEntry->Hash(), index); michael@0: michael@0: if (aEntry->IsRemoved()) { michael@0: if (entry) { michael@0: entry->MarkRemoved(); michael@0: entry->MarkDirty(); michael@0: } michael@0: } else { michael@0: if (!entry) { michael@0: entry = index->mIndex.PutEntry(*aEntry->Hash()); michael@0: } michael@0: michael@0: *entry = *aEntry; michael@0: entry->MarkDirty(); michael@0: } michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: void michael@0: CacheIndex::EnsureNoFreshEntry() michael@0: { michael@0: #ifdef DEBUG_STATS michael@0: CacheIndexStats debugStats; michael@0: debugStats.DisableLogging(); michael@0: mIndex.EnumerateEntries(&CacheIndex::SumIndexStats, &debugStats); michael@0: MOZ_ASSERT(debugStats.Fresh() == 0); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: CacheIndex::EnsureCorrectStats() michael@0: { michael@0: #ifdef DEBUG_STATS michael@0: MOZ_ASSERT(mPendingUpdates.Count() == 0); michael@0: CacheIndexStats debugStats; michael@0: debugStats.DisableLogging(); michael@0: mIndex.EnumerateEntries(&CacheIndex::SumIndexStats, &debugStats); michael@0: MOZ_ASSERT(debugStats == mIndexStats); michael@0: #endif michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: CacheIndex::SumIndexStats(CacheIndexEntry *aEntry, void* aClosure) michael@0: { michael@0: CacheIndexStats *stats = static_cast(aClosure); michael@0: stats->BeforeChange(nullptr); michael@0: stats->AfterChange(aEntry); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: CacheIndex::FinishRead(bool aSucceeded) michael@0: { michael@0: LOG(("CacheIndex::FinishRead() [succeeded=%d]", aSucceeded)); michael@0: AssertOwnsLock(); michael@0: michael@0: MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == READING); michael@0: michael@0: MOZ_ASSERT( michael@0: // -> rebuild michael@0: (!aSucceeded && !mIndexOnDiskIsValid && !mJournalReadSuccessfully) || michael@0: // -> update michael@0: (!aSucceeded && mIndexOnDiskIsValid && !mJournalReadSuccessfully) || michael@0: // -> ready michael@0: (aSucceeded && mIndexOnDiskIsValid && mJournalReadSuccessfully)); michael@0: michael@0: if (mState == SHUTDOWN) { michael@0: RemoveFile(NS_LITERAL_CSTRING(kTempIndexName)); michael@0: RemoveFile(NS_LITERAL_CSTRING(kJournalName)); michael@0: } else { michael@0: if (mIndexHandle && !mIndexOnDiskIsValid) { michael@0: CacheFileIOManager::DoomFile(mIndexHandle, nullptr); michael@0: } michael@0: michael@0: if (mJournalHandle) { michael@0: CacheFileIOManager::DoomFile(mJournalHandle, nullptr); michael@0: } michael@0: } michael@0: michael@0: if (mIndexFileOpener) { michael@0: mIndexFileOpener->Cancel(); michael@0: mIndexFileOpener = nullptr; michael@0: } michael@0: if (mJournalFileOpener) { michael@0: mJournalFileOpener->Cancel(); michael@0: mJournalFileOpener = nullptr; michael@0: } michael@0: if (mTmpFileOpener) { michael@0: mTmpFileOpener->Cancel(); michael@0: mTmpFileOpener = nullptr; michael@0: } michael@0: michael@0: mIndexHandle = nullptr; michael@0: mJournalHandle = nullptr; michael@0: mRWHash = nullptr; michael@0: ReleaseBuffer(); michael@0: michael@0: if (mState == SHUTDOWN) { michael@0: return; michael@0: } michael@0: michael@0: if (!mIndexOnDiskIsValid) { michael@0: MOZ_ASSERT(mTmpJournal.Count() == 0); michael@0: EnsureNoFreshEntry(); michael@0: ProcessPendingOperations(); michael@0: // Remove all entries that we haven't seen during this session michael@0: mIndex.EnumerateEntries(&CacheIndex::RemoveNonFreshEntries, this); michael@0: StartUpdatingIndex(true); michael@0: return; michael@0: } michael@0: michael@0: if (!mJournalReadSuccessfully) { michael@0: mTmpJournal.Clear(); michael@0: EnsureNoFreshEntry(); michael@0: ProcessPendingOperations(); michael@0: StartUpdatingIndex(false); michael@0: return; michael@0: } michael@0: michael@0: MergeJournal(); michael@0: EnsureNoFreshEntry(); michael@0: ProcessPendingOperations(); michael@0: mIndexStats.Log(); michael@0: michael@0: ChangeState(READY); michael@0: mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately michael@0: } michael@0: michael@0: // static michael@0: void michael@0: CacheIndex::DelayedUpdate(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: LOG(("CacheIndex::DelayedUpdate()")); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr index = gInstance; michael@0: michael@0: if (!index) { michael@0: return; michael@0: } michael@0: michael@0: CacheIndexAutoLock lock(index); michael@0: michael@0: index->mUpdateTimer = nullptr; michael@0: michael@0: if (!index->IsIndexUsable()) { michael@0: return; michael@0: } michael@0: michael@0: if (index->mState == READY && index->mShuttingDown) { michael@0: return; michael@0: } michael@0: michael@0: // mUpdateEventPending must be false here since StartUpdatingIndex() won't michael@0: // schedule timer if it is true. michael@0: MOZ_ASSERT(!index->mUpdateEventPending); michael@0: if (index->mState != BUILDING && index->mState != UPDATING) { michael@0: LOG(("CacheIndex::DelayedUpdate() - Update was canceled")); michael@0: return; michael@0: } michael@0: michael@0: // We need to redispatch to run with lower priority michael@0: nsRefPtr ioThread = CacheFileIOManager::IOThread(); michael@0: MOZ_ASSERT(ioThread); michael@0: michael@0: index->mUpdateEventPending = true; michael@0: rv = ioThread->Dispatch(index, CacheIOThread::INDEX); michael@0: if (NS_FAILED(rv)) { michael@0: index->mUpdateEventPending = false; michael@0: NS_WARNING("CacheIndex::DelayedUpdate() - Can't dispatch event"); michael@0: LOG(("CacheIndex::DelayedUpdate() - Can't dispatch event" )); michael@0: index->FinishUpdate(false); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::ScheduleUpdateTimer(uint32_t aDelay) michael@0: { michael@0: LOG(("CacheIndex::ScheduleUpdateTimer() [delay=%u]", aDelay)); michael@0: michael@0: MOZ_ASSERT(!mUpdateTimer); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr timer = do_CreateInstance("@mozilla.org/timer;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr ioTarget = CacheFileIOManager::IOTarget(); michael@0: MOZ_ASSERT(ioTarget); michael@0: michael@0: rv = timer->SetTarget(ioTarget); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = timer->InitWithFuncCallback(CacheIndex::DelayedUpdate, nullptr, michael@0: aDelay, nsITimer::TYPE_ONE_SHOT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mUpdateTimer.swap(timer); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::SetupDirectoryEnumerator() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ASSERT(!mDirEnumerator); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr file; michael@0: michael@0: rv = mCacheDirectory->Clone(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = file->AppendNative(NS_LITERAL_CSTRING(kEntriesDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool exists; michael@0: rv = file->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!exists) { michael@0: NS_WARNING("CacheIndex::SetupDirectoryEnumerator() - Entries directory " michael@0: "doesn't exist!"); michael@0: LOG(("CacheIndex::SetupDirectoryEnumerator() - Entries directory doesn't " michael@0: "exist!" )); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsCOMPtr enumerator; michael@0: rv = file->GetDirectoryEntries(getter_AddRefs(enumerator)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mDirEnumerator = do_QueryInterface(enumerator, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CacheIndex::InitEntryFromDiskData(CacheIndexEntry *aEntry, michael@0: CacheFileMetadata *aMetaData, michael@0: int64_t aFileSize) michael@0: { michael@0: aEntry->InitNew(); michael@0: aEntry->MarkDirty(); michael@0: aEntry->MarkFresh(); michael@0: aEntry->Init(aMetaData->AppId(), aMetaData->IsAnonymous(), michael@0: aMetaData->IsInBrowser()); michael@0: michael@0: uint32_t expirationTime; michael@0: aMetaData->GetExpirationTime(&expirationTime); michael@0: aEntry->SetExpirationTime(expirationTime); michael@0: michael@0: uint32_t frecency; michael@0: aMetaData->GetFrecency(&frecency); michael@0: aEntry->SetFrecency(frecency); michael@0: michael@0: aEntry->SetFileSize(static_cast( michael@0: std::min(static_cast(PR_UINT32_MAX), michael@0: (aFileSize + 0x3FF) >> 10))); michael@0: } michael@0: michael@0: bool michael@0: CacheIndex::IsUpdatePending() michael@0: { michael@0: AssertOwnsLock(); michael@0: michael@0: if (mUpdateTimer || mUpdateEventPending) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: CacheIndex::BuildIndex() michael@0: { michael@0: LOG(("CacheIndex::BuildIndex()")); michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: MOZ_ASSERT(mPendingUpdates.Count() == 0); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!mDirEnumerator) { michael@0: { michael@0: // Do not do IO under the lock. michael@0: CacheIndexAutoUnlock unlock(this); michael@0: rv = SetupDirectoryEnumerator(); michael@0: } michael@0: if (mState == SHUTDOWN) { michael@0: // The index was shut down while we released the lock. FinishUpdate() was michael@0: // already called from Shutdown(), so just simply return here. michael@0: return; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: FinishUpdate(false); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: while (true) { michael@0: if (CacheIOThread::YieldAndRerun()) { michael@0: LOG(("CacheIndex::BuildIndex() - Breaking loop for higher level events.")); michael@0: mUpdateEventPending = true; michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr file; michael@0: { michael@0: // Do not do IO under the lock. michael@0: CacheIndexAutoUnlock unlock(this); michael@0: rv = mDirEnumerator->GetNextFile(getter_AddRefs(file)); michael@0: } michael@0: if (mState == SHUTDOWN) { michael@0: return; michael@0: } michael@0: if (!file) { michael@0: FinishUpdate(NS_SUCCEEDED(rv)); michael@0: return; michael@0: } michael@0: michael@0: nsAutoCString leaf; michael@0: rv = file->GetNativeLeafName(leaf); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::BuildIndex() - GetNativeLeafName() failed! Skipping " michael@0: "file.")); michael@0: mDontMarkIndexClean = true; michael@0: continue; michael@0: } michael@0: michael@0: SHA1Sum::Hash hash; michael@0: rv = CacheFileIOManager::StrToHash(leaf, &hash); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::BuildIndex() - Filename is not a hash, removing file. " michael@0: "[name=%s]", leaf.get())); michael@0: file->Remove(false); michael@0: continue; michael@0: } michael@0: michael@0: CacheIndexEntry *entry = mIndex.GetEntry(hash); michael@0: if (entry && entry->IsRemoved()) { michael@0: LOG(("CacheIndex::BuildIndex() - Found file that should not exist. " michael@0: "[name=%s]", leaf.get())); michael@0: entry->Log(); michael@0: MOZ_ASSERT(entry->IsFresh()); michael@0: entry = nullptr; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: nsRefPtr handle; michael@0: CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false, michael@0: getter_AddRefs(handle)); michael@0: #endif michael@0: michael@0: if (entry) { michael@0: // the entry is up to date michael@0: LOG(("CacheIndex::BuildIndex() - Skipping file because the entry is up to" michael@0: " date. [name=%s]", leaf.get())); michael@0: entry->Log(); michael@0: MOZ_ASSERT(entry->IsFresh()); // The entry must be from this session michael@0: // there must be an active CacheFile if the entry is not initialized michael@0: MOZ_ASSERT(entry->IsInitialized() || handle); michael@0: continue; michael@0: } michael@0: michael@0: MOZ_ASSERT(!handle); michael@0: michael@0: nsRefPtr meta = new CacheFileMetadata(); michael@0: int64_t size = 0; michael@0: michael@0: { michael@0: // Do not do IO under the lock. michael@0: CacheIndexAutoUnlock unlock(this); michael@0: rv = meta->SyncReadMetadata(file); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = file->GetFileSize(&size); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::BuildIndex() - Cannot get filesize of file that was" michael@0: " successfully parsed. [name=%s]", leaf.get())); michael@0: } michael@0: } michael@0: } michael@0: if (mState == SHUTDOWN) { michael@0: return; michael@0: } michael@0: michael@0: // Nobody could add the entry while the lock was released since we modify michael@0: // the index only on IO thread and this loop is executed on IO thread too. michael@0: entry = mIndex.GetEntry(hash); michael@0: MOZ_ASSERT(!entry || entry->IsRemoved()); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::BuildIndex() - CacheFileMetadata::SyncReadMetadata() " michael@0: "failed, removing file. [name=%s]", leaf.get())); michael@0: file->Remove(false); michael@0: } else { michael@0: CacheIndexEntryAutoManage entryMng(&hash, this); michael@0: entry = mIndex.PutEntry(hash); michael@0: InitEntryFromDiskData(entry, meta, size); michael@0: LOG(("CacheIndex::BuildIndex() - Added entry to index. [hash=%s]", michael@0: leaf.get())); michael@0: entry->Log(); michael@0: } michael@0: } michael@0: michael@0: NS_NOTREACHED("We should never get here"); michael@0: } michael@0: michael@0: bool michael@0: CacheIndex::StartUpdatingIndexIfNeeded(bool aSwitchingToReadyState) michael@0: { michael@0: // Start updating process when we are in or we are switching to READY state michael@0: // and index needs update, but not during shutdown or when removing all michael@0: // entries. michael@0: if ((mState == READY || aSwitchingToReadyState) && mIndexNeedsUpdate && michael@0: !mShuttingDown && !mRemovingAll) { michael@0: LOG(("CacheIndex::StartUpdatingIndexIfNeeded() - starting update process")); michael@0: mIndexNeedsUpdate = false; michael@0: StartUpdatingIndex(false); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: CacheIndex::StartUpdatingIndex(bool aRebuild) michael@0: { michael@0: LOG(("CacheIndex::StartUpdatingIndex() [rebuild=%d]", aRebuild)); michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: nsresult rv; michael@0: michael@0: mIndexStats.Log(); michael@0: michael@0: ChangeState(aRebuild ? BUILDING : UPDATING); michael@0: mDontMarkIndexClean = false; michael@0: michael@0: if (mShuttingDown || mRemovingAll) { michael@0: FinishUpdate(false); michael@0: return; michael@0: } michael@0: michael@0: if (IsUpdatePending()) { michael@0: LOG(("CacheIndex::StartUpdatingIndex() - Update is already pending")); michael@0: return; michael@0: } michael@0: michael@0: uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds(); michael@0: if (elapsed < kUpdateIndexStartDelay) { michael@0: LOG(("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, " michael@0: "scheduling timer to fire in %u ms.", elapsed, michael@0: kUpdateIndexStartDelay - elapsed)); michael@0: rv = ScheduleUpdateTimer(kUpdateIndexStartDelay - elapsed); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: LOG(("CacheIndex::StartUpdatingIndex() - ScheduleUpdateTimer() failed. " michael@0: "Starting update immediately.")); michael@0: } else { michael@0: LOG(("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, " michael@0: "starting update now.", elapsed)); michael@0: } michael@0: michael@0: nsRefPtr ioThread = CacheFileIOManager::IOThread(); michael@0: MOZ_ASSERT(ioThread); michael@0: michael@0: // We need to dispatch an event even if we are on IO thread since we need to michael@0: // update the index with the correct priority. michael@0: mUpdateEventPending = true; michael@0: rv = ioThread->Dispatch(this, CacheIOThread::INDEX); michael@0: if (NS_FAILED(rv)) { michael@0: mUpdateEventPending = false; michael@0: NS_WARNING("CacheIndex::StartUpdatingIndex() - Can't dispatch event"); michael@0: LOG(("CacheIndex::StartUpdatingIndex() - Can't dispatch event" )); michael@0: FinishUpdate(false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheIndex::UpdateIndex() michael@0: { michael@0: LOG(("CacheIndex::UpdateIndex()")); michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: MOZ_ASSERT(mPendingUpdates.Count() == 0); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!mDirEnumerator) { michael@0: { michael@0: // Do not do IO under the lock. michael@0: CacheIndexAutoUnlock unlock(this); michael@0: rv = SetupDirectoryEnumerator(); michael@0: } michael@0: if (mState == SHUTDOWN) { michael@0: // The index was shut down while we released the lock. FinishUpdate() was michael@0: // already called from Shutdown(), so just simply return here. michael@0: return; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: FinishUpdate(false); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: while (true) { michael@0: if (CacheIOThread::YieldAndRerun()) { michael@0: LOG(("CacheIndex::UpdateIndex() - Breaking loop for higher level " michael@0: "events.")); michael@0: mUpdateEventPending = true; michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr file; michael@0: { michael@0: // Do not do IO under the lock. michael@0: CacheIndexAutoUnlock unlock(this); michael@0: rv = mDirEnumerator->GetNextFile(getter_AddRefs(file)); michael@0: } michael@0: if (mState == SHUTDOWN) { michael@0: return; michael@0: } michael@0: if (!file) { michael@0: FinishUpdate(NS_SUCCEEDED(rv)); michael@0: return; michael@0: } michael@0: michael@0: nsAutoCString leaf; michael@0: rv = file->GetNativeLeafName(leaf); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::UpdateIndex() - GetNativeLeafName() failed! Skipping " michael@0: "file.")); michael@0: mDontMarkIndexClean = true; michael@0: continue; michael@0: } michael@0: michael@0: SHA1Sum::Hash hash; michael@0: rv = CacheFileIOManager::StrToHash(leaf, &hash); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::UpdateIndex() - Filename is not a hash, removing file. " michael@0: "[name=%s]", leaf.get())); michael@0: file->Remove(false); michael@0: continue; michael@0: } michael@0: michael@0: CacheIndexEntry *entry = mIndex.GetEntry(hash); michael@0: if (entry && entry->IsRemoved()) { michael@0: if (entry->IsFresh()) { michael@0: LOG(("CacheIndex::UpdateIndex() - Found file that should not exist. " michael@0: "[name=%s]", leaf.get())); michael@0: entry->Log(); michael@0: } michael@0: entry = nullptr; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: nsRefPtr handle; michael@0: CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false, michael@0: getter_AddRefs(handle)); michael@0: #endif michael@0: michael@0: if (entry && entry->IsFresh()) { michael@0: // the entry is up to date michael@0: LOG(("CacheIndex::UpdateIndex() - Skipping file because the entry is up " michael@0: " to date. [name=%s]", leaf.get())); michael@0: entry->Log(); michael@0: // there must be an active CacheFile if the entry is not initialized michael@0: MOZ_ASSERT(entry->IsInitialized() || handle); michael@0: continue; michael@0: } michael@0: michael@0: MOZ_ASSERT(!handle); michael@0: michael@0: if (entry) { michael@0: PRTime lastModifiedTime; michael@0: { michael@0: // Do not do IO under the lock. michael@0: CacheIndexAutoUnlock unlock(this); michael@0: rv = file->GetLastModifiedTime(&lastModifiedTime); michael@0: } michael@0: if (mState == SHUTDOWN) { michael@0: return; michael@0: } michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::UpdateIndex() - Cannot get lastModifiedTime. " michael@0: "[name=%s]", leaf.get())); michael@0: // Assume the file is newer than index michael@0: } else { michael@0: if (mIndexTimeStamp > (lastModifiedTime / PR_MSEC_PER_SEC)) { michael@0: LOG(("CacheIndex::UpdateIndex() - Skipping file because of last " michael@0: "modified time. [name=%s, indexTimeStamp=%u, " michael@0: "lastModifiedTime=%u]", leaf.get(), mIndexTimeStamp, michael@0: lastModifiedTime / PR_MSEC_PER_SEC)); michael@0: michael@0: CacheIndexEntryAutoManage entryMng(&hash, this); michael@0: entry->MarkFresh(); michael@0: continue; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsRefPtr meta = new CacheFileMetadata(); michael@0: int64_t size = 0; michael@0: michael@0: { michael@0: // Do not do IO under the lock. michael@0: CacheIndexAutoUnlock unlock(this); michael@0: rv = meta->SyncReadMetadata(file); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = file->GetFileSize(&size); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::UpdateIndex() - Cannot get filesize of file that " michael@0: "was successfully parsed. [name=%s]", leaf.get())); michael@0: } michael@0: } michael@0: } michael@0: if (mState == SHUTDOWN) { michael@0: return; michael@0: } michael@0: michael@0: // Nobody could add the entry while the lock was released since we modify michael@0: // the index only on IO thread and this loop is executed on IO thread too. michael@0: entry = mIndex.GetEntry(hash); michael@0: MOZ_ASSERT(!entry || !entry->IsFresh()); michael@0: michael@0: CacheIndexEntryAutoManage entryMng(&hash, this); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::UpdateIndex() - CacheFileMetadata::SyncReadMetadata() " michael@0: "failed, removing file. [name=%s]", leaf.get())); michael@0: file->Remove(false); michael@0: if (entry) { michael@0: entry->MarkRemoved(); michael@0: entry->MarkFresh(); michael@0: entry->MarkDirty(); michael@0: } michael@0: } else { michael@0: entry = mIndex.PutEntry(hash); michael@0: InitEntryFromDiskData(entry, meta, size); michael@0: LOG(("CacheIndex::UpdateIndex() - Added/updated entry to/in index. " michael@0: "[hash=%s]", leaf.get())); michael@0: entry->Log(); michael@0: } michael@0: } michael@0: michael@0: NS_NOTREACHED("We should never get here"); michael@0: } michael@0: michael@0: void michael@0: CacheIndex::FinishUpdate(bool aSucceeded) michael@0: { michael@0: LOG(("CacheIndex::FinishUpdate() [succeeded=%d]", aSucceeded)); michael@0: michael@0: MOZ_ASSERT(mState == UPDATING || mState == BUILDING || michael@0: (!aSucceeded && mState == SHUTDOWN)); michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: if (mDirEnumerator) { michael@0: if (NS_IsMainThread()) { michael@0: LOG(("CacheIndex::FinishUpdate() - posting of PreShutdownInternal failed?" michael@0: " Cannot safely release mDirEnumerator, leaking it!")); michael@0: NS_WARNING(("CacheIndex::FinishUpdate() - Leaking mDirEnumerator!")); michael@0: // This can happen only in case dispatching event to IO thread failed in michael@0: // CacheIndex::PreShutdown(). michael@0: mDirEnumerator.forget(); // Leak it since dir enumerator is not threadsafe michael@0: } else { michael@0: mDirEnumerator->Close(); michael@0: mDirEnumerator = nullptr; michael@0: } michael@0: } michael@0: michael@0: if (!aSucceeded) { michael@0: mDontMarkIndexClean = true; michael@0: } michael@0: michael@0: if (mState == SHUTDOWN) { michael@0: return; michael@0: } michael@0: michael@0: if (mState == UPDATING && aSucceeded) { michael@0: // If we've iterated over all entries successfully then all entries that michael@0: // really exist on the disk are now marked as fresh. All non-fresh entries michael@0: // don't exist anymore and must be removed from the index. michael@0: mIndex.EnumerateEntries(&CacheIndex::RemoveNonFreshEntries, this); michael@0: } michael@0: michael@0: // Make sure we won't start update. If the build or update failed, there is no michael@0: // reason to believe that it will succeed next time. michael@0: mIndexNeedsUpdate = false; michael@0: michael@0: ChangeState(READY); michael@0: mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: CacheIndex::RemoveNonFreshEntries(CacheIndexEntry *aEntry, void* aClosure) michael@0: { michael@0: if (aEntry->IsFresh()) { michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: LOG(("CacheFile::RemoveNonFreshEntries() - Removing entry. " michael@0: "[hash=%08x%08x%08x%08x%08x]", LOGSHA1(aEntry->Hash()))); michael@0: michael@0: CacheIndex *index = static_cast(aClosure); michael@0: michael@0: CacheIndexEntryAutoManage emng(aEntry->Hash(), index); michael@0: emng.DoNotSearchInIndex(); michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: // static michael@0: char const * michael@0: CacheIndex::StateString(EState aState) michael@0: { michael@0: switch (aState) { michael@0: case INITIAL: return "INITIAL"; michael@0: case READING: return "READING"; michael@0: case WRITING: return "WRITING"; michael@0: case BUILDING: return "BUILDING"; michael@0: case UPDATING: return "UPDATING"; michael@0: case READY: return "READY"; michael@0: case SHUTDOWN: return "SHUTDOWN"; michael@0: } michael@0: michael@0: MOZ_ASSERT(false, "Unexpected state!"); michael@0: return "?"; michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: CacheIndex::ChangeState(EState aNewState) michael@0: { michael@0: LOG(("CacheIndex::ChangeState() changing state %s -> %s", StateString(mState), michael@0: StateString(aNewState))); michael@0: michael@0: // All pending updates should be processed before changing state michael@0: MOZ_ASSERT(mPendingUpdates.Count() == 0); michael@0: michael@0: // PreShutdownInternal() should change the state to READY from every state. It michael@0: // may go through different states, but once we are in READY state the only michael@0: // possible transition is to SHUTDOWN state. michael@0: MOZ_ASSERT(!mShuttingDown || mState != READY || aNewState == SHUTDOWN); michael@0: michael@0: // Start updating process when switching to READY state if needed michael@0: if (aNewState == READY && StartUpdatingIndexIfNeeded(true)) { michael@0: return; michael@0: } michael@0: michael@0: // Try to evict entries over limit everytime we're leaving state READING, michael@0: // BUILDING or UPDATING, but not during shutdown or when removing all michael@0: // entries. michael@0: if (!mShuttingDown && !mRemovingAll && aNewState != SHUTDOWN && michael@0: (mState == READING || mState == BUILDING || mState == UPDATING)) { michael@0: CacheFileIOManager::EvictIfOverLimit(); michael@0: } michael@0: michael@0: mState = aNewState; michael@0: michael@0: if (mState != SHUTDOWN) { michael@0: CacheFileIOManager::CacheIndexStateChanged(); michael@0: } michael@0: michael@0: if (mState == READY && mDiskConsumptionObservers.Length()) { michael@0: for (uint32_t i = 0; i < mDiskConsumptionObservers.Length(); ++i) { michael@0: DiskConsumptionObserver* o = mDiskConsumptionObservers[i]; michael@0: // Safe to call under the lock. We always post to the main thread. michael@0: o->OnDiskConsumption(mIndexStats.Size() << 10); michael@0: } michael@0: michael@0: mDiskConsumptionObservers.Clear(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheIndex::AllocBuffer() michael@0: { michael@0: switch (mState) { michael@0: case WRITING: michael@0: mRWBufSize = sizeof(CacheIndexHeader) + sizeof(CacheHash::Hash32_t) + michael@0: mProcessEntries * sizeof(CacheIndexRecord); michael@0: if (mRWBufSize > kMaxBufSize) { michael@0: mRWBufSize = kMaxBufSize; michael@0: } michael@0: break; michael@0: case READING: michael@0: mRWBufSize = kMaxBufSize; michael@0: break; michael@0: default: michael@0: MOZ_ASSERT(false, "Unexpected state!"); michael@0: } michael@0: michael@0: mRWBuf = static_cast(moz_xmalloc(mRWBufSize)); michael@0: } michael@0: michael@0: void michael@0: CacheIndex::ReleaseBuffer() michael@0: { michael@0: if (!mRWBuf) { michael@0: return; michael@0: } michael@0: michael@0: free(mRWBuf); michael@0: mRWBuf = nullptr; michael@0: mRWBufSize = 0; michael@0: mRWBufPos = 0; michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: class FrecencyComparator michael@0: { michael@0: public: michael@0: bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const { michael@0: return a->mFrecency == b->mFrecency; michael@0: } michael@0: bool LessThan(CacheIndexRecord* a, CacheIndexRecord* b) const { michael@0: // Place entries with frecency 0 at the end of the array. michael@0: if (a->mFrecency == 0) { michael@0: return false; michael@0: } michael@0: if (b->mFrecency == 0) { michael@0: return true; michael@0: } michael@0: return a->mFrecency < b->mFrecency; michael@0: } michael@0: }; michael@0: michael@0: class ExpirationComparator michael@0: { michael@0: public: michael@0: bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const { michael@0: return a->mExpirationTime == b->mExpirationTime; michael@0: } michael@0: bool LessThan(CacheIndexRecord* a, CacheIndexRecord* b) const { michael@0: return a->mExpirationTime < b->mExpirationTime; michael@0: } michael@0: }; michael@0: michael@0: } // anon michael@0: michael@0: void michael@0: CacheIndex::InsertRecordToFrecencyArray(CacheIndexRecord *aRecord) michael@0: { michael@0: LOG(("CacheIndex::InsertRecordToFrecencyArray() [record=%p, hash=%08x%08x%08x" michael@0: "%08x%08x]", aRecord, LOGSHA1(aRecord->mHash))); michael@0: michael@0: MOZ_ASSERT(!mFrecencyArray.Contains(aRecord)); michael@0: mFrecencyArray.InsertElementSorted(aRecord, FrecencyComparator()); michael@0: } michael@0: michael@0: void michael@0: CacheIndex::InsertRecordToExpirationArray(CacheIndexRecord *aRecord) michael@0: { michael@0: LOG(("CacheIndex::InsertRecordToExpirationArray() [record=%p, hash=%08x%08x" michael@0: "%08x%08x%08x]", aRecord, LOGSHA1(aRecord->mHash))); michael@0: michael@0: MOZ_ASSERT(!mExpirationArray.Contains(aRecord)); michael@0: mExpirationArray.InsertElementSorted(aRecord, ExpirationComparator()); michael@0: } michael@0: michael@0: void michael@0: CacheIndex::RemoveRecordFromFrecencyArray(CacheIndexRecord *aRecord) michael@0: { michael@0: LOG(("CacheIndex::RemoveRecordFromFrecencyArray() [record=%p]", aRecord)); michael@0: michael@0: DebugOnly removed; michael@0: removed = mFrecencyArray.RemoveElement(aRecord); michael@0: MOZ_ASSERT(removed); michael@0: } michael@0: michael@0: void michael@0: CacheIndex::RemoveRecordFromExpirationArray(CacheIndexRecord *aRecord) michael@0: { michael@0: LOG(("CacheIndex::RemoveRecordFromExpirationArray() [record=%p]", aRecord)); michael@0: michael@0: DebugOnly removed; michael@0: removed = mExpirationArray.RemoveElement(aRecord); michael@0: MOZ_ASSERT(removed); michael@0: } michael@0: michael@0: void michael@0: CacheIndex::AddRecordToIterators(CacheIndexRecord *aRecord) michael@0: { michael@0: AssertOwnsLock(); michael@0: michael@0: for (uint32_t i = 0; i < mIterators.Length(); ++i) { michael@0: // Add a new record only when iterator is supposed to be updated. michael@0: if (mIterators[i]->ShouldBeNewAdded()) { michael@0: mIterators[i]->AddRecord(aRecord); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheIndex::RemoveRecordFromIterators(CacheIndexRecord *aRecord) michael@0: { michael@0: AssertOwnsLock(); michael@0: michael@0: for (uint32_t i = 0; i < mIterators.Length(); ++i) { michael@0: // Remove the record from iterator always, it makes no sence to return michael@0: // non-existing entries. Also the pointer to the record is no longer valid michael@0: // once the entry is removed from index. michael@0: mIterators[i]->RemoveRecord(aRecord); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheIndex::ReplaceRecordInIterators(CacheIndexRecord *aOldRecord, michael@0: CacheIndexRecord *aNewRecord) michael@0: { michael@0: AssertOwnsLock(); michael@0: michael@0: for (uint32_t i = 0; i < mIterators.Length(); ++i) { michael@0: // We have to replace the record always since the pointer is no longer michael@0: // valid after this point. NOTE: Replacing the record doesn't mean that michael@0: // a new entry was added, it just means that the data in the entry was michael@0: // changed (e.g. a file size) and we had to track this change in michael@0: // mPendingUpdates since mIndex was read-only. michael@0: mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::Run() michael@0: { michael@0: LOG(("CacheIndex::Run()")); michael@0: michael@0: CacheIndexAutoLock lock(this); michael@0: michael@0: if (!IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (mState == READY && mShuttingDown) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mUpdateEventPending = false; michael@0: michael@0: switch (mState) { michael@0: case BUILDING: michael@0: BuildIndex(); michael@0: break; michael@0: case UPDATING: michael@0: UpdateIndex(); michael@0: break; michael@0: default: michael@0: LOG(("CacheIndex::Run() - Update/Build was canceled")); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::OnFileOpenedInternal(FileOpenHelper *aOpener, michael@0: CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: LOG(("CacheIndex::OnFileOpenedInternal() [opener=%p, handle=%p, " michael@0: "result=0x%08x]", aOpener, aHandle, aResult)); michael@0: michael@0: nsresult rv; michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: if (!IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (mState == READY && mShuttingDown) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: switch (mState) { michael@0: case WRITING: michael@0: MOZ_ASSERT(aOpener == mIndexFileOpener); michael@0: mIndexFileOpener = nullptr; michael@0: michael@0: if (NS_FAILED(aResult)) { michael@0: LOG(("CacheIndex::OnFileOpenedInternal() - Can't open index file for " michael@0: "writing [rv=0x%08x]", aResult)); michael@0: FinishWrite(false); michael@0: } else { michael@0: mIndexHandle = aHandle; michael@0: WriteRecords(); michael@0: } michael@0: break; michael@0: case READING: michael@0: if (aOpener == mIndexFileOpener) { michael@0: mIndexFileOpener = nullptr; michael@0: michael@0: if (NS_SUCCEEDED(aResult)) { michael@0: if (aHandle->FileSize() == 0) { michael@0: FinishRead(false); michael@0: CacheFileIOManager::DoomFile(aHandle, nullptr); michael@0: break; michael@0: } else { michael@0: mIndexHandle = aHandle; michael@0: } michael@0: } else { michael@0: FinishRead(false); michael@0: break; michael@0: } michael@0: } else if (aOpener == mJournalFileOpener) { michael@0: mJournalFileOpener = nullptr; michael@0: mJournalHandle = aHandle; michael@0: } else if (aOpener == mTmpFileOpener) { michael@0: mTmpFileOpener = nullptr; michael@0: mTmpHandle = aHandle; michael@0: } else { michael@0: MOZ_ASSERT(false, "Unexpected state!"); michael@0: } michael@0: michael@0: if (mIndexFileOpener || mJournalFileOpener || mTmpFileOpener) { michael@0: // Some opener still didn't finish michael@0: break; michael@0: } michael@0: michael@0: // We fail and cancel all other openers when we opening index file fails. michael@0: MOZ_ASSERT(mIndexHandle); michael@0: michael@0: if (mTmpHandle) { michael@0: CacheFileIOManager::DoomFile(mTmpHandle, nullptr); michael@0: mTmpHandle = nullptr; michael@0: michael@0: if (mJournalHandle) { // this shouldn't normally happen michael@0: LOG(("CacheIndex::OnFileOpenedInternal() - Unexpected state, all " michael@0: "files [%s, %s, %s] should never exist. Removing whole index.", michael@0: kIndexName, kJournalName, kTempIndexName)); michael@0: FinishRead(false); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (mJournalHandle) { michael@0: // Rename journal to make sure we update index on next start in case michael@0: // firefox crashes michael@0: rv = CacheFileIOManager::RenameFile( michael@0: mJournalHandle, NS_LITERAL_CSTRING(kTempIndexName), this); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::OnFileOpenedInternal() - CacheFileIOManager::" michael@0: "RenameFile() failed synchronously [rv=0x%08x]", rv)); michael@0: FinishRead(false); michael@0: break; michael@0: } michael@0: } else { michael@0: StartReadingIndex(); michael@0: } michael@0: michael@0: break; michael@0: default: michael@0: MOZ_ASSERT(false, "Unexpected state!"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("CacheIndex::OnFileOpened should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, michael@0: nsresult aResult) michael@0: { michael@0: LOG(("CacheIndex::OnDataWritten() [handle=%p, result=0x%08x]", aHandle, michael@0: aResult)); michael@0: michael@0: nsresult rv; michael@0: michael@0: CacheIndexAutoLock lock(this); michael@0: michael@0: if (!IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (mState == READY && mShuttingDown) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: switch (mState) { michael@0: case WRITING: michael@0: if (mIndexHandle != aHandle) { michael@0: LOG(("CacheIndex::OnDataWritten() - ignoring notification since it " michael@0: "belongs to previously canceled operation [state=%d]", mState)); michael@0: break; michael@0: } michael@0: michael@0: if (NS_FAILED(aResult)) { michael@0: FinishWrite(false); michael@0: } else { michael@0: if (mSkipEntries == mProcessEntries) { michael@0: rv = CacheFileIOManager::RenameFile(mIndexHandle, michael@0: NS_LITERAL_CSTRING(kIndexName), michael@0: this); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheIndex::OnDataWritten() - CacheFileIOManager::" michael@0: "RenameFile() failed synchronously [rv=0x%08x]", rv)); michael@0: FinishWrite(false); michael@0: } michael@0: } else { michael@0: WriteRecords(); michael@0: } michael@0: } michael@0: break; michael@0: default: michael@0: // Writing was canceled. michael@0: LOG(("CacheIndex::OnDataWritten() - ignoring notification since the " michael@0: "operation was previously canceled [state=%d]", mState)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) michael@0: { michael@0: LOG(("CacheIndex::OnDataRead() [handle=%p, result=0x%08x]", aHandle, michael@0: aResult)); michael@0: michael@0: CacheIndexAutoLock lock(this); michael@0: michael@0: if (!IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: switch (mState) { michael@0: case READING: michael@0: MOZ_ASSERT(mIndexHandle == aHandle || mJournalHandle == aHandle); michael@0: michael@0: if (NS_FAILED(aResult)) { michael@0: FinishRead(false); michael@0: } else { michael@0: if (!mIndexOnDiskIsValid) { michael@0: ParseRecords(); michael@0: } else { michael@0: ParseJournal(); michael@0: } michael@0: } michael@0: break; michael@0: default: michael@0: // Reading was canceled. michael@0: LOG(("CacheIndex::OnDataRead() - ignoring notification since the " michael@0: "operation was previously canceled [state=%d]", mState)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("CacheIndex::OnFileDoomed should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("CacheIndex::OnEOFSet should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheIndex::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: LOG(("CacheIndex::OnFileRenamed() [handle=%p, result=0x%08x]", aHandle, michael@0: aResult)); michael@0: michael@0: CacheIndexAutoLock lock(this); michael@0: michael@0: if (!IsIndexUsable()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (mState == READY && mShuttingDown) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: switch (mState) { michael@0: case WRITING: michael@0: // This is a result of renaming the new index written to tmpfile to index michael@0: // file. This is the last step when writing the index and the whole michael@0: // writing process is successful iff renaming was successful. michael@0: michael@0: if (mIndexHandle != aHandle) { michael@0: LOG(("CacheIndex::OnFileRenamed() - ignoring notification since it " michael@0: "belongs to previously canceled operation [state=%d]", mState)); michael@0: break; michael@0: } michael@0: michael@0: FinishWrite(NS_SUCCEEDED(aResult)); michael@0: break; michael@0: case READING: michael@0: // This is a result of renaming journal file to tmpfile. It is renamed michael@0: // before we start reading index and journal file and it should normally michael@0: // succeed. If it fails give up reading of index. michael@0: michael@0: if (mJournalHandle != aHandle) { michael@0: LOG(("CacheIndex::OnFileRenamed() - ignoring notification since it " michael@0: "belongs to previously canceled operation [state=%d]", mState)); michael@0: break; michael@0: } michael@0: michael@0: if (NS_FAILED(aResult)) { michael@0: FinishRead(false); michael@0: } else { michael@0: StartReadingIndex(); michael@0: } michael@0: break; michael@0: default: michael@0: // Reading/writing was canceled. michael@0: LOG(("CacheIndex::OnFileRenamed() - ignoring notification since the " michael@0: "operation was previously canceled [state=%d]", mState)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Memory reporting michael@0: michael@0: namespace { // anon michael@0: michael@0: size_t michael@0: CollectIndexEntryMemory(CacheIndexEntry* aEntry, michael@0: mozilla::MallocSizeOf mallocSizeOf, michael@0: void *arg) michael@0: { michael@0: return aEntry->SizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: } // anon michael@0: michael@0: size_t michael@0: CacheIndex::SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: CacheIndexAutoLock lock(const_cast(this)); michael@0: michael@0: size_t n = 0; michael@0: nsCOMPtr sizeOf; michael@0: michael@0: // mIndexHandle and mJournalHandle are reported via SizeOfHandlesRunnable michael@0: // in CacheFileIOManager::SizeOfExcludingThisInternal as part of special michael@0: // handles array. michael@0: michael@0: sizeOf = do_QueryInterface(mCacheDirectory); michael@0: if (sizeOf) { michael@0: n += sizeOf->SizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: sizeOf = do_QueryInterface(mUpdateTimer); michael@0: if (sizeOf) { michael@0: n += sizeOf->SizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: n += mallocSizeOf(mRWBuf); michael@0: n += mallocSizeOf(mRWHash); michael@0: michael@0: n += mIndex.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf); michael@0: n += mPendingUpdates.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf); michael@0: n += mTmpJournal.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf); michael@0: michael@0: // mFrecencyArray and mExpirationArray items are reported by michael@0: // mIndex/mPendingUpdates michael@0: n += mFrecencyArray.SizeOfExcludingThis(mallocSizeOf); michael@0: n += mExpirationArray.SizeOfExcludingThis(mallocSizeOf); michael@0: n += mDiskConsumptionObservers.SizeOfExcludingThis(mallocSizeOf); michael@0: michael@0: return n; michael@0: } michael@0: michael@0: // static michael@0: size_t michael@0: CacheIndex::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) michael@0: { michael@0: if (!gInstance) michael@0: return 0; michael@0: michael@0: return gInstance->SizeOfExcludingThisInternal(mallocSizeOf); michael@0: } michael@0: michael@0: // static michael@0: size_t michael@0: CacheIndex::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) michael@0: { michael@0: return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: } // net michael@0: } // mozilla