Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/netwerk/cache2/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 127 kB image not shown  

Quelle  CacheFileIOManager.cpp   Sprache: C

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include <limits>
#include "CacheLog.h"
#include "CacheFileIOManager.h"

#include "CacheHashUtils.h"
#include "CacheStorageService.h"
#include "CacheIndex.h"
#include "CacheFileUtils.h"
#include "nsError.h"
#include "nsThreadUtils.h"
#include "CacheFile.h"
#include "CacheObserver.h"
#include "nsIFile.h"
#include "CacheFileContextEvictor.h"
#include "nsITimer.h"
#include "nsIDirectoryEnumerator.h"
#include "nsEffectiveTLDService.h"
#include "nsIObserverService.h"
#include "nsISizeOf.h"
#include "mozilla/net/MozURL.h"
#include "mozilla/Telemetry.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Services.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "nsDirectoryServiceUtils.h"
#include "nsAppDirectoryServiceDefs.h"
#include "private/pprio.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Preferences.h"
#include "nsNetUtil.h"
#include "mozilla/glean/NetwerkMetrics.h"

#ifdef MOZ_BACKGROUNDTASKS
#  include "mozilla/BackgroundTasksRunner.h"
#  include "nsIBackgroundTasks.h"
#endif

// include files for ftruncate (or equivalent)
#if defined(XP_UNIX)
#  include <unistd.h>
#elif defined(XP_WIN)
#  include <windows.h>
#  undef CreateFile
#  undef CREATE_NEW
#else
// XXX add necessary include file for ftruncate (or equivalent)
#endif

namespace mozilla::net {

#define kOpenHandlesLimit 128
#define kMetadataWriteDelay 5000
#define kRemoveTrashStartDelay 60000    // in milliseconds
#define kSmartSizeUpdateInterval 60000  // in milliseconds

#ifdef ANDROID
const uint32_t kMaxCacheSizeKB = 512 * 1024;  // 512 MB
#else
const uint32_t kMaxCacheSizeKB = 1024 * 1024;  // 1 GB
#endif
const uint32_t kMaxClearOnShutdownCacheSizeKB = 150 * 1024;  // 150 MB
const auto kPurgeExtension = ".purge.bg_rm"_ns;

bool CacheFileHandle::DispatchRelease() {
  if (CacheFileIOManager::IsOnIOThreadOrCeased()) {
    return false;
  }

  nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
  if (!ioTarget) {
    return false;
  }

  nsresult rv = ioTarget->Dispatch(
      NewNonOwningRunnableMethod("net::CacheFileHandle::Release"this,
                                 &CacheFileHandle::Release),
      nsIEventTarget::DISPATCH_NORMAL);
  return NS_SUCCEEDED(rv);
}

NS_IMPL_ADDREF(CacheFileHandle)
NS_IMETHODIMP_(MozExternalRefCountType)
CacheFileHandle::Release() {
  nsrefcnt count = mRefCnt - 1;
  if (DispatchRelease()) {
    // Redispatched to the IO thread.
    return count;
  }

  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());

  LOG(("CacheFileHandle::Release() [this=%p, refcnt=%" PRIuPTR "]"this,
       mRefCnt.get()));
  MOZ_ASSERT(0 != mRefCnt, "dup release");
  count = --mRefCnt;
  NS_LOG_RELEASE(this, count, "CacheFileHandle");

  if (0 == count) {
    mRefCnt = 1;
    delete (this);
    return 0;
  }

  return count;
}

NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash* aHash, bool aPriority,
                                 PinningStatus aPinning)
    : mHash(aHash),
      mIsDoomed(false),
      mClosed(false),
      mPriority(aPriority),
      mSpecialFile(false),
      mInvalid(false),
      mFileExists(false),
      mDoomWhenFoundPinned(false),
      mDoomWhenFoundNonPinned(false),
      mKilled(false),
      mPinning(aPinning),
      mFileSize(-1),
      mFD(nullptr) {
  // If we initialize mDoomed in the initialization list, that initialization is
  // not guaranteeded to be atomic.  Whereas this assignment here is guaranteed
  // to be atomic.  TSan will see this (atomic) assignment and be satisfied
  // that cross-thread accesses to mIsDoomed are properly synchronized.
  mIsDoomed = false;
  LOG((
      "CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]",
      this, LOGSHA1(aHash)));
}

CacheFileHandle::CacheFileHandle(const nsACString& aKey, bool aPriority,
                                 PinningStatus aPinning)
    : mHash(nullptr),
      mIsDoomed(false),
      mClosed(false),
      mPriority(aPriority),
      mSpecialFile(true),
      mInvalid(false),
      mFileExists(false),
      mDoomWhenFoundPinned(false),
      mDoomWhenFoundNonPinned(false),
      mKilled(false),
      mPinning(aPinning),
      mFileSize(-1),
      mFD(nullptr),
      mKey(aKey) {
  // See comment above about the initialization of mIsDoomed.
  mIsDoomed = false;
  LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]"this,
       PromiseFlatCString(aKey).get()));
}

CacheFileHandle::~CacheFileHandle() {
  LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]"this));

  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());

  RefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
  if (!IsClosed() && ioMan) {
    ioMan->CloseHandleInternal(this);
  }
}

void CacheFileHandle::Log() {
  nsAutoCString leafName;
  if (mFile) {
    mFile->GetNativeLeafName(leafName);
  }

  if (mSpecialFile) {
    LOG(
        ("CacheFileHandle::Log() - special file [this=%p, "
         "isDoomed=%d, priority=%d, closed=%d, invalid=%d, "
         "pinning=%" PRIu32 ", fileExists=%d, fileSize=%" PRId64
         ", leafName=%s, key=%s]",
         thisbool(mIsDoomed), bool(mPriority), bool(mClosed), bool(mInvalid),
         static_cast<uint32_t>(mPinning), bool(mFileExists), int64_t(mFileSize),
         leafName.get(), mKey.get()));
  } else {
    LOG(
        ("CacheFileHandle::Log() - entry file [this=%p, "
         "hash=%08x%08x%08x%08x%08x, "
         "isDoomed=%d, priority=%d, closed=%d, invalid=%d, "
         "pinning=%" PRIu32 ", fileExists=%d, fileSize=%" PRId64
         ", leafName=%s, key=%s]",
         this, LOGSHA1(mHash), bool(mIsDoomed), bool(mPriority), bool(mClosed),
         bool(mInvalid), static_cast<uint32_t>(mPinning), bool(mFileExists),
         int64_t(mFileSize), leafName.get(), mKey.get()));
  }
}

uint32_t CacheFileHandle::FileSizeInK() const {
  MOZ_ASSERT(mFileSize != -1);
  uint64_t size64 = mFileSize;

  size64 += 0x3FF;
  size64 >>= 10;

  uint32_t size;
  if (size64 >> 32) {
    NS_WARNING(
        "CacheFileHandle::FileSizeInK() - FileSize is too large, "
        "truncating to PR_UINT32_MAX");
    size = PR_UINT32_MAX;
  } else {
    size = static_cast<uint32_t>(size64);
  }

  return size;
}

bool CacheFileHandle::SetPinned(bool aPinned) {
  LOG(("CacheFileHandle::SetPinned [this=%p, pinned=%d]"this, aPinned));

  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());

  mPinning = aPinned ? PinningStatus::PINNED : PinningStatus::NON_PINNED;

  if ((MOZ_UNLIKELY(mDoomWhenFoundPinned) && aPinned) ||
      (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && !aPinned)) {
    LOG((" dooming, when: pinned=%d, non-pinned=%d, found: pinned=%d",
         bool(mDoomWhenFoundPinned), bool(mDoomWhenFoundNonPinned), aPinned));

    mDoomWhenFoundPinned = false;
    mDoomWhenFoundNonPinned = false;

    return false;
  }

  return true;
}

// Memory reporting

size_t CacheFileHandle::SizeOfExcludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
  size_t n = 0;
  nsCOMPtr<nsISizeOf> sizeOf;

  sizeOf = do_QueryInterface(mFile);
  if (sizeOf) {
    n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
  }

  n += mallocSizeOf(mFD);
  n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
  return n;
}

size_t CacheFileHandle::SizeOfIncludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
  return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
}

/******************************************************************************
 *  CacheFileHandles::HandleHashKey
 *****************************************************************************/


void CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle* aHandle) {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());

  mHandles.InsertElementAt(0, aHandle);
}

void CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle* aHandle) {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());

  DebugOnly<bool> found{};
  found = mHandles.RemoveElement(aHandle);
  MOZ_ASSERT(found);
}

already_AddRefed<CacheFileHandle>
CacheFileHandles::HandleHashKey::GetNewestHandle() {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());

  RefPtr<CacheFileHandle> handle;
  if (mHandles.Length()) {
    handle = mHandles[0];
  }

  return handle.forget();
}

void CacheFileHandles::HandleHashKey::GetHandles(
    nsTArray<RefPtr<CacheFileHandle>>& aResult) {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());

  for (uint32_t i = 0; i < mHandles.Length(); ++i) {
    CacheFileHandle* handle = mHandles[i];
    aResult.AppendElement(handle);
  }
}

#ifdef DEBUG

void CacheFileHandles::HandleHashKey::AssertHandlesState() {
  for (uint32_t i = 0; i < mHandles.Length(); ++i) {
    CacheFileHandle* handle = mHandles[i];
    MOZ_ASSERT(handle->IsDoomed());
  }
}

#endif

size_t CacheFileHandles::HandleHashKey::SizeOfExcludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());

  size_t n = 0;
  n += mallocSizeOf(mHash.get());
  for (uint32_t i = 0; i < mHandles.Length(); ++i) {
    n += mHandles[i]->SizeOfIncludingThis(mallocSizeOf);
  }

  return n;
}

/******************************************************************************
 *  CacheFileHandles
 *****************************************************************************/


CacheFileHandles::CacheFileHandles() {
  LOG(("CacheFileHandles::CacheFileHandles() [this=%p]"this));
  MOZ_COUNT_CTOR(CacheFileHandles);
}

CacheFileHandles::~CacheFileHandles() {
  LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]"this));
  MOZ_COUNT_DTOR(CacheFileHandles);
}

nsresult CacheFileHandles::GetHandle(const SHA1Sum::Hash* aHash,
                                     CacheFileHandle** _retval) {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
  MOZ_ASSERT(aHash);

#ifdef DEBUG_HANDLES
  LOG(("CacheFileHandles::GetHandle() [hash=%08x%08x%08x%08x%08x]",
       LOGSHA1(aHash)));
#endif

  // find hash entry for key
  HandleHashKey* entry = mTable.GetEntry(*aHash);
  if (!entry) {
    LOG(
        ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
         "no handle entries found",
         LOGSHA1(aHash)));
    return NS_ERROR_NOT_AVAILABLE;
  }

#ifdef DEBUG_HANDLES
  Log(entry);
#endif

  // Check if the entry is doomed
  RefPtr<CacheFileHandle> handle = entry->GetNewestHandle();
  if (!handle) {
    LOG(
        ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
         "no handle found %p, entry %p",
         LOGSHA1(aHash), handle.get(), entry));
    return NS_ERROR_NOT_AVAILABLE;
  }

  if (handle->IsDoomed()) {
    LOG(
        ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
         "found doomed handle %p, entry %p",
         LOGSHA1(aHash), handle.get(), entry));
    return NS_ERROR_NOT_AVAILABLE;
  }

  LOG(
      ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
       "found handle %p, entry %p",
       LOGSHA1(aHash), handle.get(), entry));

  handle.forget(_retval);
  return NS_OK;
}

already_AddRefed<CacheFileHandle> CacheFileHandles::NewHandle(
    const SHA1Sum::Hash* aHash, bool aPriority,
    CacheFileHandle::PinningStatus aPinning) {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
  MOZ_ASSERT(aHash);

#ifdef DEBUG_HANDLES
  LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]",
       LOGSHA1(aHash)));
#endif

  // find hash entry for key
  HandleHashKey* entry = mTable.PutEntry(*aHash);

#ifdef DEBUG_HANDLES
  Log(entry);
#endif

#ifdef DEBUG
  entry->AssertHandlesState();
#endif

  RefPtr<CacheFileHandle> handle =
      new CacheFileHandle(entry->Hash(), aPriority, aPinning);
  entry->AddHandle(handle);

  LOG(
      ("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
       "created new handle %p, entry=%p",
       LOGSHA1(aHash), handle.get(), entry));
  return handle.forget();
}

void CacheFileHandles::RemoveHandle(CacheFileHandle* aHandle) {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
  MOZ_ASSERT(aHandle);

  if (!aHandle) {
    return;
  }

#ifdef DEBUG_HANDLES
  LOG((
      "CacheFileHandles::RemoveHandle() [handle=%p, hash=%08x%08x%08x%08x%08x]",
      aHandle, LOGSHA1(aHandle->Hash())));
#endif

  // find hash entry for key
  HandleHashKey* entry = mTable.GetEntry(*aHandle->Hash());
  if (!entry) {
    MOZ_ASSERT(CacheFileIOManager::IsShutdown(),
               "Should find entry when removing a handle before shutdown");

    LOG(
        ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
         "no entries found",
         LOGSHA1(aHandle->Hash())));
    return;
  }

#ifdef DEBUG_HANDLES
  Log(entry);
#endif

  LOG(
      ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
       "removing handle %p",
       LOGSHA1(entry->Hash()), aHandle));
  entry->RemoveHandle(aHandle);

  if (entry->IsEmpty()) {
    LOG(
        ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
         "list is empty, removing entry %p",
         LOGSHA1(entry->Hash()), entry));
    mTable.RemoveEntry(entry);
  }
}

void CacheFileHandles::GetAllHandles(
    nsTArray<RefPtr<CacheFileHandle>>* _retval) {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
  for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
    iter.Get()->GetHandles(*_retval);
  }
}

void CacheFileHandles::GetActiveHandles(
    nsTArray<RefPtr<CacheFileHandle>>* _retval) {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
  for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
    RefPtr<CacheFileHandle> handle = iter.Get()->GetNewestHandle();
    MOZ_ASSERT(handle);

    if (!handle->IsDoomed()) {
      _retval->AppendElement(handle);
    }
  }
}

void CacheFileHandles::ClearAll() {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
  mTable.Clear();
}

uint32_t CacheFileHandles::HandleCount() { return mTable.Count(); }

#ifdef DEBUG_HANDLES
void CacheFileHandles::Log(CacheFileHandlesEntry* entry) {
  LOG(("CacheFileHandles::Log() BEGIN [entry=%p]", entry));

  nsTArray<RefPtr<CacheFileHandle>> array;
  aEntry->GetHandles(array);

  for (uint32_t i = 0; i < array.Length(); ++i) {
    CacheFileHandle* handle = array[i];
    handle->Log();
  }

  LOG(("CacheFileHandles::Log() END [entry=%p]", entry));
}
#endif

// Memory reporting

size_t CacheFileHandles::SizeOfExcludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());

  return mTable.SizeOfExcludingThis(mallocSizeOf);
}

// Events

class ShutdownEvent : public Runnable, nsITimerCallback {
  NS_DECL_ISUPPORTS_INHERITED
 public:
  ShutdownEvent() : Runnable("net::ShutdownEvent") {}

 protected:
  ~ShutdownEvent() = default;

 public:
  NS_IMETHOD Run() override {
    CacheFileIOManager::gInstance->ShutdownInternal();

    mNotified = true;

    NS_DispatchToMainThread(
        NS_NewRunnableFunction("CacheFileIOManager::ShutdownEvent::Run", []() {
          // This empty runnable is dispatched just in case the MT event loop
          // becomes empty - we need to process a task to break out of
          // SpinEventLoopUntil.
        }));

    return NS_OK;
  }

  NS_IMETHOD Notify(nsITimer* timer) override {
    if (mNotified) {
      return NS_OK;
    }

    // If there is any IO blocking on the IO thread, this will
    // try to cancel it.
    CacheFileIOManager::gInstance->mIOThread->CancelBlockingIO();

    // After this runs the first time, the browser_cache_max_shutdown_io_lag
    // time has elapsed. The CacheIO thread may pick up more blocking IO tasks
    // so we want to block those too if necessary.
    mTimer->SetDelay(
        StaticPrefs::browser_cache_shutdown_io_time_between_cancellations_ms());
    return NS_OK;
  }

  void PostAndWait() {
    nsresult rv = CacheFileIOManager::gInstance->mIOThread->Dispatch(
        this,
        CacheIOThread::WRITE);  // When writes and closing of handles is done
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    // If we failed to post the even there's no reason to go into the loop
    // because mNotified will never be set to true.
    if (NS_FAILED(rv)) {
      NS_WARNING("Posting ShutdownEvent task failed");
      return;
    }

    rv = NS_NewTimerWithCallback(
        getter_AddRefs(mTimer), this,
        StaticPrefs::browser_cache_max_shutdown_io_lag() * 1000,
        nsITimer::TYPE_REPEATING_SLACK);
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    mozilla::SpinEventLoopUntil("CacheFileIOManager::ShutdownEvent"_ns,
                                [&]() { return bool(mNotified); });

    if (mTimer) {
      mTimer->Cancel();
      mTimer = nullptr;
    }
  }

 protected:
  Atomic<bool> mNotified{false};
  nsCOMPtr<nsITimer> mTimer;
};

NS_IMPL_ISUPPORTS_INHERITED(ShutdownEvent, Runnable, nsITimerCallback)

// Class responsible for reporting IO performance stats
class IOPerfReportEvent {
 public:
  explicit IOPerfReportEvent(CacheFileUtils::CachePerfStats::EDataType aType)
      : mType(aType), mEventCounter(0) {}

  void Start(CacheIOThread* aIOThread) {
    mStartTime = TimeStamp::Now();
    mEventCounter = aIOThread->EventCounter();
  }

  void Report(CacheIOThread* aIOThread) {
    if (mStartTime.IsNull()) {
      return;
    }

    // Single IO operations can take less than 1ms. So we use microseconds to
    // keep a good resolution of data.
    uint32_t duration = (TimeStamp::Now() - mStartTime).ToMicroseconds();

    // This is a simple prefiltering of values that might differ a lot from the
    // average value. Do not add the value to the filtered stats when the event
    // had to wait in a long queue.
    uint32_t eventCounter = aIOThread->EventCounter();
    bool shortOnly = eventCounter - mEventCounter >= 5;

    CacheFileUtils::CachePerfStats::AddValue(mType, duration, shortOnly);
  }

 protected:
  CacheFileUtils::CachePerfStats::EDataType mType;
  TimeStamp mStartTime;
  uint32_t mEventCounter;
};

class OpenFileEvent : public Runnable, public IOPerfReportEvent {
 public:
  OpenFileEvent(const nsACString& aKey, uint32_t aFlags,
                CacheFileIOListener* aCallback)
      : Runnable("net::OpenFileEvent"),
        IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_OPEN),
        mFlags(aFlags),
        mCallback(aCallback),
        mKey(aKey) {
    mIOMan = CacheFileIOManager::gInstance;
    if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) {
      Start(mIOMan->mIOThread);
    }
  }

 protected:
  ~OpenFileEvent() = default;

 public:
  NS_IMETHOD Run() override {
    nsresult rv = NS_OK;

    if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) {
      SHA1Sum sum;
      sum.update(mKey.BeginReading(), mKey.Length());
      sum.finish(mHash);
    }

    if (!mIOMan) {
      rv = NS_ERROR_NOT_INITIALIZED;
    } else {
      if (mFlags & CacheFileIOManager::SPECIAL_FILE) {
        rv = mIOMan->OpenSpecialFileInternal(mKey, mFlags,
                                             getter_AddRefs(mHandle));
      } else {
        rv = mIOMan->OpenFileInternal(&mHash, mKey, mFlags,
                                      getter_AddRefs(mHandle));
        if (NS_SUCCEEDED(rv)) {
          Report(mIOMan->mIOThread);
        }
      }
      mIOMan = nullptr;
      if (mHandle) {
        if (mHandle->Key().IsEmpty()) {
          mHandle->Key() = mKey;
        }
      }
    }

    mCallback->OnFileOpened(mHandle, rv);
    return NS_OK;
  }

 protected:
  SHA1Sum::Hash mHash{};
  uint32_t mFlags;
  nsCOMPtr<CacheFileIOListener> mCallback;
  RefPtr<CacheFileIOManager> mIOMan;
  RefPtr<CacheFileHandle> mHandle;
  nsCString mKey;
};

class ReadEvent : public Runnable, public IOPerfReportEvent {
 public:
  ReadEvent(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf,
            int32_t aCount, CacheFileIOListener* aCallback)
      : Runnable("net::ReadEvent"),
        IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_READ),
        mHandle(aHandle),
        mOffset(aOffset),
        mBuf(aBuf),
        mCount(aCount),
        mCallback(aCallback) {
    if (!mHandle->IsSpecialFile()) {
      Start(CacheFileIOManager::gInstance->mIOThread);
    }
  }

 protected:
  ~ReadEvent() = default;

 public:
  NS_IMETHOD Run() override {
    nsresult rv;

    if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
      rv = NS_ERROR_NOT_INITIALIZED;
    } else {
      rv = CacheFileIOManager::gInstance->ReadInternal(mHandle, mOffset, mBuf,
                                                       mCount);
      if (NS_SUCCEEDED(rv)) {
        Report(CacheFileIOManager::gInstance->mIOThread);
      }
    }

    mCallback->OnDataRead(mHandle, mBuf, rv);
    return NS_OK;
  }

 protected:
  RefPtr<CacheFileHandle> mHandle;
  int64_t mOffset;
  char* mBuf;
  int32_t mCount;
  nsCOMPtr<CacheFileIOListener> mCallback;
};

class WriteEvent : public Runnable, public IOPerfReportEvent {
 public:
  WriteEvent(CacheFileHandle* aHandle, int64_t aOffset, const char* aBuf,
             int32_t aCount, bool aValidate, bool aTruncate,
             CacheFileIOListener* aCallback)
      : Runnable("net::WriteEvent"),
        IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_WRITE),
        mHandle(aHandle),
        mOffset(aOffset),
        mBuf(aBuf),
        mCount(aCount),
        mValidate(aValidate),
        mTruncate(aTruncate),
        mCallback(aCallback) {
    if (!mHandle->IsSpecialFile()) {
      Start(CacheFileIOManager::gInstance->mIOThread);
    }
  }

 protected:
  ~WriteEvent() {
    if (!mCallback && mBuf) {
      free(const_cast<char*>(mBuf));
    }
  }

 public:
  NS_IMETHOD Run() override {
    nsresult rv;

    if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
      // We usually get here only after the internal shutdown
      // (i.e. mShuttingDown == true).  Pretend write has succeeded
      // to avoid any past-shutdown file dooming.
      rv = (CacheObserver::IsPastShutdownIOLag() ||
            CacheFileIOManager::gInstance->mShuttingDown)
               ? NS_OK
               : NS_ERROR_NOT_INITIALIZED;
    } else {
      rv = CacheFileIOManager::gInstance->WriteInternal(
          mHandle, mOffset, mBuf, mCount, mValidate, mTruncate);
      if (NS_SUCCEEDED(rv)) {
        Report(CacheFileIOManager::gInstance->mIOThread);
      }
      if (NS_FAILED(rv) && !mCallback) {
        // No listener is going to handle the error, doom the file
        CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
      }
    }
    if (mCallback) {
      mCallback->OnDataWritten(mHandle, mBuf, rv);
    } else {
      free(const_cast<char*>(mBuf));
      mBuf = nullptr;
    }

    return NS_OK;
  }

 protected:
  RefPtr<CacheFileHandle> mHandle;
  int64_t mOffset;
  const char* mBuf;
  int32_t mCount;
  bool mValidate : 1;
  bool mTruncate : 1;
  nsCOMPtr<CacheFileIOListener> mCallback;
};

class DoomFileEvent : public Runnable {
 public:
  DoomFileEvent(CacheFileHandle* aHandle, CacheFileIOListener* aCallback)
      : Runnable("net::DoomFileEvent"),
        mCallback(aCallback),
        mHandle(aHandle) {}

 protected:
  ~DoomFileEvent() = default;

 public:
  NS_IMETHOD Run() override {
    nsresult rv;

    if (mHandle->IsClosed()) {
      rv = NS_ERROR_NOT_INITIALIZED;
    } else {
      rv = CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
    }

    if (mCallback) {
      mCallback->OnFileDoomed(mHandle, rv);
    }

    return NS_OK;
  }

 protected:
  nsCOMPtr<CacheFileIOListener> mCallback;
  nsCOMPtr<nsIEventTarget> mTarget;
  RefPtr<CacheFileHandle> mHandle;
};

class DoomFileByKeyEvent : public Runnable {
 public:
  DoomFileByKeyEvent(const nsACString& aKey, CacheFileIOListener* aCallback)
      : Runnable("net::DoomFileByKeyEvent"), mCallback(aCallback) {
    SHA1Sum sum;
    sum.update(aKey.BeginReading(), aKey.Length());
    sum.finish(mHash);

    mIOMan = CacheFileIOManager::gInstance;
  }

 protected:
  ~DoomFileByKeyEvent() = default;

 public:
  NS_IMETHOD Run() override {
    nsresult rv;

    if (!mIOMan) {
      rv = NS_ERROR_NOT_INITIALIZED;
    } else {
      rv = mIOMan->DoomFileByKeyInternal(&mHash);
      mIOMan = nullptr;
    }

    if (mCallback) {
      mCallback->OnFileDoomed(nullptr, rv);
    }

    return NS_OK;
  }

 protected:
  SHA1Sum::Hash mHash{};
  nsCOMPtr<CacheFileIOListener> mCallback;
  RefPtr<CacheFileIOManager> mIOMan;
};

class ReleaseNSPRHandleEvent : public Runnable {
 public:
  explicit ReleaseNSPRHandleEvent(CacheFileHandle* aHandle)
      : Runnable("net::ReleaseNSPRHandleEvent"), mHandle(aHandle) {}

 protected:
  ~ReleaseNSPRHandleEvent() = default;

 public:
  NS_IMETHOD Run() override {
    if (!mHandle->IsClosed()) {
      CacheFileIOManager::gInstance->MaybeReleaseNSPRHandleInternal(mHandle);
    }

    return NS_OK;
  }

 protected:
  RefPtr<CacheFileHandle> mHandle;
};

class TruncateSeekSetEOFEvent : public Runnable {
 public:
  TruncateSeekSetEOFEvent(CacheFileHandle* aHandle, int64_t aTruncatePos,
                          int64_t aEOFPos, CacheFileIOListener* aCallback)
      : Runnable("net::TruncateSeekSetEOFEvent"),
        mHandle(aHandle),
        mTruncatePos(aTruncatePos),
        mEOFPos(aEOFPos),
        mCallback(aCallback) {}

 protected:
  ~TruncateSeekSetEOFEvent() = default;

 public:
  NS_IMETHOD Run() override {
    nsresult rv;

    if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
      rv = NS_ERROR_NOT_INITIALIZED;
    } else {
      rv = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal(
          mHandle, mTruncatePos, mEOFPos);
    }

    if (mCallback) {
      mCallback->OnEOFSet(mHandle, rv);
    }

    return NS_OK;
  }

 protected:
  RefPtr<CacheFileHandle> mHandle;
  int64_t mTruncatePos;
  int64_t mEOFPos;
  nsCOMPtr<CacheFileIOListener> mCallback;
};

class RenameFileEvent : public Runnable {
 public:
  RenameFileEvent(CacheFileHandle* aHandle, const nsACString& aNewName,
                  CacheFileIOListener* aCallback)
      : Runnable("net::RenameFileEvent"),
        mHandle(aHandle),
        mNewName(aNewName),
        mCallback(aCallback) {}

 protected:
  ~RenameFileEvent() = default;

 public:
  NS_IMETHOD Run() override {
    nsresult rv;

    if (mHandle->IsClosed()) {
      rv = NS_ERROR_NOT_INITIALIZED;
    } else {
      rv = CacheFileIOManager::gInstance->RenameFileInternal(mHandle, mNewName);
    }

    if (mCallback) {
      mCallback->OnFileRenamed(mHandle, rv);
    }

    return NS_OK;
  }

 protected:
  RefPtr<CacheFileHandle> mHandle;
  nsCString mNewName;
  nsCOMPtr<CacheFileIOListener> mCallback;
};

class InitIndexEntryEvent : public Runnable {
 public:
  InitIndexEntryEvent(CacheFileHandle* aHandle,
                      OriginAttrsHash aOriginAttrsHash, bool aAnonymous,
                      bool aPinning)
      : Runnable("net::InitIndexEntryEvent"),
        mHandle(aHandle),
        mOriginAttrsHash(aOriginAttrsHash),
        mAnonymous(aAnonymous),
        mPinning(aPinning) {}

 protected:
  ~InitIndexEntryEvent() = default;

 public:
  NS_IMETHOD Run() override {
    if (mHandle->IsClosed() || mHandle->IsDoomed()) {
      return NS_OK;
    }

    CacheIndex::InitEntry(mHandle->Hash(), mOriginAttrsHash, mAnonymous,
                          mPinning);

    // We cannot set the filesize before we init the entry. If we're opening
    // an existing entry file, frecency will be set after parsing the entry
    // file, but we must set the filesize here since nobody is going to set it
    // if there is no write to the file.
    uint32_t sizeInK = mHandle->FileSizeInK();
    CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, nullptr, nullptr,
                            nullptr, &sizeInK);

    return NS_OK;
  }

 protected:
  RefPtr<CacheFileHandle> mHandle;
  OriginAttrsHash mOriginAttrsHash;
  bool mAnonymous;
  bool mPinning;
};

class UpdateIndexEntryEvent : public Runnable {
 public:
  UpdateIndexEntryEvent(CacheFileHandle* aHandle, const uint32_t* aFrecency,
                        const bool* aHasAltData, const uint16_t* aOnStartTime,
                        const uint16_t* aOnStopTime,
                        const uint8_t* aContentType)
      : Runnable("net::UpdateIndexEntryEvent"),
        mHandle(aHandle),
        mHasFrecency(false),
        mHasHasAltData(false),
        mHasOnStartTime(false),
        mHasOnStopTime(false),
        mHasContentType(false),
        mFrecency(0),
        mHasAltData(false),
        mOnStartTime(0),
        mOnStopTime(0),
        mContentType(nsICacheEntry::CONTENT_TYPE_UNKNOWN) {
    if (aFrecency) {
      mHasFrecency = true;
      mFrecency = *aFrecency;
    }
    if (aHasAltData) {
      mHasHasAltData = true;
      mHasAltData = *aHasAltData;
    }
    if (aOnStartTime) {
      mHasOnStartTime = true;
      mOnStartTime = *aOnStartTime;
    }
    if (aOnStopTime) {
      mHasOnStopTime = true;
      mOnStopTime = *aOnStopTime;
    }
    if (aContentType) {
      mHasContentType = true;
      mContentType = *aContentType;
    }
  }

 protected:
  ~UpdateIndexEntryEvent() = default;

 public:
  NS_IMETHOD Run() override {
    if (mHandle->IsClosed() || mHandle->IsDoomed()) {
      return NS_OK;
    }

    CacheIndex::UpdateEntry(mHandle->Hash(),
                            mHasFrecency ? &mFrecency : nullptr,
                            mHasHasAltData ? &mHasAltData : nullptr,
                            mHasOnStartTime ? &mOnStartTime : nullptr,
                            mHasOnStopTime ? &mOnStopTime : nullptr,
                            mHasContentType ? &mContentType : nullptr, nullptr);
    return NS_OK;
  }

 protected:
  RefPtr<CacheFileHandle> mHandle;

  bool mHasFrecency;
  bool mHasHasAltData;
  bool mHasOnStartTime;
  bool mHasOnStopTime;
  bool mHasContentType;

  uint32_t mFrecency;
  bool mHasAltData;
  uint16_t mOnStartTime;
  uint16_t mOnStopTime;
  uint8_t mContentType;
};

class MetadataWriteScheduleEvent : public Runnable {
 public:
  enum EMode { SCHEDULE, UNSCHEDULE, SHUTDOWN } mMode;

  RefPtr<CacheFile> mFile;
  RefPtr<CacheFileIOManager> mIOMan;

  MetadataWriteScheduleEvent(CacheFileIOManager* aManager, CacheFile* aFile,
                             EMode aMode)
      : Runnable("net::MetadataWriteScheduleEvent"),
        mMode(aMode),
        mFile(aFile),
        mIOMan(aManager) {}

  virtual ~MetadataWriteScheduleEvent() = default;

  NS_IMETHOD Run() override {
    RefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
    if (!ioMan) {
      NS_WARNING(
          "CacheFileIOManager already gone in "
          "MetadataWriteScheduleEvent::Run()");
      return NS_OK;
    }

    switch (mMode) {
      case SCHEDULE:
        ioMan->ScheduleMetadataWriteInternal(mFile);
        break;
      case UNSCHEDULE:
        ioMan->UnscheduleMetadataWriteInternal(mFile);
        break;
      case SHUTDOWN:
        ioMan->ShutdownMetadataWriteSchedulingInternal();
        break;
    }
    return NS_OK;
  }
};

StaticRefPtr<CacheFileIOManager> CacheFileIOManager::gInstance;

NS_IMPL_ISUPPORTS(CacheFileIOManager, nsITimerCallback, nsINamed)

CacheFileIOManager::CacheFileIOManager()

{
  LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]"this));
  MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!");
}

CacheFileIOManager::~CacheFileIOManager() {
  LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]"this));
}

// static
nsresult CacheFileIOManager::Init() {
  LOG(("CacheFileIOManager::Init()"));

  MOZ_ASSERT(NS_IsMainThread());

  if (gInstance) {
    return NS_ERROR_ALREADY_INITIALIZED;
  }

  RefPtr<CacheFileIOManager> ioMan = new CacheFileIOManager();

  nsresult rv = ioMan->InitInternal();
  NS_ENSURE_SUCCESS(rv, rv);

  gInstance = std::move(ioMan);
  return NS_OK;
}

nsresult CacheFileIOManager::InitInternal() {
  nsresult rv;

  mIOThread = new CacheIOThread();

  rv = mIOThread->Init();
  MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread");
  NS_ENSURE_SUCCESS(rv, rv);

  mStartTime = TimeStamp::NowLoRes();

  return NS_OK;
}

// static
nsresult CacheFileIOManager::Shutdown() {
  LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance.get()));

  MOZ_ASSERT(NS_IsMainThread());

  if (!gInstance) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_V2> shutdownTimer;

  CacheIndex::PreShutdown();

  ShutdownMetadataWriteScheduling();

  RefPtr<ShutdownEvent> ev = new ShutdownEvent();
  ev->PostAndWait();

  MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0);
  MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0);

  if (gInstance->mIOThread) {
    gInstance->mIOThread->Shutdown();
  }

  CacheIndex::Shutdown();

  if (CacheObserver::ClearCacheOnShutdown()) {
    Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE2_SHUTDOWN_CLEAR_PRIVATE>
        totalTimer;
    gInstance->SyncRemoveAllCacheFiles();
  }

  gInstance = nullptr;

  return NS_OK;
}

void CacheFileIOManager::ShutdownInternal() {
  LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]"this));

  MOZ_ASSERT(mIOThread->IsCurrentThread());

  // No new handles can be created after this flag is set
  mShuttingDown = true;

  if (mTrashTimer) {
    mTrashTimer->Cancel();
    mTrashTimer = nullptr;
  }

  // close all handles and delete all associated files
  nsTArray<RefPtr<CacheFileHandle>> handles;
  mHandles.GetAllHandles(&handles);
  handles.AppendElements(mSpecialHandles);

  for (uint32_t i = 0; i < handles.Length(); i++) {
    CacheFileHandle* h = handles[i];
    h->mClosed = true;

    h->Log();

    // Close completely written files.
    MaybeReleaseNSPRHandleInternal(h);
    // Don't bother removing invalid and/or doomed files to improve
    // shutdown perfomrance.
    // Doomed files are already in the doomed directory from which
    // we never reuse files and delete the dir on next session startup.
    // Invalid files don't have metadata and thus won't load anyway
    // (hashes won't match).

    if (!h->IsSpecialFile() && !h->mIsDoomed && !h->mFileExists) {
      CacheIndex::RemoveEntry(h->Hash());
    }

    // Remove the handle from mHandles/mSpecialHandles
    if (h->IsSpecialFile()) {
      mSpecialHandles.RemoveElement(h);
    } else {
      mHandles.RemoveHandle(h);
    }

    // Pointer to the hash is no longer valid once the last handle with the
    // given hash is released. Null out the pointer so that we crash if there
    // is a bug in this code and we dereference the pointer after this point.
    if (!h->IsSpecialFile()) {
      h->mHash = nullptr;
    }
  }

  // Assert the table is empty. When we are here, no new handles can be added
  // and handles will no longer remove them self from this table and we don't
  // want to keep invalid handles here. Also, there is no lookup after this
  // point to happen.
  MOZ_ASSERT(mHandles.HandleCount() == 0);

  // Release trash directory enumerator
  if (mTrashDirEnumerator) {
    mTrashDirEnumerator->Close();
    mTrashDirEnumerator = nullptr;
  }

  if (mContextEvictor) {
    mContextEvictor->Shutdown();
    mContextEvictor = nullptr;
  }
}

// static
nsresult CacheFileIOManager::OnProfile() {
  LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance.get()));

  RefPtr<CacheFileIOManager> ioMan = gInstance;
  if (!ioMan) {
    // CacheFileIOManager::Init() failed, probably could not create the IO
    // thread, just go with it...
    return NS_ERROR_NOT_INITIALIZED;
  }

  nsresult rv;

  nsCOMPtr<nsIFile> directory;

  CacheObserver::ParentDirOverride(getter_AddRefs(directory));

#if defined(MOZ_WIDGET_ANDROID)
  nsCOMPtr<nsIFile> profilelessDirectory;
  char* cachePath = getenv("CACHE_DIRECTORY");
  if (!directory && cachePath && *cachePath) {
    rv = NS_NewNativeLocalFile(nsDependentCString(cachePath),
                               getter_AddRefs(directory));
    if (NS_SUCCEEDED(rv)) {
      // Save this directory as the profileless path.
      rv = directory->Clone(getter_AddRefs(profilelessDirectory));
      NS_ENSURE_SUCCESS(rv, rv);

      // Add profile leaf name to the directory name to distinguish
      // multiple profiles Fennec supports.
      nsCOMPtr<nsIFile> profD;
      rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                  getter_AddRefs(profD));

      nsAutoCString leafName;
      if (NS_SUCCEEDED(rv)) {
        rv = profD->GetNativeLeafName(leafName);
      }
      if (NS_SUCCEEDED(rv)) {
        rv = directory->AppendNative(leafName);
      }
      if (NS_FAILED(rv)) {
        directory = nullptr;
      }
    }
  }
#endif

  if (!directory) {
    rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
                                getter_AddRefs(directory));
  }

  if (!directory) {
    rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
                                getter_AddRefs(directory));
  }

  if (directory) {
    rv = directory->Append(u"cache2"_ns);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // All functions return a clone.
  ioMan->mCacheDirectory.swap(directory);

#if defined(MOZ_WIDGET_ANDROID)
  if (profilelessDirectory) {
    rv = profilelessDirectory->Append(u"cache2"_ns);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  ioMan->mCacheProfilelessDirectory.swap(profilelessDirectory);
#endif

  if (ioMan->mCacheDirectory) {
    CacheIndex::Init(ioMan->mCacheDirectory);
  }

  return NS_OK;
}

static bool inBackgroundTask() {
  MOZ_ASSERT(NS_IsMainThread(), "backgroundtasks are main thread only");
#if defined(MOZ_BACKGROUNDTASKS)
  nsCOMPtr<nsIBackgroundTasks> backgroundTaskService =
      do_GetService("@mozilla.org/backgroundtasks;1");
  if (!backgroundTaskService) {
    return false;
  }
  bool isBackgroundTask = false;
  backgroundTaskService->GetIsBackgroundTaskMode(&isBackgroundTask);
  return isBackgroundTask;
#else
  return false;
#endif
}

// static
nsresult CacheFileIOManager::OnDelayedStartupFinished() {
  // If we don't clear the cache at shutdown, or we don't use a
  // background task then there's no need to dispatch a cleanup task
  // at startup
  if (!CacheObserver::ClearCacheOnShutdown()) {
    return NS_OK;
  }
  if (!StaticPrefs::network_cache_shutdown_purge_in_background_task()) {
    return NS_OK;
  }

  // If this is a background task already, there's no need to
  // dispatch another one.
  if (inBackgroundTask()) {
    return NS_OK;
  }

  RefPtr<CacheFileIOManager> ioMan = gInstance;
  nsCOMPtr<nsIEventTarget> target = IOTarget();
  if (NS_WARN_IF(!ioMan || !target)) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  return target->Dispatch(
      NS_NewRunnableFunction("CacheFileIOManager::OnDelayedStartupFinished",
                             [ioMan = std::move(ioMan)] {
                               ioMan->DispatchPurgeTask(""_ns, "0"_ns,
                                                        kPurgeExtension);
                             }),
      nsIEventTarget::DISPATCH_NORMAL);
}

// static
nsresult CacheFileIOManager::OnIdleDaily() {
  // In case the background task process fails for some reason (bug 1848542)
  // We will remove the directories in the main process on a daily idle.
  if (!CacheObserver::ClearCacheOnShutdown()) {
    return NS_OK;
  }
  if (!StaticPrefs::network_cache_shutdown_purge_in_background_task()) {
    return NS_OK;
  }

  RefPtr<CacheFileIOManager> ioMan = gInstance;
  nsCOMPtr<nsIFile> parentDir;
  nsresult rv = ioMan->mCacheDirectory->GetParent(getter_AddRefs(parentDir));
  if (NS_FAILED(rv) || !parentDir) {
    return NS_OK;
  }

  NS_DispatchBackgroundTask(
      NS_NewRunnableFunction(
          "CacheFileIOManager::CheckLeftoverFolders",
          [dir = std::move(parentDir)] {
            nsCOMPtr<nsIDirectoryEnumerator> directories;
            nsresult rv = dir->GetDirectoryEntries(getter_AddRefs(directories));
            if (NS_FAILED(rv)) {
              return;
            }
            bool hasMoreElements = false;
            while (
                NS_SUCCEEDED(directories->HasMoreElements(&hasMoreElements)) &&
                hasMoreElements) {
              nsCOMPtr<nsIFile> subdir;
              rv = directories->GetNextFile(getter_AddRefs(subdir));
              if (NS_FAILED(rv) || !subdir) {
                break;
              }
              nsAutoCString leafName;
              rv = subdir->GetNativeLeafName(leafName);
              if (NS_FAILED(rv)) {
                continue;
              }
              if (leafName.Find(kPurgeExtension) != kNotFound) {
                mozilla::glean::networking::residual_cache_folder_count.Add(1);
                rv = subdir->Remove(true);
                if (NS_SUCCEEDED(rv)) {
                  mozilla::glean::networking::residual_cache_folder_removal
                      .Get("success"_ns)
                      .Add(1);
                } else {
                  mozilla::glean::networking::residual_cache_folder_removal
                      .Get("failure"_ns)
                      .Add(1);
                }
              }
            }

            return;
          }),
      NS_DISPATCH_EVENT_MAY_BLOCK);

  return NS_OK;
}

// static
already_AddRefed<nsIEventTarget> CacheFileIOManager::IOTarget() {
  nsCOMPtr<nsIEventTarget> target;
  if (gInstance && gInstance->mIOThread) {
    target = gInstance->mIOThread->Target();
  }

  return target.forget();
}

// static
already_AddRefed<CacheIOThread> CacheFileIOManager::IOThread() {
  RefPtr<CacheIOThread> thread;
  if (gInstance) {
    thread = gInstance->mIOThread;
  }

  return thread.forget();
}

// static
bool CacheFileIOManager::IsOnIOThread() {
  RefPtr<CacheFileIOManager> ioMan = gInstance;
  if (ioMan && ioMan->mIOThread) {
    return ioMan->mIOThread->IsCurrentThread();
  }

  return false;
}

// static
bool CacheFileIOManager::IsOnIOThreadOrCeased() {
  RefPtr<CacheFileIOManager> ioMan = gInstance;
  if (ioMan && ioMan->mIOThread) {
    return ioMan->mIOThread->IsCurrentThread();
  }

  // Ceased...
  return true;
}

// static
bool CacheFileIOManager::IsShutdown() {
  if (!gInstance) {
    return true;
  }
  return gInstance->mShuttingDown;
}

// static
nsresult CacheFileIOManager::ScheduleMetadataWrite(CacheFile* aFile) {
  RefPtr<CacheFileIOManager> ioMan = gInstance;
  NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);

  NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);

  RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
      ioMan, aFile, MetadataWriteScheduleEvent::SCHEDULE);
  nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
  NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
  return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
}

nsresult CacheFileIOManager::ScheduleMetadataWriteInternal(CacheFile* aFile) {
  MOZ_ASSERT(IsOnIOThreadOrCeased());

  nsresult rv;

  if (!mMetadataWritesTimer) {
    rv = NS_NewTimerWithCallback(getter_AddRefs(mMetadataWritesTimer), this,
                                 kMetadataWriteDelay, nsITimer::TYPE_ONE_SHOT);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (mScheduledMetadataWrites.IndexOf(aFile) !=
      nsTArray<RefPtr<mozilla::net::CacheFile>>::NoIndex) {
    return NS_OK;
  }

  mScheduledMetadataWrites.AppendElement(aFile);

  return NS_OK;
}

// static
nsresult CacheFileIOManager::UnscheduleMetadataWrite(CacheFile* aFile) {
  RefPtr<CacheFileIOManager> ioMan = gInstance;
  NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);

  NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);

  RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
      ioMan, aFile, MetadataWriteScheduleEvent::UNSCHEDULE);
  nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
  NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
  return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
}

void CacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile* aFile) {
  MOZ_ASSERT(IsOnIOThreadOrCeased());

  mScheduledMetadataWrites.RemoveElement(aFile);

  if (mScheduledMetadataWrites.Length() == 0 && mMetadataWritesTimer) {
    mMetadataWritesTimer->Cancel();
    mMetadataWritesTimer = nullptr;
  }
}

// static
nsresult CacheFileIOManager::ShutdownMetadataWriteScheduling() {
  RefPtr<CacheFileIOManager> ioMan = gInstance;
  NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);

  RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
      ioMan, nullptr, MetadataWriteScheduleEvent::SHUTDOWN);
  nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
  NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
  return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
}

void CacheFileIOManager::ShutdownMetadataWriteSchedulingInternal() {
  MOZ_ASSERT(IsOnIOThreadOrCeased());

  nsTArray<RefPtr<CacheFile>> files = std::move(mScheduledMetadataWrites);
  for (uint32_t i = 0; i < files.Length(); ++i) {
    CacheFile* file = files[i];
    file->WriteMetadataIfNeeded();
  }

  if (mMetadataWritesTimer) {
    mMetadataWritesTimer->Cancel();
    mMetadataWritesTimer = nullptr;
  }
}

NS_IMETHODIMP
CacheFileIOManager::Notify(nsITimer* aTimer) {
  MOZ_ASSERT(IsOnIOThreadOrCeased());
  MOZ_ASSERT(mMetadataWritesTimer == aTimer);

  mMetadataWritesTimer = nullptr;

  nsTArray<RefPtr<CacheFile>> files = std::move(mScheduledMetadataWrites);
  for (uint32_t i = 0; i < files.Length(); ++i) {
    CacheFile* file = files[i];
    file->WriteMetadataIfNeeded();
  }

  return NS_OK;
}

NS_IMETHODIMP
CacheFileIOManager::GetName(nsACString& aName) {
  aName.AssignLiteral("CacheFileIOManager");
  return NS_OK;
}

// static
nsresult CacheFileIOManager::OpenFile(const nsACString& aKey, uint32_t aFlags,
                                      CacheFileIOListener* aCallback) {
  LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]",
       PromiseFlatCString(aKey).get(), aFlags, aCallback));

  nsresult rv;
  RefPtr<CacheFileIOManager> ioMan = gInstance;

  if (!ioMan) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  bool priority = aFlags & CacheFileIOManager::PRIORITY;
  RefPtr<OpenFileEvent> ev = new OpenFileEvent(aKey, aFlags, aCallback);
  rv = ioMan->mIOThread->Dispatch(
      ev, priority ? CacheIOThread::OPEN_PRIORITY : CacheIOThread::OPEN);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash* aHash,
                                              const nsACString& aKey,
                                              uint32_t aFlags,
                                              CacheFileHandle** _retval) {
  LOG(
      ("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, "
       "key=%s, flags=%d]",
       LOGSHA1(aHash), PromiseFlatCString(aKey).get(), aFlags));

  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());

  nsresult rv;

  if (mShuttingDown) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  CacheIOThread::Cancelable cancelable(
      true /* never called for special handles */);

  if (!mTreeCreated) {
    rv = CreateCacheTree();
    if (NS_FAILED(rv)) return rv;
  }

  CacheFileHandle::PinningStatus pinning =
      aFlags & PINNED ? CacheFileHandle::PinningStatus::PINNED
                      : CacheFileHandle::PinningStatus::NON_PINNED;

  nsCOMPtr<nsIFile> file;
  rv = GetFile(aHash, getter_AddRefs(file));
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<CacheFileHandle> handle;
  mHandles.GetHandle(aHash, getter_AddRefs(handle));

  if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
    if (handle) {
      rv = DoomFileInternal(handle);
      NS_ENSURE_SUCCESS(rv, rv);
      handle = nullptr;
    }

    handle = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning);

    bool exists;
    rv = file->Exists(&exists);
    NS_ENSURE_SUCCESS(rv, rv);

    if (exists) {
      CacheIndex::RemoveEntry(aHash);

      LOG(
          ("CacheFileIOManager::OpenFileInternal() - Removing old file from "
           "disk"));
      rv = file->Remove(false);
      if (NS_FAILED(rv)) {
        NS_WARNING("Cannot remove old entry from the disk");
        LOG(
            ("CacheFileIOManager::OpenFileInternal() - Removing old file failed"
             ". [rv=0x%08" PRIx32 "]",
             static_cast<uint32_t>(rv)));
      }
    }

    CacheIndex::AddEntry(aHash);
    handle->mFile.swap(file);
    handle->mFileSize = 0;
  }

  if (handle) {
    handle.swap(*_retval);
    return NS_OK;
  }

  bool exists, evictedAsPinned = false, evictedAsNonPinned = false;
  rv = file->Exists(&exists);
  NS_ENSURE_SUCCESS(rv, rv);

  if (exists && mContextEvictor) {
    if (mContextEvictor->ContextsCount() == 0) {
      mContextEvictor = nullptr;
    } else {
      mContextEvictor->WasEvicted(aKey, file, &evictedAsPinned,
                                  &evictedAsNonPinned);
    }
  }

  if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  if (exists) {
    // For existing files we determine the pinning status later, after the
    // metadata gets parsed.
    pinning = CacheFileHandle::PinningStatus::UNKNOWN;
  }

  handle = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning);
  if (exists) {
    // If this file has been found evicted through the context file evictor
    // above for any of pinned or non-pinned state, these calls ensure we doom
    // the handle ASAP we know the real pinning state after metadta has been
    // parsed.  DoomFileInternal on the |handle| doesn't doom right now, since
    // the pinning state is unknown and we pass down a pinning restriction.
    if (evictedAsPinned) {
      rv = DoomFileInternal(handle, DOOM_WHEN_PINNED);
      MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
    }
    if (evictedAsNonPinned) {
      rv = DoomFileInternal(handle, DOOM_WHEN_NON_PINNED);
      MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
    }

    int64_t fileSize = -1;
    rv = file->GetFileSize(&fileSize);
    NS_ENSURE_SUCCESS(rv, rv);

    handle->mFileSize = fileSize;
    handle->mFileExists = true;

    CacheIndex::EnsureEntryExists(aHash);
  } else {
    handle->mFileSize = 0;

    CacheIndex::AddEntry(aHash);
  }

  handle->mFile.swap(file);
  handle.swap(*_retval);
  return NS_OK;
}

nsresult CacheFileIOManager::OpenSpecialFileInternal(
    const nsACString& aKey, uint32_t aFlags, CacheFileHandle** _retval) {
  LOG(("CacheFileIOManager::OpenSpecialFileInternal() [key=%s, flags=%d]",
       PromiseFlatCString(aKey).get(), aFlags));

  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());

  nsresult rv;

  if (mShuttingDown) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  if (!mTreeCreated) {
    rv = CreateCacheTree();
    if (NS_FAILED(rv)) return rv;
  }

  nsCOMPtr<nsIFile> file;
  rv = GetSpecialFile(aKey, getter_AddRefs(file));
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<CacheFileHandle> handle;
  for (uint32_t i = 0; i < mSpecialHandles.Length(); i++) {
    if (!mSpecialHandles[i]->IsDoomed() && mSpecialHandles[i]->Key() == aKey) {
      handle = mSpecialHandles[i];
      break;
    }
  }

  if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
    if (handle) {
      rv = DoomFileInternal(handle);
      NS_ENSURE_SUCCESS(rv, rv);
      handle = nullptr;
    }

    handle = new CacheFileHandle(aKey, aFlags & PRIORITY,
                                 CacheFileHandle::PinningStatus::NON_PINNED);
    mSpecialHandles.AppendElement(handle);

    bool exists;
    rv = file->Exists(&exists);
    NS_ENSURE_SUCCESS(rv, rv);

    if (exists) {
      LOG(
          ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from "
           "disk"));
      rv = file->Remove(false);
      if (NS_FAILED(rv)) {
        NS_WARNING("Cannot remove old entry from the disk");
        LOG(
            ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file "
             "failed. [rv=0x%08" PRIx32 "]",
             static_cast<uint32_t>(rv)));
      }
    }

    handle->mFile.swap(file);
    handle->mFileSize = 0;
  }

  if (handle) {
    handle.swap(*_retval);
    return NS_OK;
  }

  bool exists;
  rv = file->Exists(&exists);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  handle = new CacheFileHandle(aKey, aFlags & PRIORITY,
                               CacheFileHandle::PinningStatus::NON_PINNED);
  mSpecialHandles.AppendElement(handle);

  if (exists) {
    int64_t fileSize = -1;
    rv = file->GetFileSize(&fileSize);
    NS_ENSURE_SUCCESS(rv, rv);

    handle->mFileSize = fileSize;
    handle->mFileExists = true;
  } else {
    handle->mFileSize = 0;
  }

  handle->mFile.swap(file);
  handle.swap(*_retval);
  return NS_OK;
}

void CacheFileIOManager::CloseHandleInternal(CacheFileHandle* aHandle) {
  nsresult rv;
  LOG(("CacheFileIOManager::CloseHandleInternal() [handle=%p]", aHandle));

  MOZ_ASSERT(!aHandle->IsClosed());

  aHandle->Log();

  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());

  CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());

  // Maybe close file handle (can be legally bypassed after shutdown)
  rv = MaybeReleaseNSPRHandleInternal(aHandle);

  // Delete the file if the entry was doomed or invalid and
  // filedesc properly closed
  if ((aHandle->mIsDoomed || aHandle->mInvalid) && aHandle->mFileExists &&
      NS_SUCCEEDED(rv)) {
    LOG(
        ("CacheFileIOManager::CloseHandleInternal() - Removing file from "
         "disk"));

    rv = aHandle->mFile->Remove(false);
    if (NS_SUCCEEDED(rv)) {
      aHandle->mFileExists = false;
    } else {
      LOG((" failed to remove the file [rv=0x%08" PRIx32 "]",
           static_cast<uint32_t>(rv)));
    }
  }

  if (!aHandle->IsSpecialFile() && !aHandle->mIsDoomed &&
      (aHandle->mInvalid || !aHandle->mFileExists)) {
    CacheIndex::RemoveEntry(aHandle->Hash());
  }

  // Don't remove handles after shutdown
  if (!mShuttingDown) {
    if (aHandle->IsSpecialFile()) {
      mSpecialHandles.RemoveElement(aHandle);
    } else {
      mHandles.RemoveHandle(aHandle);
    }
  }
}

// static
nsresult CacheFileIOManager::Read(CacheFileHandle* aHandle, int64_t aOffset,
                                  char* aBuf, int32_t aCount,
                                  CacheFileIOListener* aCallback) {
  LOG(("CacheFileIOManager::Read() [handle=%p, offset=%" PRId64 ", count=%d, "
       "listener=%p]",
       aHandle, aOffset, aCount, aCallback));

  if (CacheObserver::ShuttingDown()) {
    LOG((" no reads after shutdown"));
    return NS_ERROR_NOT_INITIALIZED;
  }

  nsresult rv;
  RefPtr<CacheFileIOManager> ioMan = gInstance;

  if (aHandle->IsClosed() || !ioMan) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  RefPtr<ReadEvent> ev =
      new ReadEvent(aHandle, aOffset, aBuf, aCount, aCallback);
  rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
                                          ? CacheIOThread::READ_PRIORITY
                                          : CacheIOThread::READ);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult CacheFileIOManager::ReadInternal(CacheFileHandle* aHandle,
                                          int64_t aOffset, char* aBuf,
                                          int32_t aCount) {
  LOG(("CacheFileIOManager::ReadInternal() [handle=%p, offset=%" PRId64
       ", count=%d]",
       aHandle, aOffset, aCount));

  nsresult rv;

  if (CacheObserver::ShuttingDown()) {
    LOG((" no reads after shutdown"));
    return NS_ERROR_NOT_INITIALIZED;
  }

  if (!aHandle->mFileExists) {
    NS_WARNING("Trying to read from non-existent file");
    return NS_ERROR_NOT_AVAILABLE;
  }

  CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());

  if (!aHandle->mFD) {
    rv = OpenNSPRHandle(aHandle);
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
    NSPRHandleUsed(aHandle);
  }

  // Check again, OpenNSPRHandle could figure out the file was gone.
  if (!aHandle->mFileExists) {
    NS_WARNING("Trying to read from non-existent file");
    return NS_ERROR_NOT_AVAILABLE;
  }

  int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
  if (offset == -1) {
    return NS_ERROR_FAILURE;
  }

  int32_t bytesRead = PR_Read(aHandle->mFD, aBuf, aCount);
  if (bytesRead != aCount) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

// static
nsresult CacheFileIOManager::Write(CacheFileHandle* aHandle, int64_t aOffset,
                                   const char* aBuf, int32_t aCount,
                                   bool aValidate, bool aTruncate,
                                   CacheFileIOListener* aCallback) {
  LOG(("CacheFileIOManager::Write() [handle=%p, offset=%" PRId64 ", count=%d, "
       "validate=%d, truncate=%d, listener=%p]",
       aHandle, aOffset, aCount, aValidate, aTruncate, aCallback));

  MOZ_ASSERT(aCallback);

  RefPtr<CacheFileIOManager> ioMan = gInstance;

  if (aHandle->IsClosed() || aCallback->IsKilled() || !ioMan) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  RefPtr<WriteEvent> ev = new WriteEvent(aHandle, aOffset, aBuf, aCount,
                                         aValidate, aTruncate, aCallback);
  return ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
                                            ? CacheIOThread::WRITE_PRIORITY
                                            : CacheIOThread::WRITE);
}

// static
nsresult CacheFileIOManager::WriteWithoutCallback(CacheFileHandle* aHandle,
                                                  int64_t aOffset, char* aBuf,
                                                  int32_t aCount,
                                                  bool aValidate,
                                                  bool aTruncate) {
  LOG(("CacheFileIOManager::WriteWithoutCallback() [handle=%p, offset=%" PRId64
       ", count=%d, "
       "validate=%d, truncate=%d]",
       aHandle, aOffset, aCount, aValidate, aTruncate));

  RefPtr<CacheFileIOManager> ioMan = gInstance;

  if (aHandle->IsClosed() || !ioMan) {
    // When no callback is provided, CacheFileIOManager is responsible for
    // releasing the buffer. We must release it even in case of failure.
    free(aBuf);
    return NS_ERROR_NOT_INITIALIZED;
  }

  RefPtr<WriteEvent> ev = new WriteEvent(aHandle, aOffset, aBuf, aCount,
                                         aValidate, aTruncate, nullptr);
  return ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
                                            ? CacheIOThread::WRITE_PRIORITY
                                            : CacheIOThread::WRITE);
}

static nsresult TruncFile(PRFileDesc* aFD, int64_t aEOF) {
#if defined(XP_UNIX)
  if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) {
    NS_ERROR("ftruncate failed");
    return NS_ERROR_FAILURE;
  }
#elif defined(XP_WIN)
  int64_t cnt = PR_Seek64(aFD, aEOF, PR_SEEK_SET);
  if (cnt == -1) {
    return NS_ERROR_FAILURE;
  }
  if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))) {
    NS_ERROR("SetEndOfFile failed");
    return NS_ERROR_FAILURE;
  }
#else
  MOZ_ASSERT(false"Not implemented!");
  return NS_ERROR_NOT_IMPLEMENTED;
#endif

  return NS_OK;
}

nsresult CacheFileIOManager::WriteInternal(CacheFileHandle* aHandle,
                                           int64_t aOffset, const char* aBuf,
                                           int32_t aCount, bool aValidate,
                                           bool aTruncate) {
  LOG(("CacheFileIOManager::WriteInternal() [handle=%p, offset=%" PRId64
       ", count=%d, "
       "validate=%d, truncate=%d]",
       aHandle, aOffset, aCount, aValidate, aTruncate));

  nsresult rv;

  if (aHandle->mKilled) {
    LOG((" handle already killed, nothing written"));
    return NS_OK;
  }

  if (CacheObserver::ShuttingDown() && (!aValidate || !aHandle->mFD)) {
    aHandle->mKilled = true;
    LOG((" killing the handle, nothing written"));
    return NS_OK;
  }

  if (CacheObserver::IsPastShutdownIOLag()) {
    LOG((" past the shutdown I/O lag, nothing written"));
    // Pretend the write has succeeded, otherwise upper layers will doom
    // the file and we end up with I/O anyway.
    return NS_OK;
  }

  CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());

  if (!aHandle->mFileExists) {
    rv = CreateFile(aHandle);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (!aHandle->mFD) {
    rv = OpenNSPRHandle(aHandle);
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
    NSPRHandleUsed(aHandle);
  }

  // Check again, OpenNSPRHandle could figure out the file was gone.
  if (!aHandle->mFileExists) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  // When this operation would increase cache size, check whether the cache size
  // reached the hard limit and whether it would cause critical low disk space.
  if (aHandle->mFileSize < aOffset + aCount) {
    if (mOverLimitEvicting && mCacheSizeOnHardLimit) {
      LOG(
          ("CacheFileIOManager::WriteInternal() - failing because cache size "
           "reached hard limit!"));
      return NS_ERROR_FILE_NO_DEVICE_SPACE;
    }

    int64_t freeSpace;
    rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      freeSpace = -1;
      LOG(
          ("CacheFileIOManager::WriteInternal() - GetDiskSpaceAvailable() "
           "failed! [rv=0x%08" PRIx32 "]",
           static_cast<uint32_t>(rv)));
    } else {
      freeSpace >>= 10;  // bytes to kilobytes
      uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit();
      if (freeSpace - aOffset - aCount + aHandle->mFileSize < limit) {
        LOG(
            ("CacheFileIOManager::WriteInternal() - Low free space, refusing "
             "to write! [freeSpace=%" PRId64 "kB, limit=%ukB]",
             freeSpace, limit));
        return NS_ERROR_FILE_NO_DEVICE_SPACE;
      }
    }
  }

  // Write invalidates the entry by default
  aHandle->mInvalid = true;

  int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
  if (offset == -1) {
    return NS_ERROR_FAILURE;
  }

  int32_t bytesWritten = PR_Write(aHandle->mFD, aBuf, aCount);

  if (bytesWritten != -1) {
    uint32_t oldSizeInK = aHandle->FileSizeInK();
    int64_t writeEnd = aOffset + bytesWritten;

    if (aTruncate) {
      rv = TruncFile(aHandle->mFD, writeEnd);
      NS_ENSURE_SUCCESS(rv, rv);

      aHandle->mFileSize = writeEnd;
    } else {
      if (aHandle->mFileSize < writeEnd) {
        aHandle->mFileSize = writeEnd;
      }
    }

    uint32_t newSizeInK = aHandle->FileSizeInK();

    if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() &&
        !aHandle->IsSpecialFile()) {
      CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, nullptr,
                              nullptr, nullptr, &newSizeInK);

      if (oldSizeInK < newSizeInK) {
        EvictIfOverLimitInternal();
      }
    }

    CacheIndex::UpdateTotalBytesWritten(bytesWritten);
  }

  if (bytesWritten != aCount) {
    return NS_ERROR_FAILURE;
  }

  // Write was successful and this write validates the entry (i.e. metadata)
  if (aValidate) {
    aHandle->mInvalid = false;
  }

  return NS_OK;
}

// static
nsresult CacheFileIOManager::DoomFile(CacheFileHandle* aHandle,
                                      CacheFileIOListener* aCallback) {
  LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]", aHandle,
       aCallback));

  nsresult rv;
  RefPtr<CacheFileIOManager> ioMan = gInstance;

  if (aHandle->IsClosed() || !ioMan) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  RefPtr<DoomFileEvent> ev = new DoomFileEvent(aHandle, aCallback);
  rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
                                          ? CacheIOThread::OPEN_PRIORITY
                                          : CacheIOThread::OPEN);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult CacheFileIOManager::DoomFileInternal(
    CacheFileHandle* aHandle, PinningDoomRestriction aPinningDoomRestriction) {
  LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle));
  aHandle->Log();

  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());

  nsresult rv;

  if (aHandle->IsDoomed()) {
    return NS_OK;
  }

  CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());

  if (aPinningDoomRestriction > NO_RESTRICTION) {
    switch (aHandle->mPinning) {
      case CacheFileHandle::PinningStatus::NON_PINNED:
        if (MOZ_LIKELY(aPinningDoomRestriction != DOOM_WHEN_NON_PINNED)) {
          LOG((" not dooming, it's a non-pinned handle"));
          return NS_OK;
        }
        // Doom now
        break;

      case CacheFileHandle::PinningStatus::PINNED:
        if (MOZ_UNLIKELY(aPinningDoomRestriction != DOOM_WHEN_PINNED)) {
          LOG((" not dooming, it's a pinned handle"));
          return NS_OK;
        }
        // Doom now
        break;

      case CacheFileHandle::PinningStatus::UNKNOWN:
        if (MOZ_LIKELY(aPinningDoomRestriction == DOOM_WHEN_NON_PINNED)) {
          LOG((" doom when non-pinned set"));
          aHandle->mDoomWhenFoundNonPinned = true;
        } else if (MOZ_UNLIKELY(aPinningDoomRestriction == DOOM_WHEN_PINNED)) {
          LOG((" doom when pinned set"));
          aHandle->mDoomWhenFoundPinned = true;
        }

        LOG((" pinning status not known, deferring doom decision"));
        return NS_OK;
    }
  }

  if (aHandle->mFileExists) {
    // we need to move the current file to the doomed directory
--> --------------------

--> maximum size reached

--> --------------------

97%


¤ Dauer der Verarbeitung: 0.28 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.