summaryrefslogtreecommitdiffstats
path: root/netwerk/cache2/CacheFileIOManager.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /netwerk/cache2/CacheFileIOManager.h
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--netwerk/cache2/CacheFileIOManager.h490
1 files changed, 490 insertions, 0 deletions
diff --git a/netwerk/cache2/CacheFileIOManager.h b/netwerk/cache2/CacheFileIOManager.h
new file mode 100644
index 0000000000..62533fb668
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -0,0 +1,490 @@
+/* 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 CacheFileIOManager__h__
+#define CacheFileIOManager__h__
+
+#include "CacheIOThread.h"
+#include "CacheStorageService.h"
+#include "CacheHashUtils.h"
+#include "nsIEventTarget.h"
+#include "nsINamed.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsTHashtable.h"
+#include "prio.h"
+
+// #define DEBUG_HANDLES 1
+
+class nsIFile;
+class nsITimer;
+class nsIDirectoryEnumerator;
+class nsILoadContextInfo;
+
+namespace mozilla {
+namespace net {
+
+class CacheFile;
+class CacheFileIOListener;
+
+#ifdef DEBUG_HANDLES
+class CacheFileHandlesEntry;
+#endif
+
+#define ENTRIES_DIR "entries"
+#define DOOMED_DIR "doomed"
+#define TRASH_DIR "trash"
+
+class CacheFileHandle final : public nsISupports {
+ public:
+ enum class PinningStatus : uint32_t { UNKNOWN, NON_PINNED, PINNED };
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ bool DispatchRelease();
+
+ CacheFileHandle(const SHA1Sum::Hash* aHash, bool aPriority,
+ PinningStatus aPinning);
+ CacheFileHandle(const nsACString& aKey, bool aPriority,
+ PinningStatus aPinning);
+ void Log();
+ bool IsDoomed() const { return mIsDoomed; }
+ const SHA1Sum::Hash* Hash() const { return mHash; }
+ int64_t FileSize() const { return mFileSize; }
+ uint32_t FileSizeInK() const;
+ bool IsPriority() const { return mPriority; }
+ bool FileExists() const { return mFileExists; }
+ bool IsClosed() const { return mClosed; }
+ bool IsSpecialFile() const { return mSpecialFile; }
+ nsCString& Key() { return mKey; }
+
+ // Returns false when this handle has been doomed based on the pinning state
+ // update.
+ bool SetPinned(bool aPinned);
+ void SetInvalid() { mInvalid = true; }
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ private:
+ friend class CacheFileIOManager;
+ friend class CacheFileHandles;
+ friend class ReleaseNSPRHandleEvent;
+
+ virtual ~CacheFileHandle();
+
+ const SHA1Sum::Hash* mHash;
+ mozilla::Atomic<bool, ReleaseAcquire> mIsDoomed;
+ mozilla::Atomic<bool, ReleaseAcquire> mClosed;
+
+ // mPriority and mSpecialFile are plain "bool", not "bool:1", so as to
+ // avoid bitfield races with the byte containing mInvalid et al. See
+ // bug 1278502.
+ bool const mPriority;
+ bool const mSpecialFile;
+
+ mozilla::Atomic<bool, Relaxed> mInvalid;
+
+ // These bit flags are all accessed only on the IO thread
+ bool mFileExists : 1; // This means that the file should exists,
+ // but it can be still deleted by OS/user
+ // and then a subsequent OpenNSPRFileDesc()
+ // will fail.
+
+ // Both initially false. Can be raised to true only when this handle is to be
+ // doomed during the period when the pinning status is unknown. After the
+ // pinning status determination we check these flags and possibly doom. These
+ // flags are only accessed on the IO thread.
+ bool mDoomWhenFoundPinned : 1;
+ bool mDoomWhenFoundNonPinned : 1;
+ // Set when after shutdown AND:
+ // - when writing: writing data (not metadata) OR the physical file handle is
+ // not currently open
+ // - when truncating: the physical file handle is not currently open
+ // When set it prevents any further writes or truncates on such handles to
+ // happen immediately after shutdown and gives a chance to write metadata of
+ // already open files quickly as possible (only that renders them actually
+ // usable by the cache.)
+ bool mKilled : 1;
+ // For existing files this is always pre-set to UNKNOWN. The status is
+ // udpated accordingly after the matadata has been parsed. For new files the
+ // flag is set according to which storage kind is opening the cache entry and
+ // remains so for the handle's lifetime. The status can only change from
+ // UNKNOWN (if set so initially) to one of PINNED or NON_PINNED and it stays
+ // unchanged afterwards. This status is only accessed on the IO thread.
+ PinningStatus mPinning;
+
+ nsCOMPtr<nsIFile> mFile;
+
+ // file size is atomic because it is used on main thread by
+ // nsHttpChannel::ReportNetVSCacheTelemetry()
+ Atomic<int64_t, Relaxed> mFileSize;
+ PRFileDesc* mFD; // if null then the file doesn't exists on the disk
+ nsCString mKey;
+};
+
+class CacheFileHandles {
+ public:
+ CacheFileHandles();
+ ~CacheFileHandles();
+
+ nsresult GetHandle(const SHA1Sum::Hash* aHash, CacheFileHandle** _retval);
+ already_AddRefed<CacheFileHandle> NewHandle(const SHA1Sum::Hash*,
+ bool aPriority,
+ CacheFileHandle::PinningStatus);
+ void RemoveHandle(CacheFileHandle* aHandle);
+ void GetAllHandles(nsTArray<RefPtr<CacheFileHandle> >* _retval);
+ void GetActiveHandles(nsTArray<RefPtr<CacheFileHandle> >* _retval);
+ void ClearAll();
+ uint32_t HandleCount();
+
+#ifdef DEBUG_HANDLES
+ void Log(CacheFileHandlesEntry* entry);
+#endif
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ class HandleHashKey : public PLDHashEntryHdr {
+ public:
+ using KeyType = const SHA1Sum::Hash&;
+ using KeyTypePointer = const SHA1Sum::Hash*;
+
+ explicit HandleHashKey(KeyTypePointer aKey) {
+ MOZ_COUNT_CTOR(HandleHashKey);
+ mHash = MakeUnique<uint8_t[]>(SHA1Sum::kHashSize);
+ memcpy(mHash.get(), aKey, sizeof(SHA1Sum::Hash));
+ }
+ HandleHashKey(const HandleHashKey& aOther) {
+ MOZ_ASSERT_UNREACHABLE("HandleHashKey copy constructor is forbidden!");
+ }
+ MOZ_COUNTED_DTOR(HandleHashKey)
+
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return memcmp(mHash.get(), aKey, sizeof(SHA1Sum::Hash)) == 0;
+ }
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return (reinterpret_cast<const uint32_t*>(aKey))[0];
+ }
+
+ void AddHandle(CacheFileHandle* aHandle);
+ void RemoveHandle(CacheFileHandle* aHandle);
+ already_AddRefed<CacheFileHandle> GetNewestHandle();
+ void GetHandles(nsTArray<RefPtr<CacheFileHandle> >& aResult);
+
+ SHA1Sum::Hash* Hash() const {
+ return reinterpret_cast<SHA1Sum::Hash*>(mHash.get());
+ }
+ bool IsEmpty() const { return mHandles.Length() == 0; }
+
+ enum { ALLOW_MEMMOVE = true };
+
+#ifdef DEBUG
+ void AssertHandlesState();
+#endif
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ private:
+ // We can't make this UniquePtr<SHA1Sum::Hash>, because you can't have
+ // UniquePtrs with known bounds. So we settle for this representation
+ // and using appropriate casts when we need to access it as a
+ // SHA1Sum::Hash.
+ UniquePtr<uint8_t[]> mHash;
+ // Use weak pointers since the hash table access is on a single thread
+ // only and CacheFileHandle removes itself from this table in its dtor
+ // that may only be called on the same thread as we work with the hashtable
+ // since we dispatch its Release() to this thread.
+ nsTArray<CacheFileHandle*> mHandles;
+ };
+
+ private:
+ nsTHashtable<HandleHashKey> mTable;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class OpenFileEvent;
+class ReadEvent;
+class WriteEvent;
+class MetadataWriteScheduleEvent;
+class CacheFileContextEvictor;
+
+#define CACHEFILEIOLISTENER_IID \
+ { /* dcaf2ddc-17cf-4242-bca1-8c86936375a5 */ \
+ 0xdcaf2ddc, 0x17cf, 0x4242, { \
+ 0xbc, 0xa1, 0x8c, 0x86, 0x93, 0x63, 0x75, 0xa5 \
+ } \
+ }
+
+class CacheFileIOListener : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEIOLISTENER_IID)
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) = 0;
+ NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) = 0;
+ NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) = 0;
+ NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) = 0;
+ NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) = 0;
+ NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) = 0;
+
+ virtual bool IsKilled() { return false; }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileIOListener, CACHEFILEIOLISTENER_IID)
+
+class CacheFileIOManager final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ enum {
+ OPEN = 0U,
+ CREATE = 1U,
+ CREATE_NEW = 2U,
+ PRIORITY = 4U,
+ SPECIAL_FILE = 8U,
+ PINNED = 16U
+ };
+
+ CacheFileIOManager();
+
+ static nsresult Init();
+ static nsresult Shutdown();
+ static nsresult OnProfile();
+ static nsresult OnDelayedStartupFinished();
+ static already_AddRefed<nsIEventTarget> IOTarget();
+ static already_AddRefed<CacheIOThread> IOThread();
+ static bool IsOnIOThread();
+ static bool IsOnIOThreadOrCeased();
+ static bool IsShutdown();
+
+ // Make aFile's WriteMetadataIfNeeded be called automatically after
+ // a short interval.
+ static nsresult ScheduleMetadataWrite(CacheFile* aFile);
+ // Remove aFile from the scheduling registry array.
+ // WriteMetadataIfNeeded will not be automatically called.
+ static nsresult UnscheduleMetadataWrite(CacheFile* aFile);
+ // Shuts the scheduling off and flushes all pending metadata writes.
+ static nsresult ShutdownMetadataWriteScheduling();
+
+ static nsresult OpenFile(const nsACString& aKey, uint32_t aFlags,
+ CacheFileIOListener* aCallback);
+ static nsresult Read(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf,
+ int32_t aCount, CacheFileIOListener* aCallback);
+ static nsresult Write(CacheFileHandle* aHandle, int64_t aOffset,
+ const char* aBuf, int32_t aCount, bool aValidate,
+ bool aTruncate, CacheFileIOListener* aCallback);
+ // PinningDoomRestriction:
+ // NO_RESTRICTION
+ // no restriction is checked, the file is simply always doomed
+ // DOOM_WHEN_(NON)_PINNED, we branch based on the pinning status of the
+ // handle:
+ // UNKNOWN: the handle is marked to be doomed when later found (non)pinned
+ // PINNED/NON_PINNED: doom only when the restriction matches the pin status
+ // and the handle has not yet been required to doom during the UNKNOWN
+ // period
+ enum PinningDoomRestriction {
+ NO_RESTRICTION,
+ DOOM_WHEN_NON_PINNED,
+ DOOM_WHEN_PINNED
+ };
+ static nsresult DoomFile(CacheFileHandle* aHandle,
+ CacheFileIOListener* aCallback);
+ static nsresult DoomFileByKey(const nsACString& aKey,
+ CacheFileIOListener* aCallback);
+ static nsresult ReleaseNSPRHandle(CacheFileHandle* aHandle);
+ static nsresult TruncateSeekSetEOF(CacheFileHandle* aHandle,
+ int64_t aTruncatePos, int64_t aEOFPos,
+ CacheFileIOListener* aCallback);
+ static nsresult RenameFile(CacheFileHandle* aHandle,
+ const nsACString& aNewName,
+ CacheFileIOListener* aCallback);
+ static nsresult EvictIfOverLimit();
+ static nsresult EvictAll();
+ static nsresult EvictByContext(nsILoadContextInfo* aLoadContextInfo,
+ bool aPinned, const nsAString& aOrigin,
+ const nsAString& aBaseDomain = u""_ns);
+
+ static nsresult InitIndexEntry(CacheFileHandle* aHandle,
+ OriginAttrsHash aOriginAttrsHash,
+ bool aAnonymous, bool aPinning);
+ static nsresult UpdateIndexEntry(CacheFileHandle* aHandle,
+ const uint32_t* aFrecency,
+ const bool* aHasAltData,
+ const uint16_t* aOnStartTime,
+ const uint16_t* aOnStopTime,
+ const uint8_t* aContentType);
+
+ static nsresult UpdateIndexEntry();
+
+ enum EEnumerateMode { ENTRIES, DOOMED };
+
+ static void GetCacheDirectory(nsIFile** result);
+#if defined(MOZ_WIDGET_ANDROID)
+ static void GetProfilelessCacheDirectory(nsIFile** result);
+#endif
+
+ // Calls synchronously OnEntryInfo for an entry with the given hash.
+ // Tries to find an existing entry in the service hashtables first, if not
+ // found, loads synchronously from disk file.
+ // Callable on the IO thread only.
+ static nsresult GetEntryInfo(
+ const SHA1Sum::Hash* aHash,
+ CacheStorageService::EntryInfoCallback* aCallback);
+
+ // Memory reporting
+ static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
+ static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+ private:
+ friend class CacheFileHandle;
+ friend class CacheFileChunk;
+ friend class CacheFile;
+ friend class ShutdownEvent;
+ friend class OpenFileEvent;
+ friend class CloseHandleEvent;
+ friend class ReadEvent;
+ friend class WriteEvent;
+ friend class DoomFileEvent;
+ friend class DoomFileByKeyEvent;
+ friend class ReleaseNSPRHandleEvent;
+ friend class TruncateSeekSetEOFEvent;
+ friend class RenameFileEvent;
+ friend class CacheIndex;
+ friend class MetadataWriteScheduleEvent;
+ friend class CacheFileContextEvictor;
+
+ virtual ~CacheFileIOManager();
+
+ nsresult InitInternal();
+ void ShutdownInternal();
+
+ nsresult OpenFileInternal(const SHA1Sum::Hash* aHash, const nsACString& aKey,
+ uint32_t aFlags, CacheFileHandle** _retval);
+ nsresult OpenSpecialFileInternal(const nsACString& aKey, uint32_t aFlags,
+ CacheFileHandle** _retval);
+ void CloseHandleInternal(CacheFileHandle* aHandle);
+ nsresult ReadInternal(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf,
+ int32_t aCount);
+ nsresult WriteInternal(CacheFileHandle* aHandle, int64_t aOffset,
+ const char* aBuf, int32_t aCount, bool aValidate,
+ bool aTruncate);
+ nsresult DoomFileInternal(
+ CacheFileHandle* aHandle,
+ PinningDoomRestriction aPinningDoomRestriction = NO_RESTRICTION);
+ nsresult DoomFileByKeyInternal(const SHA1Sum::Hash* aHash);
+ nsresult MaybeReleaseNSPRHandleInternal(CacheFileHandle* aHandle,
+ bool aIgnoreShutdownLag = false);
+ nsresult TruncateSeekSetEOFInternal(CacheFileHandle* aHandle,
+ int64_t aTruncatePos, int64_t aEOFPos);
+ nsresult RenameFileInternal(CacheFileHandle* aHandle,
+ const nsACString& aNewName);
+ nsresult EvictIfOverLimitInternal();
+ nsresult OverLimitEvictionInternal();
+ nsresult EvictAllInternal();
+ nsresult EvictByContextInternal(nsILoadContextInfo* aLoadContextInfo,
+ bool aPinned, const nsAString& aOrigin,
+ const nsAString& aBaseDomain = u""_ns);
+
+ nsresult TrashDirectory(nsIFile* aFile);
+ static void OnTrashTimer(nsITimer* aTimer, void* aClosure);
+ nsresult StartRemovingTrash();
+ nsresult RemoveTrashInternal();
+ nsresult FindTrashDirToRemove();
+
+ nsresult CreateFile(CacheFileHandle* aHandle);
+ static void HashToStr(const SHA1Sum::Hash* aHash, nsACString& _retval);
+ static nsresult StrToHash(const nsACString& aHash, SHA1Sum::Hash* _retval);
+ nsresult GetFile(const SHA1Sum::Hash* aHash, nsIFile** _retval);
+ nsresult GetSpecialFile(const nsACString& aKey, nsIFile** _retval);
+ nsresult GetDoomedFile(nsIFile** _retval);
+ nsresult IsEmptyDirectory(nsIFile* aFile, bool* _retval);
+ nsresult CheckAndCreateDir(nsIFile* aFile, const char* aDir,
+ bool aEnsureEmptyDir);
+ nsresult CreateCacheTree();
+ nsresult OpenNSPRHandle(CacheFileHandle* aHandle, bool aCreate = false);
+ void NSPRHandleUsed(CacheFileHandle* aHandle);
+
+ // Removing all cache files during shutdown
+ nsresult SyncRemoveDir(nsIFile* aFile, const char* aDir);
+ void SyncRemoveAllCacheFiles();
+
+ nsresult ScheduleMetadataWriteInternal(CacheFile* aFile);
+ void UnscheduleMetadataWriteInternal(CacheFile* aFile);
+ void ShutdownMetadataWriteSchedulingInternal();
+
+ static nsresult CacheIndexStateChanged();
+ void CacheIndexStateChangedInternal();
+
+ // Dispatches a purgeHTTP background task to delete the cache directoy
+ // indicated by aCacheDirName.
+ // When this feature is enabled, a task will be dispatched at shutdown
+ // or after browser startup (to cleanup potential left-over directories)
+ nsresult DispatchPurgeTask(const nsCString& aCacheDirName,
+ const nsCString& aSecondsToWait,
+ const nsCString& aPurgeExtension);
+
+ // Smart size calculation. UpdateSmartCacheSize() must be called on IO thread.
+ // It is called in EvictIfOverLimitInternal() just before we decide whether to
+ // start overlimit eviction or not and also in OverLimitEvictionInternal()
+ // before we start an eviction loop.
+ nsresult UpdateSmartCacheSize(int64_t aFreeSpace);
+
+ // Memory reporting (private part)
+ size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ static StaticRefPtr<CacheFileIOManager> gInstance;
+
+ TimeStamp mStartTime;
+ // Set true on the IO thread, CLOSE level as part of the internal shutdown
+ // procedure.
+ bool mShuttingDown{false};
+ RefPtr<CacheIOThread> mIOThread;
+ nsCOMPtr<nsIFile> mCacheDirectory;
+#if defined(MOZ_WIDGET_ANDROID)
+ // On Android we add the active profile directory name between the path
+ // and the 'cache2' leaf name. However, to delete any leftover data from
+ // times before we were doing it, we still need to access the directory
+ // w/o the profile name in the path. Here it is stored.
+ nsCOMPtr<nsIFile> mCacheProfilelessDirectory;
+#endif
+ bool mTreeCreated{false};
+ bool mTreeCreationFailed{false};
+ CacheFileHandles mHandles;
+ nsTArray<CacheFileHandle*> mHandlesByLastUsed;
+ nsTArray<CacheFileHandle*> mSpecialHandles;
+ nsTArray<RefPtr<CacheFile> > mScheduledMetadataWrites;
+ nsCOMPtr<nsITimer> mMetadataWritesTimer;
+ bool mOverLimitEvicting{false};
+ // When overlimit eviction is too slow and cache size reaches 105% of the
+ // limit, this flag is set and no other content is cached to prevent
+ // uncontrolled cache growing.
+ bool mCacheSizeOnHardLimit{false};
+ bool mRemovingTrashDirs{false};
+ nsCOMPtr<nsITimer> mTrashTimer;
+ nsCOMPtr<nsIFile> mTrashDir;
+ nsCOMPtr<nsIDirectoryEnumerator> mTrashDirEnumerator;
+ nsTArray<nsCString> mFailedTrashDirs;
+ RefPtr<CacheFileContextEvictor> mContextEvictor;
+ TimeStamp mLastSmartSizeTime;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif