summaryrefslogtreecommitdiffstats
path: root/netwerk/cache2/CacheIndex.h
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/cache2/CacheIndex.h')
-rw-r--r--netwerk/cache2/CacheIndex.h1311
1 files changed, 1311 insertions, 0 deletions
diff --git a/netwerk/cache2/CacheIndex.h b/netwerk/cache2/CacheIndex.h
new file mode 100644
index 0000000000..053c8ce654
--- /dev/null
+++ b/netwerk/cache2/CacheIndex.h
@@ -0,0 +1,1311 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheIndex__h__
+#define CacheIndex__h__
+
+#include "CacheLog.h"
+#include "CacheFileIOManager.h"
+#include "nsIRunnable.h"
+#include "CacheHashUtils.h"
+#include "nsICacheStorageService.h"
+#include "nsICacheEntry.h"
+#include "nsILoadContextInfo.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsTHashtable.h"
+#include "nsThreadUtils.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+
+class nsIFile;
+class nsIDirectoryEnumerator;
+class nsITimer;
+
+#ifdef DEBUG
+# define DEBUG_STATS 1
+#endif
+
+namespace mozilla {
+namespace net {
+
+class CacheFileMetadata;
+class FileOpenHelper;
+class CacheIndexIterator;
+
+const uint16_t kIndexTimeNotAvailable = 0xFFFFU;
+const uint16_t kIndexTimeOutOfBound = 0xFFFEU;
+
+using CacheIndexHeader = struct {
+ // Version of the index. The index must be ignored and deleted when the file
+ // on disk was written with a newer version.
+ uint32_t mVersion;
+
+ // Timestamp of time when the last successful write of the index started.
+ // During update process we use this timestamp for a quick validation of entry
+ // files. If last modified time of the file is lower than this timestamp, we
+ // skip parsing of such file since the information in index should be up to
+ // date.
+ uint32_t mTimeStamp;
+
+ // We set this flag as soon as possible after parsing index during startup
+ // and clean it after we write journal to disk during shutdown. We ignore the
+ // journal and start update process whenever this flag is set during index
+ // parsing.
+ uint32_t mIsDirty;
+
+ // The amount of data written to the cache. When it reaches
+ // kTelemetryReportBytesLimit a telemetry report is sent and the counter is
+ // reset.
+ uint32_t mKBWritten;
+};
+
+static_assert(sizeof(CacheIndexHeader::mVersion) +
+ sizeof(CacheIndexHeader::mTimeStamp) +
+ sizeof(CacheIndexHeader::mIsDirty) +
+ sizeof(CacheIndexHeader::mKBWritten) ==
+ sizeof(CacheIndexHeader),
+ "Unexpected sizeof(CacheIndexHeader)!");
+
+#pragma pack(push, 1)
+struct CacheIndexRecord {
+ SHA1Sum::Hash mHash{};
+ uint32_t mFrecency{0};
+ OriginAttrsHash mOriginAttrsHash{0};
+ uint16_t mOnStartTime{kIndexTimeNotAvailable};
+ uint16_t mOnStopTime{kIndexTimeNotAvailable};
+ uint8_t mContentType{nsICacheEntry::CONTENT_TYPE_UNKNOWN};
+
+ /*
+ * 1000 0000 0000 0000 0000 0000 0000 0000 : initialized
+ * 0100 0000 0000 0000 0000 0000 0000 0000 : anonymous
+ * 0010 0000 0000 0000 0000 0000 0000 0000 : removed
+ * 0001 0000 0000 0000 0000 0000 0000 0000 : dirty
+ * 0000 1000 0000 0000 0000 0000 0000 0000 : fresh
+ * 0000 0100 0000 0000 0000 0000 0000 0000 : pinned
+ * 0000 0010 0000 0000 0000 0000 0000 0000 : has cached alt data
+ * 0000 0001 0000 0000 0000 0000 0000 0000 : reserved
+ * 0000 0000 1111 1111 1111 1111 1111 1111 : file size (in kB)
+ */
+ uint32_t mFlags{0};
+
+ CacheIndexRecord() = default;
+};
+#pragma pack(pop)
+
+static_assert(sizeof(CacheIndexRecord::mHash) +
+ sizeof(CacheIndexRecord::mFrecency) +
+ sizeof(CacheIndexRecord::mOriginAttrsHash) +
+ sizeof(CacheIndexRecord::mOnStartTime) +
+ sizeof(CacheIndexRecord::mOnStopTime) +
+ sizeof(CacheIndexRecord::mContentType) +
+ sizeof(CacheIndexRecord::mFlags) ==
+ sizeof(CacheIndexRecord),
+ "Unexpected sizeof(CacheIndexRecord)!");
+
+class CacheIndexRecordWrapper final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(
+ CacheIndexRecordWrapper, DispatchDeleteSelfToCurrentThread());
+
+ CacheIndexRecordWrapper() : mRec(MakeUnique<CacheIndexRecord>()) {}
+ CacheIndexRecord* Get() { return mRec.get(); }
+
+ private:
+ ~CacheIndexRecordWrapper();
+ void DispatchDeleteSelfToCurrentThread();
+ UniquePtr<CacheIndexRecord> mRec;
+ friend class DeleteCacheIndexRecordWrapper;
+};
+
+class CacheIndexEntry : public PLDHashEntryHdr {
+ public:
+ using KeyType = const SHA1Sum::Hash&;
+ using KeyTypePointer = const SHA1Sum::Hash*;
+
+ explicit CacheIndexEntry(KeyTypePointer aKey) {
+ MOZ_COUNT_CTOR(CacheIndexEntry);
+ mRec = new CacheIndexRecordWrapper();
+ LOG(("CacheIndexEntry::CacheIndexEntry() - Created record [rec=%p]",
+ mRec->Get()));
+ memcpy(&mRec->Get()->mHash, aKey, sizeof(SHA1Sum::Hash));
+ }
+ CacheIndexEntry(const CacheIndexEntry& aOther) {
+ MOZ_ASSERT_UNREACHABLE("CacheIndexEntry copy constructor is forbidden!");
+ }
+ ~CacheIndexEntry() {
+ MOZ_COUNT_DTOR(CacheIndexEntry);
+ LOG(("CacheIndexEntry::~CacheIndexEntry() - Deleting record [rec=%p]",
+ mRec->Get()));
+ }
+
+ // KeyEquals(): does this entry match this key?
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return memcmp(&mRec->Get()->mHash, aKey, sizeof(SHA1Sum::Hash)) == 0;
+ }
+
+ // KeyToPointer(): Convert KeyType to KeyTypePointer
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+ // HashKey(): calculate the hash number
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return (reinterpret_cast<const uint32_t*>(aKey))[0];
+ }
+
+ // ALLOW_MEMMOVE can we move this class with memmove(), or do we have
+ // to use the copy constructor?
+ enum { ALLOW_MEMMOVE = true };
+
+ bool operator==(const CacheIndexEntry& aOther) const {
+ return KeyEquals(&aOther.mRec->Get()->mHash);
+ }
+
+ CacheIndexEntry& operator=(const CacheIndexEntry& aOther) {
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, &aOther.mRec->Get()->mHash,
+ sizeof(SHA1Sum::Hash)) == 0);
+ mRec->Get()->mFrecency = aOther.mRec->Get()->mFrecency;
+ mRec->Get()->mOriginAttrsHash = aOther.mRec->Get()->mOriginAttrsHash;
+ mRec->Get()->mOnStartTime = aOther.mRec->Get()->mOnStartTime;
+ mRec->Get()->mOnStopTime = aOther.mRec->Get()->mOnStopTime;
+ mRec->Get()->mContentType = aOther.mRec->Get()->mContentType;
+ mRec->Get()->mFlags = aOther.mRec->Get()->mFlags;
+ return *this;
+ }
+
+ void InitNew() {
+ mRec->Get()->mFrecency = 0;
+ mRec->Get()->mOriginAttrsHash = 0;
+ mRec->Get()->mOnStartTime = kIndexTimeNotAvailable;
+ mRec->Get()->mOnStopTime = kIndexTimeNotAvailable;
+ mRec->Get()->mContentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+ mRec->Get()->mFlags = 0;
+ }
+
+ void Init(OriginAttrsHash aOriginAttrsHash, bool aAnonymous, bool aPinned) {
+ MOZ_ASSERT(mRec->Get()->mFrecency == 0);
+ MOZ_ASSERT(mRec->Get()->mOriginAttrsHash == 0);
+ MOZ_ASSERT(mRec->Get()->mOnStartTime == kIndexTimeNotAvailable);
+ MOZ_ASSERT(mRec->Get()->mOnStopTime == kIndexTimeNotAvailable);
+ MOZ_ASSERT(mRec->Get()->mContentType ==
+ nsICacheEntry::CONTENT_TYPE_UNKNOWN);
+ // When we init the entry it must be fresh and may be dirty
+ MOZ_ASSERT((mRec->Get()->mFlags & ~kDirtyMask) == kFreshMask);
+
+ mRec->Get()->mOriginAttrsHash = aOriginAttrsHash;
+ mRec->Get()->mFlags |= kInitializedMask;
+ if (aAnonymous) {
+ mRec->Get()->mFlags |= kAnonymousMask;
+ }
+ if (aPinned) {
+ mRec->Get()->mFlags |= kPinnedMask;
+ }
+ }
+
+ const SHA1Sum::Hash* Hash() const { return &mRec->Get()->mHash; }
+
+ bool IsInitialized() const {
+ return !!(mRec->Get()->mFlags & kInitializedMask);
+ }
+
+ mozilla::net::OriginAttrsHash OriginAttrsHash() const {
+ return mRec->Get()->mOriginAttrsHash;
+ }
+
+ bool Anonymous() const { return !!(mRec->Get()->mFlags & kAnonymousMask); }
+
+ bool IsRemoved() const { return !!(mRec->Get()->mFlags & kRemovedMask); }
+ void MarkRemoved() { mRec->Get()->mFlags |= kRemovedMask; }
+
+ bool IsDirty() const { return !!(mRec->Get()->mFlags & kDirtyMask); }
+ void MarkDirty() { mRec->Get()->mFlags |= kDirtyMask; }
+ void ClearDirty() { mRec->Get()->mFlags &= ~kDirtyMask; }
+
+ bool IsFresh() const { return !!(mRec->Get()->mFlags & kFreshMask); }
+ void MarkFresh() { mRec->Get()->mFlags |= kFreshMask; }
+
+ bool IsPinned() const { return !!(mRec->Get()->mFlags & kPinnedMask); }
+
+ void SetFrecency(uint32_t aFrecency) { mRec->Get()->mFrecency = aFrecency; }
+ uint32_t GetFrecency() const { return mRec->Get()->mFrecency; }
+
+ void SetHasAltData(bool aHasAltData) {
+ aHasAltData ? mRec->Get()->mFlags |= kHasAltDataMask
+ : mRec->Get()->mFlags &= ~kHasAltDataMask;
+ }
+ bool GetHasAltData() const {
+ return !!(mRec->Get()->mFlags & kHasAltDataMask);
+ }
+
+ void SetOnStartTime(uint16_t aTime) { mRec->Get()->mOnStartTime = aTime; }
+ uint16_t GetOnStartTime() const { return mRec->Get()->mOnStartTime; }
+
+ void SetOnStopTime(uint16_t aTime) { mRec->Get()->mOnStopTime = aTime; }
+ uint16_t GetOnStopTime() const { return mRec->Get()->mOnStopTime; }
+
+ void SetContentType(uint8_t aType) { mRec->Get()->mContentType = aType; }
+ uint8_t GetContentType() const { return GetContentType(mRec->Get()); }
+ static uint8_t GetContentType(CacheIndexRecord* aRec) {
+ if (aRec->mContentType >= nsICacheEntry::CONTENT_TYPE_LAST) {
+ LOG(
+ ("CacheIndexEntry::GetContentType() - Found invalid content type "
+ "[hash=%08x%08x%08x%08x%08x, contentType=%u]",
+ LOGSHA1(aRec->mHash), aRec->mContentType));
+ return nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+ }
+ return aRec->mContentType;
+ }
+
+ // Sets filesize in kilobytes.
+ void SetFileSize(uint32_t aFileSize) {
+ if (aFileSize > kFileSizeMask) {
+ LOG(
+ ("CacheIndexEntry::SetFileSize() - FileSize is too large, "
+ "truncating to %u",
+ kFileSizeMask));
+ aFileSize = kFileSizeMask;
+ }
+ mRec->Get()->mFlags &= ~kFileSizeMask;
+ mRec->Get()->mFlags |= aFileSize;
+ }
+ // Returns filesize in kilobytes.
+ uint32_t GetFileSize() const { return GetFileSize(*(mRec->Get())); }
+ static uint32_t GetFileSize(const CacheIndexRecord& aRec) {
+ return aRec.mFlags & kFileSizeMask;
+ }
+ static uint32_t IsPinned(CacheIndexRecord* aRec) {
+ return aRec->mFlags & kPinnedMask;
+ }
+ bool IsFileEmpty() const { return GetFileSize() == 0; }
+
+ void WriteToBuf(void* aBuf) {
+ uint8_t* ptr = static_cast<uint8_t*>(aBuf);
+ memcpy(ptr, mRec->Get()->mHash, sizeof(SHA1Sum::Hash));
+ ptr += sizeof(SHA1Sum::Hash);
+ NetworkEndian::writeUint32(ptr, mRec->Get()->mFrecency);
+ ptr += sizeof(uint32_t);
+ NetworkEndian::writeUint64(ptr, mRec->Get()->mOriginAttrsHash);
+ ptr += sizeof(uint64_t);
+ NetworkEndian::writeUint16(ptr, mRec->Get()->mOnStartTime);
+ ptr += sizeof(uint16_t);
+ NetworkEndian::writeUint16(ptr, mRec->Get()->mOnStopTime);
+ ptr += sizeof(uint16_t);
+ *ptr = mRec->Get()->mContentType;
+ ptr += sizeof(uint8_t);
+ // Dirty and fresh flags should never go to disk, since they make sense only
+ // during current session.
+ NetworkEndian::writeUint32(
+ ptr, mRec->Get()->mFlags & ~(kDirtyMask | kFreshMask));
+ }
+
+ void ReadFromBuf(void* aBuf) {
+ const uint8_t* ptr = static_cast<const uint8_t*>(aBuf);
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, ptr, sizeof(SHA1Sum::Hash)) == 0);
+ ptr += sizeof(SHA1Sum::Hash);
+ mRec->Get()->mFrecency = NetworkEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ mRec->Get()->mOriginAttrsHash = NetworkEndian::readUint64(ptr);
+ ptr += sizeof(uint64_t);
+ mRec->Get()->mOnStartTime = NetworkEndian::readUint16(ptr);
+ ptr += sizeof(uint16_t);
+ mRec->Get()->mOnStopTime = NetworkEndian::readUint16(ptr);
+ ptr += sizeof(uint16_t);
+ mRec->Get()->mContentType = *ptr;
+ ptr += sizeof(uint8_t);
+ mRec->Get()->mFlags = NetworkEndian::readUint32(ptr);
+ }
+
+ void Log() const {
+ LOG(
+ ("CacheIndexEntry::Log() [this=%p, hash=%08x%08x%08x%08x%08x, fresh=%u,"
+ " initialized=%u, removed=%u, dirty=%u, anonymous=%u, "
+ "originAttrsHash=%" PRIx64 ", frecency=%u, hasAltData=%u, "
+ "onStartTime=%u, onStopTime=%u, contentType=%u, size=%u]",
+ this, LOGSHA1(mRec->Get()->mHash), IsFresh(), IsInitialized(),
+ IsRemoved(), IsDirty(), Anonymous(), OriginAttrsHash(), GetFrecency(),
+ GetHasAltData(), GetOnStartTime(), GetOnStopTime(), GetContentType(),
+ GetFileSize()));
+ }
+
+ static bool RecordMatchesLoadContextInfo(CacheIndexRecordWrapper* aRec,
+ nsILoadContextInfo* aInfo) {
+ MOZ_ASSERT(aInfo);
+
+ return !aInfo->IsPrivate() &&
+ GetOriginAttrsHash(*aInfo->OriginAttributesPtr()) ==
+ aRec->Get()->mOriginAttrsHash &&
+ aInfo->IsAnonymous() == !!(aRec->Get()->mFlags & kAnonymousMask);
+ }
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(mRec->Get());
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+ }
+
+ private:
+ friend class CacheIndexEntryUpdate;
+ friend class CacheIndex;
+ friend class CacheIndexEntryAutoManage;
+ friend struct CacheIndexRecord;
+
+ static const uint32_t kInitializedMask = 0x80000000;
+ static const uint32_t kAnonymousMask = 0x40000000;
+
+ // This flag is set when the entry was removed. We need to keep this
+ // information in memory until we write the index file.
+ static const uint32_t kRemovedMask = 0x20000000;
+
+ // This flag is set when the information in memory is not in sync with the
+ // information in index file on disk.
+ static const uint32_t kDirtyMask = 0x10000000;
+
+ // This flag is set when the information about the entry is fresh, i.e.
+ // we've created or opened this entry during this session, or we've seen
+ // this entry during update or build process.
+ static const uint32_t kFreshMask = 0x08000000;
+
+ // Indicates a pinned entry.
+ static const uint32_t kPinnedMask = 0x04000000;
+
+ // Indicates there is cached alternative data in the entry.
+ static const uint32_t kHasAltDataMask = 0x02000000;
+ static const uint32_t kReservedMask = 0x01000000;
+
+ // FileSize in kilobytes
+ static const uint32_t kFileSizeMask = 0x00FFFFFF;
+
+ RefPtr<CacheIndexRecordWrapper> mRec;
+};
+
+class CacheIndexEntryUpdate : public CacheIndexEntry {
+ public:
+ explicit CacheIndexEntryUpdate(CacheIndexEntry::KeyTypePointer aKey)
+ : CacheIndexEntry(aKey), mUpdateFlags(0) {
+ MOZ_COUNT_CTOR(CacheIndexEntryUpdate);
+ LOG(("CacheIndexEntryUpdate::CacheIndexEntryUpdate()"));
+ }
+ ~CacheIndexEntryUpdate() {
+ MOZ_COUNT_DTOR(CacheIndexEntryUpdate);
+ LOG(("CacheIndexEntryUpdate::~CacheIndexEntryUpdate()"));
+ }
+
+ CacheIndexEntryUpdate& operator=(const CacheIndexEntry& aOther) {
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, &aOther.mRec->Get()->mHash,
+ sizeof(SHA1Sum::Hash)) == 0);
+ mUpdateFlags = 0;
+ *(static_cast<CacheIndexEntry*>(this)) = aOther;
+ return *this;
+ }
+
+ void InitNew() {
+ mUpdateFlags = kFrecencyUpdatedMask | kHasAltDataUpdatedMask |
+ kOnStartTimeUpdatedMask | kOnStopTimeUpdatedMask |
+ kContentTypeUpdatedMask | kFileSizeUpdatedMask;
+ CacheIndexEntry::InitNew();
+ }
+
+ void SetFrecency(uint32_t aFrecency) {
+ mUpdateFlags |= kFrecencyUpdatedMask;
+ CacheIndexEntry::SetFrecency(aFrecency);
+ }
+
+ void SetHasAltData(bool aHasAltData) {
+ mUpdateFlags |= kHasAltDataUpdatedMask;
+ CacheIndexEntry::SetHasAltData(aHasAltData);
+ }
+
+ void SetOnStartTime(uint16_t aTime) {
+ mUpdateFlags |= kOnStartTimeUpdatedMask;
+ CacheIndexEntry::SetOnStartTime(aTime);
+ }
+
+ void SetOnStopTime(uint16_t aTime) {
+ mUpdateFlags |= kOnStopTimeUpdatedMask;
+ CacheIndexEntry::SetOnStopTime(aTime);
+ }
+
+ void SetContentType(uint8_t aType) {
+ mUpdateFlags |= kContentTypeUpdatedMask;
+ CacheIndexEntry::SetContentType(aType);
+ }
+
+ void SetFileSize(uint32_t aFileSize) {
+ mUpdateFlags |= kFileSizeUpdatedMask;
+ CacheIndexEntry::SetFileSize(aFileSize);
+ }
+
+ void ApplyUpdate(CacheIndexEntry* aDst) {
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, &aDst->mRec->Get()->mHash,
+ sizeof(SHA1Sum::Hash)) == 0);
+ if (mUpdateFlags & kFrecencyUpdatedMask) {
+ aDst->mRec->Get()->mFrecency = mRec->Get()->mFrecency;
+ }
+ aDst->mRec->Get()->mOriginAttrsHash = mRec->Get()->mOriginAttrsHash;
+ if (mUpdateFlags & kOnStartTimeUpdatedMask) {
+ aDst->mRec->Get()->mOnStartTime = mRec->Get()->mOnStartTime;
+ }
+ if (mUpdateFlags & kOnStopTimeUpdatedMask) {
+ aDst->mRec->Get()->mOnStopTime = mRec->Get()->mOnStopTime;
+ }
+ if (mUpdateFlags & kContentTypeUpdatedMask) {
+ aDst->mRec->Get()->mContentType = mRec->Get()->mContentType;
+ }
+ if (mUpdateFlags & kHasAltDataUpdatedMask &&
+ ((aDst->mRec->Get()->mFlags ^ mRec->Get()->mFlags) & kHasAltDataMask)) {
+ // Toggle the bit if we need to.
+ aDst->mRec->Get()->mFlags ^= kHasAltDataMask;
+ }
+
+ if (mUpdateFlags & kFileSizeUpdatedMask) {
+ // Copy all flags except |HasAltData|.
+ aDst->mRec->Get()->mFlags |= (mRec->Get()->mFlags & ~kHasAltDataMask);
+ } else {
+ // Copy all flags except |HasAltData| and file size.
+ aDst->mRec->Get()->mFlags &= kFileSizeMask;
+ aDst->mRec->Get()->mFlags |=
+ (mRec->Get()->mFlags & ~kHasAltDataMask & ~kFileSizeMask);
+ }
+ }
+
+ private:
+ static const uint32_t kFrecencyUpdatedMask = 0x00000001;
+ static const uint32_t kContentTypeUpdatedMask = 0x00000002;
+ static const uint32_t kFileSizeUpdatedMask = 0x00000004;
+ static const uint32_t kHasAltDataUpdatedMask = 0x00000008;
+ static const uint32_t kOnStartTimeUpdatedMask = 0x00000010;
+ static const uint32_t kOnStopTimeUpdatedMask = 0x00000020;
+
+ uint32_t mUpdateFlags;
+};
+
+class CacheIndexStats {
+ public:
+ CacheIndexStats() {
+ for (uint32_t i = 0; i < nsICacheEntry::CONTENT_TYPE_LAST; ++i) {
+ mCountByType[i] = 0;
+ mSizeByType[i] = 0;
+ }
+ }
+
+ bool operator==(const CacheIndexStats& aOther) const {
+ for (uint32_t i = 0; i < nsICacheEntry::CONTENT_TYPE_LAST; ++i) {
+ if (mCountByType[i] != aOther.mCountByType[i] ||
+ mSizeByType[i] != aOther.mSizeByType[i]) {
+ return false;
+ }
+ }
+
+ return
+#ifdef DEBUG
+ aOther.mStateLogged == mStateLogged &&
+#endif
+ aOther.mCount == mCount && aOther.mNotInitialized == mNotInitialized &&
+ aOther.mRemoved == mRemoved && aOther.mDirty == mDirty &&
+ aOther.mFresh == mFresh && aOther.mEmpty == mEmpty &&
+ aOther.mSize == mSize;
+ }
+
+#ifdef DEBUG
+ void DisableLogging() { mDisableLogging = true; }
+#endif
+
+ void Log() {
+ LOG(
+ ("CacheIndexStats::Log() [count=%u, notInitialized=%u, removed=%u, "
+ "dirty=%u, fresh=%u, empty=%u, size=%u]",
+ mCount, mNotInitialized, mRemoved, mDirty, mFresh, mEmpty, mSize));
+ }
+
+ void Clear() {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Clear() - state logged!");
+
+ mCount = 0;
+ mNotInitialized = 0;
+ mRemoved = 0;
+ mDirty = 0;
+ mFresh = 0;
+ mEmpty = 0;
+ mSize = 0;
+ for (uint32_t i = 0; i < nsICacheEntry::CONTENT_TYPE_LAST; ++i) {
+ mCountByType[i] = 0;
+ mSizeByType[i] = 0;
+ }
+ }
+
+#ifdef DEBUG
+ bool StateLogged() { return mStateLogged; }
+#endif
+
+ uint32_t Count() {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Count() - state logged!");
+ return mCount;
+ }
+
+ uint32_t CountByType(uint8_t aContentType) {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::CountByType() - state logged!");
+ MOZ_RELEASE_ASSERT(aContentType < nsICacheEntry::CONTENT_TYPE_LAST);
+ return mCountByType[aContentType];
+ }
+
+ uint32_t Dirty() {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Dirty() - state logged!");
+ return mDirty;
+ }
+
+ uint32_t Fresh() {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Fresh() - state logged!");
+ return mFresh;
+ }
+
+ uint32_t ActiveEntriesCount() {
+ MOZ_ASSERT(!mStateLogged,
+ "CacheIndexStats::ActiveEntriesCount() - state "
+ "logged!");
+ return mCount - mRemoved - mNotInitialized - mEmpty;
+ }
+
+ uint32_t Size() {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Size() - state logged!");
+ return mSize;
+ }
+
+ uint32_t SizeByType(uint8_t aContentType) {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::SizeByType() - state logged!");
+ MOZ_RELEASE_ASSERT(aContentType < nsICacheEntry::CONTENT_TYPE_LAST);
+ return mSizeByType[aContentType];
+ }
+
+ void BeforeChange(const CacheIndexEntry* aEntry) {
+#ifdef DEBUG_STATS
+ if (!mDisableLogging) {
+ LOG(("CacheIndexStats::BeforeChange()"));
+ Log();
+ }
+#endif
+
+ MOZ_ASSERT(!mStateLogged,
+ "CacheIndexStats::BeforeChange() - state "
+ "logged!");
+#ifdef DEBUG
+ mStateLogged = true;
+#endif
+ if (aEntry) {
+ MOZ_ASSERT(mCount);
+ uint8_t contentType = aEntry->GetContentType();
+ mCount--;
+ mCountByType[contentType]--;
+ if (aEntry->IsDirty()) {
+ MOZ_ASSERT(mDirty);
+ mDirty--;
+ }
+ if (aEntry->IsFresh()) {
+ MOZ_ASSERT(mFresh);
+ mFresh--;
+ }
+ if (aEntry->IsRemoved()) {
+ MOZ_ASSERT(mRemoved);
+ mRemoved--;
+ } else {
+ if (!aEntry->IsInitialized()) {
+ MOZ_ASSERT(mNotInitialized);
+ mNotInitialized--;
+ } else {
+ if (aEntry->IsFileEmpty()) {
+ MOZ_ASSERT(mEmpty);
+ mEmpty--;
+ } else {
+ MOZ_ASSERT(mSize >= aEntry->GetFileSize());
+ mSize -= aEntry->GetFileSize();
+ mSizeByType[contentType] -= aEntry->GetFileSize();
+ }
+ }
+ }
+ }
+ }
+
+ void AfterChange(const CacheIndexEntry* aEntry) {
+ MOZ_ASSERT(mStateLogged,
+ "CacheIndexStats::AfterChange() - state not "
+ "logged!");
+#ifdef DEBUG
+ mStateLogged = false;
+#endif
+ if (aEntry) {
+ uint8_t contentType = aEntry->GetContentType();
+ ++mCount;
+ ++mCountByType[contentType];
+ if (aEntry->IsDirty()) {
+ mDirty++;
+ }
+ if (aEntry->IsFresh()) {
+ mFresh++;
+ }
+ if (aEntry->IsRemoved()) {
+ mRemoved++;
+ } else {
+ if (!aEntry->IsInitialized()) {
+ mNotInitialized++;
+ } else {
+ if (aEntry->IsFileEmpty()) {
+ mEmpty++;
+ } else {
+ mSize += aEntry->GetFileSize();
+ mSizeByType[contentType] += aEntry->GetFileSize();
+ }
+ }
+ }
+ }
+
+#ifdef DEBUG_STATS
+ if (!mDisableLogging) {
+ LOG(("CacheIndexStats::AfterChange()"));
+ Log();
+ }
+#endif
+ }
+
+ private:
+ uint32_t mCount{0};
+ uint32_t mCountByType[nsICacheEntry::CONTENT_TYPE_LAST]{0};
+ uint32_t mNotInitialized{0};
+ uint32_t mRemoved{0};
+ uint32_t mDirty{0};
+ uint32_t mFresh{0};
+ uint32_t mEmpty{0};
+ uint32_t mSize{0};
+ uint32_t mSizeByType[nsICacheEntry::CONTENT_TYPE_LAST]{0};
+#ifdef DEBUG
+ // We completely remove the data about an entry from the stats in
+ // BeforeChange() and set this flag to true. The entry is then modified,
+ // deleted or created and the data is again put into the stats and this flag
+ // set to false. Statistics must not be read during this time since the
+ // information is not correct.
+ bool mStateLogged{false};
+
+ // Disables logging in this instance of CacheIndexStats
+ bool mDisableLogging{false};
+#endif
+};
+
+class CacheIndex final : public CacheFileIOListener, public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ CacheIndex();
+
+ static nsresult Init(nsIFile* aCacheDirectory);
+ static nsresult PreShutdown();
+ static nsresult Shutdown();
+
+ // Following methods can be called only on IO thread.
+
+ // Add entry to the index. The entry shouldn't be present in index. This
+ // method is called whenever a new handle for a new entry file is created. The
+ // newly created entry is not initialized and it must be either initialized
+ // with InitEntry() or removed with RemoveEntry().
+ static nsresult AddEntry(const SHA1Sum::Hash* aHash);
+
+ // Inform index about an existing entry that should be present in index. This
+ // method is called whenever a new handle for an existing entry file is
+ // created. Like in case of AddEntry(), either InitEntry() or RemoveEntry()
+ // must be called on the entry, since the entry is not initizlized if the
+ // index is outdated.
+ static nsresult EnsureEntryExists(const SHA1Sum::Hash* aHash);
+
+ // Initialize the entry. It MUST be present in index. Call to AddEntry() or
+ // EnsureEntryExists() must precede the call to this method.
+ static nsresult InitEntry(const SHA1Sum::Hash* aHash,
+ OriginAttrsHash aOriginAttrsHash, bool aAnonymous,
+ bool aPinned);
+
+ // Remove entry from index. The entry should be present in index.
+ static nsresult RemoveEntry(const SHA1Sum::Hash* aHash);
+
+ // Update some information in entry. The entry MUST be present in index and
+ // MUST be initialized. Call to AddEntry() or EnsureEntryExists() and to
+ // InitEntry() must precede the call to this method.
+ // Pass nullptr if the value didn't change.
+ static nsresult UpdateEntry(const SHA1Sum::Hash* aHash,
+ const uint32_t* aFrecency,
+ const bool* aHasAltData,
+ const uint16_t* aOnStartTime,
+ const uint16_t* aOnStopTime,
+ const uint8_t* aContentType,
+ const uint32_t* aSize);
+
+ // Remove all entries from the index. Called when clearing the whole cache.
+ static nsresult RemoveAll();
+
+ enum EntryStatus { EXISTS = 0, DOES_NOT_EXIST = 1, DO_NOT_KNOW = 2 };
+
+ // Returns status of the entry in index for the given key. It can be called
+ // on any thread.
+ // If the optional aCB callback is given, the it will be called with a
+ // CacheIndexEntry only if _retval is EXISTS when the method returns.
+ static nsresult HasEntry(
+ const nsACString& aKey, EntryStatus* _retval,
+ const std::function<void(const CacheIndexEntry*)>& aCB = nullptr);
+ static nsresult HasEntry(
+ const SHA1Sum::Hash& hash, EntryStatus* _retval,
+ const std::function<void(const CacheIndexEntry*)>& aCB = nullptr);
+
+ // Returns a hash of the least important entry that should be evicted if the
+ // cache size is over limit and also returns a total number of all entries in
+ // the index minus the number of forced valid entries and unpinned entries
+ // that we encounter when searching (see below)
+ static nsresult GetEntryForEviction(bool aIgnoreEmptyEntries,
+ SHA1Sum::Hash* aHash, uint32_t* aCnt);
+
+ // Checks if a cache entry is currently forced valid. Used to prevent an entry
+ // (that has been forced valid) from being evicted when the cache size reaches
+ // its limit.
+ static bool IsForcedValidEntry(const SHA1Sum::Hash* aHash);
+
+ // Returns cache size in kB.
+ static nsresult GetCacheSize(uint32_t* _retval);
+
+ // Returns number of entry files in the cache
+ static nsresult GetEntryFileCount(uint32_t* _retval);
+
+ // Synchronously returns the disk occupation and number of entries
+ // per-context. Callable on any thread. It will ignore loadContextInfo and get
+ // stats for all entries if the aInfo is a nullptr.
+ static nsresult GetCacheStats(nsILoadContextInfo* aInfo, uint32_t* aSize,
+ uint32_t* aCount);
+
+ // Asynchronously gets the disk cache size, used for display in the UI.
+ static nsresult AsyncGetDiskConsumption(
+ nsICacheStorageConsumptionObserver* aObserver);
+
+ // Returns an iterator that returns entries matching a given context that were
+ // present in the index at the time this method was called. If aAddNew is true
+ // then the iterator will also return entries created after this call.
+ // NOTE: When some entry is removed from index it is removed also from the
+ // iterator regardless what aAddNew was passed.
+ static nsresult GetIterator(nsILoadContextInfo* aInfo, bool aAddNew,
+ CacheIndexIterator** _retval);
+
+ // Returns true if we _think_ that the index is up to date. I.e. the state is
+ // READY or WRITING and mIndexNeedsUpdate as well as mShuttingDown is false.
+ static nsresult IsUpToDate(bool* _retval);
+
+ // Called from CacheStorageService::Clear() and
+ // CacheFileContextEvictor::EvictEntries(), sets a flag that blocks
+ // notification to AsyncGetDiskConsumption.
+ static void OnAsyncEviction(bool aEvicting);
+
+ // We keep track of total bytes written to the cache to be able to do
+ // a telemetry report after writting certain amount of data to the cache.
+ static void UpdateTotalBytesWritten(uint32_t aBytesWritten);
+
+ // Memory reporting
+ static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
+ static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+ private:
+ friend class CacheIndexEntryAutoManage;
+ friend class FileOpenHelper;
+ friend class CacheIndexIterator;
+ friend class CacheIndexRecordWrapper;
+ friend class DeleteCacheIndexRecordWrapper;
+
+ virtual ~CacheIndex();
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override;
+ void OnFileOpenedInternal(FileOpenHelper* aOpener, CacheFileHandle* aHandle,
+ nsresult aResult,
+ const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) override;
+ NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) override;
+ NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override;
+ NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) override;
+
+ nsresult InitInternal(nsIFile* aCacheDirectory,
+ const StaticMutexAutoLock& aProofOfLock);
+ void PreShutdownInternal();
+
+ // This method returns false when index is not initialized or is shut down.
+ bool IsIndexUsable() MOZ_REQUIRES(sLock);
+
+ // This method checks whether the entry has the same values of
+ // originAttributes and isAnonymous. We don't expect to find a collision
+ // since these values are part of the key that we hash and we use a strong
+ // hash function.
+ static bool IsCollision(CacheIndexEntry* aEntry,
+ OriginAttrsHash aOriginAttrsHash, bool aAnonymous);
+
+ // Checks whether any of the information about the entry has changed.
+ static bool HasEntryChanged(CacheIndexEntry* aEntry,
+ const uint32_t* aFrecency,
+ const bool* aHasAltData,
+ const uint16_t* aOnStartTime,
+ const uint16_t* aOnStopTime,
+ const uint8_t* aContentType,
+ const uint32_t* aSize);
+
+ // Merge all pending operations from mPendingUpdates into mIndex.
+ void ProcessPendingOperations(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ // Following methods perform writing of the index file.
+ //
+ // The index is written periodically, but not earlier than once in
+ // kMinDumpInterval and there must be at least kMinUnwrittenChanges
+ // differences between index on disk and in memory. Index is always first
+ // written to a temporary file and the old index file is replaced when the
+ // writing process succeeds.
+ //
+ // Starts writing of index when both limits (minimal delay between writes and
+ // minimum number of changes in index) were exceeded.
+ bool WriteIndexToDiskIfNeeded(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Starts writing of index file.
+ void WriteIndexToDisk(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Serializes part of mIndex hashtable to the write buffer a writes the buffer
+ // to the file.
+ void WriteRecords(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Finalizes writing process.
+ void FinishWrite(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ // Following methods perform writing of the journal during shutdown. All these
+ // methods must be called only during shutdown since they write/delete files
+ // directly on the main thread instead of using CacheFileIOManager that does
+ // it asynchronously on IO thread. Journal contains only entries that are
+ // dirty, i.e. changes that are not present in the index file on the disk.
+ // When the log is written successfully, the dirty flag in index file is
+ // cleared.
+ nsresult GetFile(const nsACString& aName, nsIFile** _retval);
+ void RemoveFile(const nsACString& aName) MOZ_REQUIRES(sLock);
+ void RemoveAllIndexFiles() MOZ_REQUIRES(sLock);
+ void RemoveJournalAndTempFile() MOZ_REQUIRES(sLock);
+ // Writes journal to the disk and clears dirty flag in index header.
+ nsresult WriteLogToDisk() MOZ_REQUIRES(sLock);
+
+ // Following methods perform reading of the index from the disk.
+ //
+ // Index is read at startup just after initializing the CacheIndex. There are
+ // 3 files used when manipulating with index: index file, journal file and
+ // a temporary file. All files contain the hash of the data, so we can check
+ // whether the content is valid and complete. Index file contains also a dirty
+ // flag in the index header which is unset on a clean shutdown. During opening
+ // and reading of the files we determine the status of the whole index from
+ // the states of the separate files. Following table shows all possible
+ // combinations:
+ //
+ // index, journal, tmpfile
+ // M * * - index is missing -> BUILD
+ // I * * - index is invalid -> BUILD
+ // D * * - index is dirty -> UPDATE
+ // C M * - index is dirty -> UPDATE
+ // C I * - unexpected state -> UPDATE
+ // C V E - unexpected state -> UPDATE
+ // C V M - index is up to date -> READY
+ //
+ // where the letters mean:
+ // * - any state
+ // E - file exists
+ // M - file is missing
+ // I - data is invalid (parsing failed or hash didn't match)
+ // D - dirty (data in index file is correct, but dirty flag is set)
+ // C - clean (index file is clean)
+ // V - valid (data in journal file is correct)
+ //
+ // Note: We accept the data from journal only when the index is up to date as
+ // a whole (i.e. C,V,M state).
+ //
+ // We rename the journal file to the temporary file as soon as possible after
+ // initial test to ensure that we start update process on the next startup if
+ // FF crashes during parsing of the index.
+ //
+ // Initiates reading index from disk.
+ void ReadIndexFromDisk(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Starts reading data from index file.
+ void StartReadingIndex(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Parses data read from index file.
+ void ParseRecords(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Starts reading data from journal file.
+ void StartReadingJournal(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Parses data read from journal file.
+ void ParseJournal(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Merges entries from journal into mIndex.
+ void MergeJournal(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // In debug build this method checks that we have no fresh entry in mIndex
+ // after we finish reading index and before we process pending operations.
+ void EnsureNoFreshEntry() MOZ_REQUIRES(sLock);
+ // In debug build this method is called after processing pending operations
+ // to make sure mIndexStats contains correct information.
+ void EnsureCorrectStats() MOZ_REQUIRES(sLock);
+
+ // Finalizes reading process.
+ void FinishRead(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ // Following methods perform updating and building of the index.
+ // Timer callback that starts update or build process.
+ static void DelayedUpdate(nsITimer* aTimer, void* aClosure);
+ void DelayedUpdateLocked(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Posts timer event that start update or build process.
+ nsresult ScheduleUpdateTimer(uint32_t aDelay) MOZ_REQUIRES(sLock);
+ nsresult SetupDirectoryEnumerator() MOZ_REQUIRES(sLock);
+ nsresult InitEntryFromDiskData(CacheIndexEntry* aEntry,
+ CacheFileMetadata* aMetaData,
+ int64_t aFileSize);
+ // Returns true when either a timer is scheduled or event is posted.
+ bool IsUpdatePending() MOZ_REQUIRES(sLock);
+ // Iterates through all files in entries directory that we didn't create/open
+ // during this session, parses them and adds the entries to the index.
+ void BuildIndex(const StaticMutexAutoLock& aProofOfLock) MOZ_REQUIRES(sLock);
+
+ bool StartUpdatingIndexIfNeeded(const StaticMutexAutoLock& aProofOfLock,
+ bool aSwitchingToReadyState = false);
+ // Starts update or build process or fires a timer when it is too early after
+ // startup.
+ void StartUpdatingIndex(bool aRebuild,
+ const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ // Iterates through all files in entries directory that we didn't create/open
+ // during this session and theirs last modified time is newer than timestamp
+ // in the index header. Parses the files and adds the entries to the index.
+ void UpdateIndex(const StaticMutexAutoLock& aProofOfLock) MOZ_REQUIRES(sLock);
+ // Finalizes update or build process.
+ void FinishUpdate(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ void RemoveNonFreshEntries(const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ enum EState {
+ // Initial state in which the index is not usable
+ // Possible transitions:
+ // -> READING
+ INITIAL = 0,
+
+ // Index is being read from the disk.
+ // Possible transitions:
+ // -> INITIAL - We failed to dispatch a read event.
+ // -> BUILDING - No or corrupted index file was found.
+ // -> UPDATING - No or corrupted journal file was found.
+ // - Dirty flag was set in index header.
+ // -> READY - Index was read successfully or was interrupted by
+ // pre-shutdown.
+ // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
+ READING = 1,
+
+ // Index is being written to the disk.
+ // Possible transitions:
+ // -> READY - Writing of index finished or was interrupted by
+ // pre-shutdown..
+ // -> UPDATING - Writing of index finished, but index was found outdated
+ // during writing.
+ // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
+ WRITING = 2,
+
+ // Index is being build.
+ // Possible transitions:
+ // -> READY - Building of index finished or was interrupted by
+ // pre-shutdown.
+ // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
+ BUILDING = 3,
+
+ // Index is being updated.
+ // Possible transitions:
+ // -> READY - Updating of index finished or was interrupted by
+ // pre-shutdown.
+ // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
+ UPDATING = 4,
+
+ // Index is ready.
+ // Possible transitions:
+ // -> UPDATING - Index was found outdated.
+ // -> SHUTDOWN - Index is shutting down.
+ READY = 5,
+
+ // Index is shutting down.
+ SHUTDOWN = 6
+ };
+
+ static char const* StateString(EState aState);
+ void ChangeState(EState aNewState, const StaticMutexAutoLock& aProofOfLock);
+ void NotifyAsyncGetDiskConsumptionCallbacks() MOZ_REQUIRES(sLock);
+
+ // Allocates and releases buffer used for reading and writing index.
+ void AllocBuffer() MOZ_REQUIRES(sLock);
+ void ReleaseBuffer() MOZ_REQUIRES(sLock);
+
+ // Methods used by CacheIndexEntryAutoManage to keep the iterators up to date.
+ void AddRecordToIterators(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ void RemoveRecordFromIterators(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+ void ReplaceRecordInIterators(CacheIndexRecordWrapper* aOldRecord,
+ CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(sLock);
+
+ // Memory reporting (private part)
+ size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const
+ MOZ_REQUIRES(sLock);
+
+ // Reports telemetry about cache, i.e. size, entry count and content type
+ // stats.
+ void DoTelemetryReport() MOZ_REQUIRES(sLock);
+
+ static mozilla::StaticRefPtr<CacheIndex> gInstance MOZ_GUARDED_BY(sLock);
+
+ // sLock guards almost everything here...
+ // Also guards FileOpenHelper::mCanceled
+ static StaticMutex sLock;
+
+ nsCOMPtr<nsIFile> mCacheDirectory;
+
+ EState mState MOZ_GUARDED_BY(sLock){INITIAL};
+ // Timestamp of time when the index was initialized. We use it to delay
+ // initial update or build of index.
+ TimeStamp mStartTime MOZ_GUARDED_BY(sLock);
+ // Set to true in PreShutdown(), it is checked on variaous places to prevent
+ // starting any process (write, update, etc.) during shutdown.
+ bool mShuttingDown MOZ_GUARDED_BY(sLock){false};
+ // When set to true, update process should start as soon as possible. This
+ // flag is set whenever we find some inconsistency which would be fixed by
+ // update process. The flag is checked always when switching to READY state.
+ // To make sure we start the update process as soon as possible, methods that
+ // set this flag should also call StartUpdatingIndexIfNeeded() to cover the
+ // case when we are currently in READY state.
+ bool mIndexNeedsUpdate MOZ_GUARDED_BY(sLock){false};
+ // Set at the beginning of RemoveAll() which clears the whole index. When
+ // removing all entries we must stop any pending reading, writing, updating or
+ // building operation. This flag is checked at various places and it prevents
+ // we won't start another operation (e.g. canceling reading of the index would
+ // normally start update or build process)
+ bool mRemovingAll MOZ_GUARDED_BY(sLock){false};
+ // Whether the index file on disk exists and is valid.
+ bool mIndexOnDiskIsValid MOZ_GUARDED_BY(sLock){false};
+ // When something goes wrong during updating or building process, we don't
+ // mark index clean (and also don't write journal) to ensure that update or
+ // build will be initiated on the next start.
+ bool mDontMarkIndexClean MOZ_GUARDED_BY(sLock){false};
+ // Timestamp value from index file. It is used during update process to skip
+ // entries that were last modified before this timestamp.
+ uint32_t mIndexTimeStamp MOZ_GUARDED_BY(sLock){0};
+ // Timestamp of last time the index was dumped to disk.
+ // NOTE: The index might not be necessarily dumped at this time. The value
+ // is used to schedule next dump of the index.
+ TimeStamp mLastDumpTime MOZ_GUARDED_BY(sLock);
+
+ // Timer of delayed update/build.
+ nsCOMPtr<nsITimer> mUpdateTimer MOZ_GUARDED_BY(sLock);
+ // True when build or update event is posted
+ bool mUpdateEventPending MOZ_GUARDED_BY(sLock){false};
+
+ // Helper members used when reading/writing index from/to disk.
+ // Contains number of entries that should be skipped:
+ // - in hashtable when writing index because they were already written
+ // - in index file when reading index because they were already read
+ uint32_t mSkipEntries MOZ_GUARDED_BY(sLock){0};
+ // Number of entries that should be written to disk. This is number of entries
+ // in hashtable that are initialized and are not marked as removed when
+ // writing begins.
+ uint32_t mProcessEntries MOZ_GUARDED_BY(sLock){0};
+ char* mRWBuf MOZ_GUARDED_BY(sLock){nullptr};
+ uint32_t mRWBufSize MOZ_GUARDED_BY(sLock){0};
+ uint32_t mRWBufPos MOZ_GUARDED_BY(sLock){0};
+ RefPtr<CacheHash> mRWHash MOZ_GUARDED_BY(sLock);
+
+ // True if read or write operation is pending. It is used to ensure that
+ // mRWBuf is not freed until OnDataRead or OnDataWritten is called.
+ bool mRWPending MOZ_GUARDED_BY(sLock){false};
+
+ // Reading of journal succeeded if true.
+ bool mJournalReadSuccessfully MOZ_GUARDED_BY(sLock){false};
+
+ // Handle used for writing and reading index file.
+ RefPtr<CacheFileHandle> mIndexHandle MOZ_GUARDED_BY(sLock);
+ // Handle used for reading journal file.
+ RefPtr<CacheFileHandle> mJournalHandle MOZ_GUARDED_BY(sLock);
+ // Used to check the existence of the file during reading process.
+ RefPtr<CacheFileHandle> mTmpHandle MOZ_GUARDED_BY(sLock);
+
+ RefPtr<FileOpenHelper> mIndexFileOpener MOZ_GUARDED_BY(sLock);
+ RefPtr<FileOpenHelper> mJournalFileOpener MOZ_GUARDED_BY(sLock);
+ RefPtr<FileOpenHelper> mTmpFileOpener MOZ_GUARDED_BY(sLock);
+
+ // Directory enumerator used when building and updating index.
+ nsCOMPtr<nsIDirectoryEnumerator> mDirEnumerator MOZ_GUARDED_BY(sLock);
+
+ // Main index hashtable.
+ nsTHashtable<CacheIndexEntry> mIndex MOZ_GUARDED_BY(sLock);
+
+ // We cannot add, remove or change any entry in mIndex in states READING and
+ // WRITING. We track all changes in mPendingUpdates during these states.
+ nsTHashtable<CacheIndexEntryUpdate> mPendingUpdates MOZ_GUARDED_BY(sLock);
+
+ // Contains information statistics for mIndex + mPendingUpdates.
+ CacheIndexStats mIndexStats MOZ_GUARDED_BY(sLock);
+
+ // When reading journal, we must first parse the whole file and apply the
+ // changes iff the journal was read successfully. mTmpJournal is used to store
+ // entries from the journal file. We throw away all these entries if parsing
+ // of the journal fails or the hash does not match.
+ nsTHashtable<CacheIndexEntry> mTmpJournal MOZ_GUARDED_BY(sLock);
+
+ // FrecencyArray maintains order of entry records for eviction. Ideally, the
+ // records would be ordered by frecency all the time, but since this would be
+ // quite expensive, we allow certain amount of entries to be out of order.
+ // When the frecency is updated the new value is always bigger than the old
+ // one. Instead of keeping updated entries at the same position, we move them
+ // at the end of the array. This protects recently updated entries from
+ // eviction. The array is sorted once we hit the limit of maximum unsorted
+ // entries.
+ class FrecencyArray {
+ class Iterator {
+ public:
+ explicit Iterator(nsTArray<RefPtr<CacheIndexRecordWrapper>>* aRecs)
+ : mRecs(aRecs), mIdx(0) {
+ while (!Done() && !(*mRecs)[mIdx]) {
+ mIdx++;
+ }
+ }
+
+ bool Done() const { return mIdx == mRecs->Length(); }
+
+ CacheIndexRecordWrapper* Get() const {
+ MOZ_ASSERT(!Done());
+ return (*mRecs)[mIdx];
+ }
+
+ void Next() {
+ MOZ_ASSERT(!Done());
+ ++mIdx;
+ while (!Done() && !(*mRecs)[mIdx]) {
+ mIdx++;
+ }
+ }
+
+ private:
+ nsTArray<RefPtr<CacheIndexRecordWrapper>>* mRecs;
+ uint32_t mIdx;
+ };
+
+ public:
+ Iterator Iter() { return Iterator(&mRecs); }
+
+ FrecencyArray() = default;
+
+ // Methods used by CacheIndexEntryAutoManage to keep the array up to date.
+ void AppendRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void RemoveRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void ReplaceRecord(CacheIndexRecordWrapper* aOldRecord,
+ CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void SortIfNeeded(const StaticMutexAutoLock& aProofOfLock);
+ bool RecordExistedUnlocked(CacheIndexRecordWrapper* aRecord);
+
+ size_t Length() const { return mRecs.Length() - mRemovedElements; }
+ void Clear(const StaticMutexAutoLock& aProofOfLock) { mRecs.Clear(); }
+
+ private:
+ friend class CacheIndex;
+
+ nsTArray<RefPtr<CacheIndexRecordWrapper>> mRecs;
+ uint32_t mUnsortedElements{0};
+ // Instead of removing elements from the array immediately, we null them out
+ // and the iterator skips them when accessing the array. The null pointers
+ // are placed at the end during sorting and we strip them out all at once.
+ // This saves moving a lot of memory in nsTArray::RemoveElementsAt.
+ uint32_t mRemovedElements{0};
+ };
+
+ FrecencyArray mFrecencyArray MOZ_GUARDED_BY(sLock);
+
+ nsTArray<CacheIndexIterator*> mIterators MOZ_GUARDED_BY(sLock);
+
+ // This flag is true iff we are between CacheStorageService:Clear() and
+ // processing all contexts to be evicted. It will make UI to show
+ // "calculating" instead of any intermediate cache size.
+ bool mAsyncGetDiskConsumptionBlocked MOZ_GUARDED_BY(sLock){false};
+
+ class DiskConsumptionObserver : public Runnable {
+ public:
+ static DiskConsumptionObserver* Init(
+ nsICacheStorageConsumptionObserver* aObserver) {
+ nsWeakPtr observer = do_GetWeakReference(aObserver);
+ if (!observer) return nullptr;
+
+ return new DiskConsumptionObserver(observer);
+ }
+
+ void OnDiskConsumption(int64_t aSize) {
+ mSize = aSize;
+ NS_DispatchToMainThread(this);
+ }
+
+ private:
+ explicit DiskConsumptionObserver(nsWeakPtr const& aWeakObserver)
+ : Runnable("net::CacheIndex::DiskConsumptionObserver"),
+ mObserver(aWeakObserver),
+ mSize(0) {}
+ virtual ~DiskConsumptionObserver() {
+ if (mObserver && !NS_IsMainThread()) {
+ NS_ReleaseOnMainThread("DiskConsumptionObserver::mObserver",
+ mObserver.forget());
+ }
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsICacheStorageConsumptionObserver> observer =
+ do_QueryReferent(mObserver);
+
+ mObserver = nullptr;
+
+ if (observer) {
+ observer->OnNetworkCacheDiskConsumption(mSize);
+ }
+
+ return NS_OK;
+ }
+
+ nsWeakPtr mObserver;
+ int64_t mSize;
+ };
+
+ // List of async observers that want to get disk consumption information
+ nsTArray<RefPtr<DiskConsumptionObserver>> mDiskConsumptionObservers
+ MOZ_GUARDED_BY(sLock);
+
+ // Number of bytes written to the cache since the last telemetry report
+ uint64_t mTotalBytesWritten MOZ_GUARDED_BY(sLock){0};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif