/* 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]" ,
this , bool (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
--> --------------------
quality 97%
¤ Dauer der Verarbeitung: 0.42 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland