michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "CacheLog.h" michael@0: #include "CacheFileIOManager.h" michael@0: michael@0: #include "../cache/nsCacheUtils.h" michael@0: #include "CacheHashUtils.h" michael@0: #include "CacheStorageService.h" michael@0: #include "CacheIndex.h" michael@0: #include "CacheFileUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "CacheFile.h" michael@0: #include "CacheObserver.h" michael@0: #include "nsIFile.h" michael@0: #include "CacheFileContextEvictor.h" michael@0: #include "nsITimer.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsIDirectoryEnumerator.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsISizeOf.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: #include "private/pprio.h" michael@0: #include "mozilla/VisualEventTracer.h" michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: // include files for ftruncate (or equivalent) michael@0: #if defined(XP_UNIX) michael@0: #include michael@0: #elif defined(XP_WIN) michael@0: #include michael@0: #undef CreateFile michael@0: #undef CREATE_NEW michael@0: #else michael@0: // XXX add necessary include file for ftruncate (or equivalent) michael@0: #endif michael@0: michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: #define kOpenHandlesLimit 64 michael@0: #define kMetadataWriteDelay 5000 michael@0: #define kRemoveTrashStartDelay 60000 // in milliseconds michael@0: #define kSmartSizeUpdateInterval 60000 // in milliseconds michael@0: michael@0: #ifdef ANDROID michael@0: const uint32_t kMaxCacheSizeKB = 200*1024; // 200 MB michael@0: #else michael@0: const uint32_t kMaxCacheSizeKB = 350*1024; // 350 MB michael@0: #endif michael@0: michael@0: bool michael@0: CacheFileHandle::DispatchRelease() michael@0: { michael@0: if (CacheFileIOManager::IsOnIOThreadOrCeased()) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr ioTarget = CacheFileIOManager::IOTarget(); michael@0: if (!ioTarget) { michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr > event = michael@0: NS_NewNonOwningRunnableMethod(this, &CacheFileHandle::Release); michael@0: nsresult rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(CacheFileHandle) michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: CacheFileHandle::Release() michael@0: { michael@0: nsrefcnt count = mRefCnt - 1; michael@0: if (DispatchRelease()) { michael@0: // Redispatched to the IO thread. michael@0: return count; michael@0: } michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: michael@0: LOG(("CacheFileHandle::Release() [this=%p, refcnt=%d]", this, mRefCnt.get())); michael@0: NS_PRECONDITION(0 != mRefCnt, "dup release"); michael@0: count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, "CacheFileHandle"); michael@0: michael@0: if (0 == count) { michael@0: mRefCnt = 1; michael@0: delete (this); michael@0: return 0; michael@0: } michael@0: michael@0: return count; michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(CacheFileHandle) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END_THREADSAFE michael@0: michael@0: CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority) michael@0: : mHash(aHash) michael@0: , mIsDoomed(false) michael@0: , mPriority(aPriority) michael@0: , mClosed(false) michael@0: , mInvalid(false) michael@0: , mFileExists(false) michael@0: , mFileSize(-1) michael@0: , mFD(nullptr) michael@0: { michael@0: LOG(("CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]" michael@0: , this, LOGSHA1(aHash))); michael@0: } michael@0: michael@0: CacheFileHandle::CacheFileHandle(const nsACString &aKey, bool aPriority) michael@0: : mHash(nullptr) michael@0: , mIsDoomed(false) michael@0: , mPriority(aPriority) michael@0: , mClosed(false) michael@0: , mInvalid(false) michael@0: , mFileExists(false) michael@0: , mFileSize(-1) michael@0: , mFD(nullptr) michael@0: , mKey(aKey) michael@0: { michael@0: LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]", this, michael@0: PromiseFlatCString(aKey).get())); michael@0: } michael@0: michael@0: CacheFileHandle::~CacheFileHandle() michael@0: { michael@0: LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this)); michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: michael@0: nsRefPtr ioMan = CacheFileIOManager::gInstance; michael@0: if (ioMan) { michael@0: ioMan->CloseHandleInternal(this); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheFileHandle::Log() michael@0: { michael@0: nsAutoCString leafName; michael@0: if (mFile) { michael@0: mFile->GetNativeLeafName(leafName); michael@0: } michael@0: michael@0: if (!mHash) { michael@0: // special file michael@0: LOG(("CacheFileHandle::Log() [this=%p, hash=nullptr, isDoomed=%d, " michael@0: "priority=%d, closed=%d, invalid=%d, " michael@0: "fileExists=%d, fileSize=%lld, leafName=%s, key=%s]", michael@0: this, mIsDoomed, mPriority, mClosed, mInvalid, michael@0: mFileExists, mFileSize, leafName.get(), mKey.get())); michael@0: } else { michael@0: LOG(("CacheFileHandle::Log() [this=%p, hash=%08x%08x%08x%08x%08x, " michael@0: "isDoomed=%d, priority=%d, closed=%d, invalid=%d, " michael@0: "fileExists=%d, fileSize=%lld, leafName=%s, key=%s]", michael@0: this, LOGSHA1(mHash), mIsDoomed, mPriority, mClosed, michael@0: mInvalid, mFileExists, mFileSize, leafName.get(), mKey.get())); michael@0: } michael@0: } michael@0: michael@0: uint32_t michael@0: CacheFileHandle::FileSizeInK() const michael@0: { michael@0: MOZ_ASSERT(mFileSize != -1); michael@0: uint64_t size64 = mFileSize; michael@0: michael@0: size64 += 0x3FF; michael@0: size64 >>= 10; michael@0: michael@0: uint32_t size; michael@0: if (size64 >> 32) { michael@0: NS_WARNING("CacheFileHandle::FileSizeInK() - FileSize is too large, " michael@0: "truncating to PR_UINT32_MAX"); michael@0: size = PR_UINT32_MAX; michael@0: } else { michael@0: size = static_cast(size64); michael@0: } michael@0: michael@0: return size; michael@0: } michael@0: michael@0: // Memory reporting michael@0: michael@0: size_t michael@0: CacheFileHandle::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: size_t n = 0; michael@0: nsCOMPtr sizeOf; michael@0: michael@0: sizeOf = do_QueryInterface(mFile); michael@0: if (sizeOf) { michael@0: n += sizeOf->SizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: n += mallocSizeOf(mFD); michael@0: n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf); michael@0: return n; michael@0: } michael@0: michael@0: size_t michael@0: CacheFileHandle::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: /****************************************************************************** michael@0: * CacheFileHandles::HandleHashKey michael@0: *****************************************************************************/ michael@0: michael@0: void michael@0: CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle* aHandle) michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: michael@0: mHandles.InsertElementAt(0, aHandle); michael@0: } michael@0: michael@0: void michael@0: CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle* aHandle) michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: michael@0: DebugOnly found; michael@0: found = mHandles.RemoveElement(aHandle); michael@0: MOZ_ASSERT(found); michael@0: } michael@0: michael@0: already_AddRefed michael@0: CacheFileHandles::HandleHashKey::GetNewestHandle() michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: michael@0: nsRefPtr handle; michael@0: if (mHandles.Length()) { michael@0: handle = mHandles[0]; michael@0: } michael@0: michael@0: return handle.forget(); michael@0: } michael@0: michael@0: void michael@0: CacheFileHandles::HandleHashKey::GetHandles(nsTArray > &aResult) michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: michael@0: for (uint32_t i = 0; i < mHandles.Length(); ++i) { michael@0: CacheFileHandle* handle = mHandles[i]; michael@0: aResult.AppendElement(handle); michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: michael@0: void michael@0: CacheFileHandles::HandleHashKey::AssertHandlesState() michael@0: { michael@0: for (uint32_t i = 0; i < mHandles.Length(); ++i) { michael@0: CacheFileHandle* handle = mHandles[i]; michael@0: MOZ_ASSERT(handle->IsDoomed()); michael@0: } michael@0: } michael@0: michael@0: #endif michael@0: michael@0: size_t michael@0: CacheFileHandles::HandleHashKey::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); michael@0: michael@0: size_t n = 0; michael@0: n += mallocSizeOf(mHash); michael@0: for (uint32_t i = 0; i < mHandles.Length(); ++i) { michael@0: n += mHandles[i]->SizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: return n; michael@0: } michael@0: michael@0: /****************************************************************************** michael@0: * CacheFileHandles michael@0: *****************************************************************************/ michael@0: michael@0: CacheFileHandles::CacheFileHandles() michael@0: { michael@0: LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this)); michael@0: MOZ_COUNT_CTOR(CacheFileHandles); michael@0: } michael@0: michael@0: CacheFileHandles::~CacheFileHandles() michael@0: { michael@0: LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this)); michael@0: MOZ_COUNT_DTOR(CacheFileHandles); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash, michael@0: bool aReturnDoomed, michael@0: CacheFileHandle **_retval) michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: MOZ_ASSERT(aHash); michael@0: michael@0: #ifdef DEBUG_HANDLES michael@0: LOG(("CacheFileHandles::GetHandle() [hash=%08x%08x%08x%08x%08x]", michael@0: LOGSHA1(aHash))); michael@0: #endif michael@0: michael@0: // find hash entry for key michael@0: HandleHashKey *entry = mTable.GetEntry(*aHash); michael@0: if (!entry) { michael@0: LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " michael@0: "no handle entries found", LOGSHA1(aHash))); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: #ifdef DEBUG_HANDLES michael@0: Log(entry); michael@0: #endif michael@0: michael@0: // Check if the entry is doomed michael@0: nsRefPtr handle = entry->GetNewestHandle(); michael@0: if (!handle) { michael@0: LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " michael@0: "no handle found %p, entry %p", LOGSHA1(aHash), handle.get(), entry)); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (handle->IsDoomed()) { michael@0: LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " michael@0: "found doomed handle %p, entry %p", LOGSHA1(aHash), handle.get(), entry)); michael@0: michael@0: // If the consumer doesn't want doomed handles, exit with NOT_AVAIL. michael@0: if (!aReturnDoomed) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } else { michael@0: LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " michael@0: "found handle %p, entry %p", LOGSHA1(aHash), handle.get(), entry)); michael@0: } michael@0: michael@0: handle.forget(_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash, michael@0: bool aPriority, michael@0: CacheFileHandle **_retval) michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: MOZ_ASSERT(aHash); michael@0: michael@0: #ifdef DEBUG_HANDLES michael@0: LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash))); michael@0: #endif michael@0: michael@0: // find hash entry for key michael@0: HandleHashKey *entry = mTable.PutEntry(*aHash); michael@0: michael@0: #ifdef DEBUG_HANDLES michael@0: Log(entry); michael@0: #endif michael@0: michael@0: #ifdef DEBUG michael@0: entry->AssertHandlesState(); michael@0: #endif michael@0: michael@0: nsRefPtr handle = new CacheFileHandle(entry->Hash(), aPriority); michael@0: entry->AddHandle(handle); michael@0: michael@0: LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x " michael@0: "created new handle %p, entry=%p", LOGSHA1(aHash), handle.get(), entry)); michael@0: michael@0: handle.forget(_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CacheFileHandles::RemoveHandle(CacheFileHandle *aHandle) michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: MOZ_ASSERT(aHandle); michael@0: michael@0: if (!aHandle) { michael@0: return; michael@0: } michael@0: michael@0: #ifdef DEBUG_HANDLES michael@0: LOG(("CacheFileHandles::RemoveHandle() [handle=%p, hash=%08x%08x%08x%08x%08x]" michael@0: , aHandle, LOGSHA1(aHandle->Hash()))); michael@0: #endif michael@0: michael@0: // find hash entry for key michael@0: HandleHashKey *entry = mTable.GetEntry(*aHandle->Hash()); michael@0: if (!entry) { michael@0: MOZ_ASSERT(CacheFileIOManager::IsShutdown(), michael@0: "Should find entry when removing a handle before shutdown"); michael@0: michael@0: LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x " michael@0: "no entries found", LOGSHA1(aHandle->Hash()))); michael@0: return; michael@0: } michael@0: michael@0: #ifdef DEBUG_HANDLES michael@0: Log(entry); michael@0: #endif michael@0: michael@0: LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x " michael@0: "removing handle %p", LOGSHA1(entry->Hash()), aHandle)); michael@0: entry->RemoveHandle(aHandle); michael@0: michael@0: if (entry->IsEmpty()) { michael@0: LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x " michael@0: "list is empty, removing entry %p", LOGSHA1(entry->Hash()), entry)); michael@0: mTable.RemoveEntry(*entry->Hash()); michael@0: } michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: GetAllHandlesEnum(CacheFileHandles::HandleHashKey* aEntry, void *aClosure) michael@0: { michael@0: nsTArray > *array = michael@0: static_cast > *>(aClosure); michael@0: michael@0: aEntry->GetHandles(*array); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: CacheFileHandles::GetAllHandles(nsTArray > *_retval) michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: mTable.EnumerateEntries(&GetAllHandlesEnum, _retval); michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: GetActiveHandlesEnum(CacheFileHandles::HandleHashKey* aEntry, void *aClosure) michael@0: { michael@0: nsTArray > *array = michael@0: static_cast > *>(aClosure); michael@0: michael@0: nsRefPtr handle = aEntry->GetNewestHandle(); michael@0: MOZ_ASSERT(handle); michael@0: michael@0: if (!handle->IsDoomed()) { michael@0: array->AppendElement(handle); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: CacheFileHandles::GetActiveHandles( michael@0: nsTArray > *_retval) michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: mTable.EnumerateEntries(&GetActiveHandlesEnum, _retval); michael@0: } michael@0: michael@0: void michael@0: CacheFileHandles::ClearAll() michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: mTable.Clear(); michael@0: } michael@0: michael@0: uint32_t michael@0: CacheFileHandles::HandleCount() michael@0: { michael@0: return mTable.Count(); michael@0: } michael@0: michael@0: #ifdef DEBUG_HANDLES michael@0: void michael@0: CacheFileHandles::Log(CacheFileHandlesEntry *entry) michael@0: { michael@0: LOG(("CacheFileHandles::Log() BEGIN [entry=%p]", entry)); michael@0: michael@0: nsTArray > array; michael@0: aEntry->GetHandles(array); michael@0: michael@0: for (uint32_t i = 0; i < array.Length(); ++i) { michael@0: CacheFileHandle *handle = array[i]; michael@0: handle->Log(); michael@0: } michael@0: michael@0: LOG(("CacheFileHandles::Log() END [entry=%p]", entry)); michael@0: } michael@0: #endif michael@0: michael@0: // Memory reporting michael@0: michael@0: namespace { // anon michael@0: michael@0: size_t michael@0: CollectHandlesMemory(CacheFileHandles::HandleHashKey* key, michael@0: mozilla::MallocSizeOf mallocSizeOf, michael@0: void *arg) michael@0: { michael@0: return key->SizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: } // anon michael@0: michael@0: size_t michael@0: CacheFileHandles::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); michael@0: michael@0: return mTable.SizeOfExcludingThis(&CollectHandlesMemory, mallocSizeOf); michael@0: } michael@0: michael@0: // Events michael@0: michael@0: class ShutdownEvent : public nsRunnable { michael@0: public: michael@0: ShutdownEvent(mozilla::Mutex *aLock, mozilla::CondVar *aCondVar) michael@0: : mLock(aLock) michael@0: , mCondVar(aCondVar) michael@0: { michael@0: MOZ_COUNT_CTOR(ShutdownEvent); michael@0: } michael@0: michael@0: ~ShutdownEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(ShutdownEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MutexAutoLock lock(*mLock); michael@0: michael@0: CacheFileIOManager::gInstance->ShutdownInternal(); michael@0: michael@0: mCondVar->Notify(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: mozilla::Mutex *mLock; michael@0: mozilla::CondVar *mCondVar; michael@0: }; michael@0: michael@0: class OpenFileEvent : public nsRunnable { michael@0: public: michael@0: OpenFileEvent(const nsACString &aKey, michael@0: uint32_t aFlags, bool aResultOnAnyThread, michael@0: CacheFileIOListener *aCallback) michael@0: : mFlags(aFlags) michael@0: , mResultOnAnyThread(aResultOnAnyThread) michael@0: , mCallback(aCallback) michael@0: , mRV(NS_ERROR_FAILURE) michael@0: , mKey(aKey) michael@0: { michael@0: MOZ_COUNT_CTOR(OpenFileEvent); michael@0: michael@0: if (!aResultOnAnyThread) { michael@0: mTarget = static_cast(NS_GetCurrentThread()); michael@0: MOZ_ASSERT(mTarget); michael@0: } michael@0: michael@0: mIOMan = CacheFileIOManager::gInstance; michael@0: michael@0: MOZ_EVENT_TRACER_NAME_OBJECT(static_cast(this), aKey.BeginReading()); michael@0: MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::open-background"); michael@0: } michael@0: michael@0: ~OpenFileEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(OpenFileEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mResultOnAnyThread || mTarget) { michael@0: mRV = NS_OK; michael@0: michael@0: if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) { michael@0: SHA1Sum sum; michael@0: sum.update(mKey.BeginReading(), mKey.Length()); michael@0: sum.finish(mHash); michael@0: } michael@0: michael@0: MOZ_EVENT_TRACER_EXEC(static_cast(this), michael@0: "net::cache::open-background"); michael@0: if (NS_SUCCEEDED(mRV)) { michael@0: if (!mIOMan) { michael@0: mRV = NS_ERROR_NOT_INITIALIZED; michael@0: } else { michael@0: if (mFlags & CacheFileIOManager::SPECIAL_FILE) { michael@0: mRV = mIOMan->OpenSpecialFileInternal(mKey, mFlags, michael@0: getter_AddRefs(mHandle)); michael@0: } else { michael@0: mRV = mIOMan->OpenFileInternal(&mHash, mKey, mFlags, michael@0: getter_AddRefs(mHandle)); michael@0: } michael@0: mIOMan = nullptr; michael@0: if (mHandle) { michael@0: MOZ_EVENT_TRACER_NAME_OBJECT(mHandle.get(), mKey.get()); michael@0: if (mHandle->Key().IsEmpty()) { michael@0: mHandle->Key() = mKey; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::open-background"); michael@0: michael@0: MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::open-result"); michael@0: michael@0: if (mTarget) { michael@0: nsCOMPtr target; michael@0: mTarget.swap(target); michael@0: return target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: michael@0: if (!mTarget) { michael@0: MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::open-result"); michael@0: mCallback->OnFileOpened(mHandle, mRV); michael@0: MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::open-result"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: SHA1Sum::Hash mHash; michael@0: uint32_t mFlags; michael@0: bool mResultOnAnyThread; michael@0: nsCOMPtr mCallback; michael@0: nsCOMPtr mTarget; michael@0: nsRefPtr mIOMan; michael@0: nsRefPtr mHandle; michael@0: nsresult mRV; michael@0: nsCString mKey; michael@0: }; michael@0: michael@0: class ReadEvent : public nsRunnable { michael@0: public: michael@0: ReadEvent(CacheFileHandle *aHandle, int64_t aOffset, char *aBuf, michael@0: int32_t aCount, bool aResultOnAnyThread, CacheFileIOListener *aCallback) michael@0: : mHandle(aHandle) michael@0: , mOffset(aOffset) michael@0: , mBuf(aBuf) michael@0: , mCount(aCount) michael@0: , mResultOnAnyThread(aResultOnAnyThread) michael@0: , mCallback(aCallback) michael@0: , mRV(NS_ERROR_FAILURE) michael@0: { michael@0: MOZ_COUNT_CTOR(ReadEvent); michael@0: michael@0: if (!aResultOnAnyThread) { michael@0: mTarget = static_cast(NS_GetCurrentThread()); michael@0: } michael@0: michael@0: MOZ_EVENT_TRACER_NAME_OBJECT(static_cast(this), aHandle->Key().get()); michael@0: MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::read-background"); michael@0: } michael@0: michael@0: ~ReadEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(ReadEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mResultOnAnyThread || mTarget) { michael@0: MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::read-background"); michael@0: if (mHandle->IsClosed()) { michael@0: mRV = NS_ERROR_NOT_INITIALIZED; michael@0: } else { michael@0: mRV = CacheFileIOManager::gInstance->ReadInternal( michael@0: mHandle, mOffset, mBuf, mCount); michael@0: } michael@0: MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::read-background"); michael@0: michael@0: MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::read-result"); michael@0: michael@0: if (mTarget) { michael@0: nsCOMPtr target; michael@0: mTarget.swap(target); michael@0: return target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: michael@0: if (!mTarget && mCallback) { michael@0: MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::read-result"); michael@0: mCallback->OnDataRead(mHandle, mBuf, mRV); michael@0: MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::read-result"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: nsRefPtr mHandle; michael@0: int64_t mOffset; michael@0: char *mBuf; michael@0: int32_t mCount; michael@0: bool mResultOnAnyThread; michael@0: nsCOMPtr mCallback; michael@0: nsCOMPtr mTarget; michael@0: nsresult mRV; michael@0: }; michael@0: michael@0: class WriteEvent : public nsRunnable { michael@0: public: michael@0: WriteEvent(CacheFileHandle *aHandle, int64_t aOffset, const char *aBuf, michael@0: int32_t aCount, bool aValidate, CacheFileIOListener *aCallback) michael@0: : mHandle(aHandle) michael@0: , mOffset(aOffset) michael@0: , mBuf(aBuf) michael@0: , mCount(aCount) michael@0: , mValidate(aValidate) michael@0: , mCallback(aCallback) michael@0: , mRV(NS_ERROR_FAILURE) michael@0: { michael@0: MOZ_COUNT_CTOR(WriteEvent); michael@0: mTarget = static_cast(NS_GetCurrentThread()); michael@0: michael@0: MOZ_EVENT_TRACER_NAME_OBJECT(static_cast(this), aHandle->Key().get()); michael@0: MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::write-background"); michael@0: } michael@0: michael@0: ~WriteEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(WriteEvent); michael@0: michael@0: if (!mCallback && mBuf) { michael@0: free(const_cast(mBuf)); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mTarget) { michael@0: MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::write-background"); michael@0: if (mHandle->IsClosed()) { michael@0: mRV = NS_ERROR_NOT_INITIALIZED; michael@0: } else { michael@0: mRV = CacheFileIOManager::gInstance->WriteInternal( michael@0: mHandle, mOffset, mBuf, mCount, mValidate); michael@0: } michael@0: MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::write-background"); michael@0: michael@0: MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::write-result"); michael@0: nsCOMPtr target; michael@0: mTarget.swap(target); michael@0: target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); michael@0: } else { michael@0: MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::write-result"); michael@0: if (mCallback) { michael@0: mCallback->OnDataWritten(mHandle, mBuf, mRV); michael@0: } else { michael@0: free(const_cast(mBuf)); michael@0: mBuf = nullptr; michael@0: } michael@0: MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::write-result"); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: nsRefPtr mHandle; michael@0: int64_t mOffset; michael@0: const char *mBuf; michael@0: int32_t mCount; michael@0: bool mValidate; michael@0: nsCOMPtr mCallback; michael@0: nsCOMPtr mTarget; michael@0: nsresult mRV; michael@0: }; michael@0: michael@0: class DoomFileEvent : public nsRunnable { michael@0: public: michael@0: DoomFileEvent(CacheFileHandle *aHandle, michael@0: CacheFileIOListener *aCallback) michael@0: : mCallback(aCallback) michael@0: , mHandle(aHandle) michael@0: , mRV(NS_ERROR_FAILURE) michael@0: { michael@0: MOZ_COUNT_CTOR(DoomFileEvent); michael@0: mTarget = static_cast(NS_GetCurrentThread()); michael@0: michael@0: MOZ_EVENT_TRACER_NAME_OBJECT(static_cast(this), aHandle->Key().get()); michael@0: MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::doom-background"); michael@0: } michael@0: michael@0: ~DoomFileEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(DoomFileEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mTarget) { michael@0: MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::doom-background"); michael@0: if (mHandle->IsClosed()) { michael@0: mRV = NS_ERROR_NOT_INITIALIZED; michael@0: } else { michael@0: mRV = CacheFileIOManager::gInstance->DoomFileInternal(mHandle); michael@0: } michael@0: MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::doom-background"); michael@0: michael@0: MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::doom-result"); michael@0: nsCOMPtr target; michael@0: mTarget.swap(target); michael@0: target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); michael@0: } else { michael@0: MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::doom-result"); michael@0: if (mCallback) { michael@0: mCallback->OnFileDoomed(mHandle, mRV); michael@0: } michael@0: MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::doom-result"); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: nsCOMPtr mCallback; michael@0: nsCOMPtr mTarget; michael@0: nsRefPtr mHandle; michael@0: nsresult mRV; michael@0: }; michael@0: michael@0: class DoomFileByKeyEvent : public nsRunnable { michael@0: public: michael@0: DoomFileByKeyEvent(const nsACString &aKey, michael@0: CacheFileIOListener *aCallback) michael@0: : mCallback(aCallback) michael@0: , mRV(NS_ERROR_FAILURE) michael@0: { michael@0: MOZ_COUNT_CTOR(DoomFileByKeyEvent); michael@0: michael@0: SHA1Sum sum; michael@0: sum.update(aKey.BeginReading(), aKey.Length()); michael@0: sum.finish(mHash); michael@0: michael@0: mTarget = static_cast(NS_GetCurrentThread()); michael@0: mIOMan = CacheFileIOManager::gInstance; michael@0: MOZ_ASSERT(mTarget); michael@0: } michael@0: michael@0: ~DoomFileByKeyEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(DoomFileByKeyEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mTarget) { michael@0: if (!mIOMan) { michael@0: mRV = NS_ERROR_NOT_INITIALIZED; michael@0: } else { michael@0: mRV = mIOMan->DoomFileByKeyInternal(&mHash); michael@0: mIOMan = nullptr; michael@0: } michael@0: michael@0: nsCOMPtr target; michael@0: mTarget.swap(target); michael@0: target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); michael@0: } else { michael@0: if (mCallback) { michael@0: mCallback->OnFileDoomed(nullptr, mRV); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: SHA1Sum::Hash mHash; michael@0: nsCOMPtr mCallback; michael@0: nsCOMPtr mTarget; michael@0: nsRefPtr mIOMan; michael@0: nsresult mRV; michael@0: }; michael@0: michael@0: class ReleaseNSPRHandleEvent : public nsRunnable { michael@0: public: michael@0: ReleaseNSPRHandleEvent(CacheFileHandle *aHandle) michael@0: : mHandle(aHandle) michael@0: { michael@0: MOZ_COUNT_CTOR(ReleaseNSPRHandleEvent); michael@0: } michael@0: michael@0: ~ReleaseNSPRHandleEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(ReleaseNSPRHandleEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mHandle->mFD && !mHandle->IsClosed()) { michael@0: CacheFileIOManager::gInstance->ReleaseNSPRHandleInternal(mHandle); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: nsRefPtr mHandle; michael@0: }; michael@0: michael@0: class TruncateSeekSetEOFEvent : public nsRunnable { michael@0: public: michael@0: TruncateSeekSetEOFEvent(CacheFileHandle *aHandle, int64_t aTruncatePos, michael@0: int64_t aEOFPos, CacheFileIOListener *aCallback) michael@0: : mHandle(aHandle) michael@0: , mTruncatePos(aTruncatePos) michael@0: , mEOFPos(aEOFPos) michael@0: , mCallback(aCallback) michael@0: , mRV(NS_ERROR_FAILURE) michael@0: { michael@0: MOZ_COUNT_CTOR(TruncateSeekSetEOFEvent); michael@0: mTarget = static_cast(NS_GetCurrentThread()); michael@0: } michael@0: michael@0: ~TruncateSeekSetEOFEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(TruncateSeekSetEOFEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mTarget) { michael@0: if (mHandle->IsClosed()) { michael@0: mRV = NS_ERROR_NOT_INITIALIZED; michael@0: } else { michael@0: mRV = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal( michael@0: mHandle, mTruncatePos, mEOFPos); michael@0: } michael@0: michael@0: nsCOMPtr target; michael@0: mTarget.swap(target); michael@0: target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); michael@0: } else { michael@0: if (mCallback) { michael@0: mCallback->OnEOFSet(mHandle, mRV); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: nsRefPtr mHandle; michael@0: int64_t mTruncatePos; michael@0: int64_t mEOFPos; michael@0: nsCOMPtr mCallback; michael@0: nsCOMPtr mTarget; michael@0: nsresult mRV; michael@0: }; michael@0: michael@0: class RenameFileEvent : public nsRunnable { michael@0: public: michael@0: RenameFileEvent(CacheFileHandle *aHandle, const nsACString &aNewName, michael@0: CacheFileIOListener *aCallback) michael@0: : mHandle(aHandle) michael@0: , mNewName(aNewName) michael@0: , mCallback(aCallback) michael@0: , mRV(NS_ERROR_FAILURE) michael@0: { michael@0: MOZ_COUNT_CTOR(RenameFileEvent); michael@0: mTarget = static_cast(NS_GetCurrentThread()); michael@0: } michael@0: michael@0: ~RenameFileEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(RenameFileEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mTarget) { michael@0: if (mHandle->IsClosed()) { michael@0: mRV = NS_ERROR_NOT_INITIALIZED; michael@0: } else { michael@0: mRV = CacheFileIOManager::gInstance->RenameFileInternal(mHandle, michael@0: mNewName); michael@0: } michael@0: michael@0: nsCOMPtr target; michael@0: mTarget.swap(target); michael@0: target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); michael@0: } else { michael@0: if (mCallback) { michael@0: mCallback->OnFileRenamed(mHandle, mRV); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: nsRefPtr mHandle; michael@0: nsCString mNewName; michael@0: nsCOMPtr mCallback; michael@0: nsCOMPtr mTarget; michael@0: nsresult mRV; michael@0: }; michael@0: michael@0: class InitIndexEntryEvent : public nsRunnable { michael@0: public: michael@0: InitIndexEntryEvent(CacheFileHandle *aHandle, uint32_t aAppId, michael@0: bool aAnonymous, bool aInBrowser) michael@0: : mHandle(aHandle) michael@0: , mAppId(aAppId) michael@0: , mAnonymous(aAnonymous) michael@0: , mInBrowser(aInBrowser) michael@0: { michael@0: MOZ_COUNT_CTOR(InitIndexEntryEvent); michael@0: } michael@0: michael@0: ~InitIndexEntryEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(InitIndexEntryEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mHandle->IsClosed() || mHandle->IsDoomed()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: CacheIndex::InitEntry(mHandle->Hash(), mAppId, mAnonymous, mInBrowser); michael@0: michael@0: // We cannot set the filesize before we init the entry. If we're opening michael@0: // an existing entry file, frecency and expiration time will be set after michael@0: // parsing the entry file, but we must set the filesize here since nobody is michael@0: // going to set it if there is no write to the file. michael@0: uint32_t sizeInK = mHandle->FileSizeInK(); michael@0: CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, &sizeInK); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: nsRefPtr mHandle; michael@0: uint32_t mAppId; michael@0: bool mAnonymous; michael@0: bool mInBrowser; michael@0: }; michael@0: michael@0: class UpdateIndexEntryEvent : public nsRunnable { michael@0: public: michael@0: UpdateIndexEntryEvent(CacheFileHandle *aHandle, const uint32_t *aFrecency, michael@0: const uint32_t *aExpirationTime) michael@0: : mHandle(aHandle) michael@0: , mHasFrecency(false) michael@0: , mHasExpirationTime(false) michael@0: { michael@0: MOZ_COUNT_CTOR(UpdateIndexEntryEvent); michael@0: if (aFrecency) { michael@0: mHasFrecency = true; michael@0: mFrecency = *aFrecency; michael@0: } michael@0: if (aExpirationTime) { michael@0: mHasExpirationTime = true; michael@0: mExpirationTime = *aExpirationTime; michael@0: } michael@0: } michael@0: michael@0: ~UpdateIndexEntryEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(UpdateIndexEntryEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mHandle->IsClosed() || mHandle->IsDoomed()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: CacheIndex::UpdateEntry(mHandle->Hash(), michael@0: mHasFrecency ? &mFrecency : nullptr, michael@0: mHasExpirationTime ? &mExpirationTime : nullptr, michael@0: nullptr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: nsRefPtr mHandle; michael@0: bool mHasFrecency; michael@0: bool mHasExpirationTime; michael@0: uint32_t mFrecency; michael@0: uint32_t mExpirationTime; michael@0: }; michael@0: michael@0: class MetadataWriteScheduleEvent : public nsRunnable michael@0: { michael@0: public: michael@0: enum EMode { michael@0: SCHEDULE, michael@0: UNSCHEDULE, michael@0: SHUTDOWN michael@0: } mMode; michael@0: michael@0: nsRefPtr mFile; michael@0: nsRefPtr mIOMan; michael@0: michael@0: MetadataWriteScheduleEvent(CacheFileIOManager * aManager, michael@0: CacheFile * aFile, michael@0: EMode aMode) michael@0: : mMode(aMode) michael@0: , mFile(aFile) michael@0: , mIOMan(aManager) michael@0: { } michael@0: michael@0: virtual ~MetadataWriteScheduleEvent() { } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsRefPtr ioMan = CacheFileIOManager::gInstance; michael@0: if (!ioMan) { michael@0: NS_WARNING("CacheFileIOManager already gone in MetadataWriteScheduleEvent::Run()"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: switch (mMode) michael@0: { michael@0: case SCHEDULE: michael@0: ioMan->ScheduleMetadataWriteInternal(mFile); michael@0: break; michael@0: case UNSCHEDULE: michael@0: ioMan->UnscheduleMetadataWriteInternal(mFile); michael@0: break; michael@0: case SHUTDOWN: michael@0: ioMan->ShutdownMetadataWriteSchedulingInternal(); michael@0: break; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: CacheFileIOManager * CacheFileIOManager::gInstance = nullptr; michael@0: michael@0: NS_IMPL_ISUPPORTS(CacheFileIOManager, nsITimerCallback) michael@0: michael@0: CacheFileIOManager::CacheFileIOManager() michael@0: : mShuttingDown(false) michael@0: , mTreeCreated(false) michael@0: , mOverLimitEvicting(false) michael@0: , mRemovingTrashDirs(false) michael@0: { michael@0: LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this)); michael@0: MOZ_COUNT_CTOR(CacheFileIOManager); michael@0: MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!"); michael@0: } michael@0: michael@0: CacheFileIOManager::~CacheFileIOManager() michael@0: { michael@0: LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this)); michael@0: MOZ_COUNT_DTOR(CacheFileIOManager); michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::Init() michael@0: { michael@0: LOG(("CacheFileIOManager::Init()")); michael@0: michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (gInstance) { michael@0: return NS_ERROR_ALREADY_INITIALIZED; michael@0: } michael@0: michael@0: nsRefPtr ioMan = new CacheFileIOManager(); michael@0: michael@0: nsresult rv = ioMan->InitInternal(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: ioMan.swap(gInstance); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::InitInternal() michael@0: { michael@0: nsresult rv; michael@0: michael@0: mIOThread = new CacheIOThread(); michael@0: michael@0: rv = mIOThread->Init(); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread"); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mStartTime = TimeStamp::NowLoRes(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::Shutdown() michael@0: { michael@0: LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance)); michael@0: michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (!gInstance) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: Telemetry::AutoTimer shutdownTimer; michael@0: michael@0: CacheIndex::PreShutdown(); michael@0: michael@0: ShutdownMetadataWriteScheduling(); michael@0: michael@0: { michael@0: mozilla::Mutex lock("CacheFileIOManager::Shutdown() lock"); michael@0: mozilla::CondVar condVar(lock, "CacheFileIOManager::Shutdown() condVar"); michael@0: michael@0: MutexAutoLock autoLock(lock); michael@0: nsRefPtr ev = new ShutdownEvent(&lock, &condVar); michael@0: DebugOnly rv; michael@0: rv = gInstance->mIOThread->Dispatch(ev, CacheIOThread::CLOSE); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: condVar.Wait(); michael@0: } michael@0: michael@0: MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0); michael@0: MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0); michael@0: michael@0: if (gInstance->mIOThread) { michael@0: gInstance->mIOThread->Shutdown(); michael@0: } michael@0: michael@0: CacheIndex::Shutdown(); michael@0: michael@0: if (CacheObserver::ClearCacheOnShutdown()) { michael@0: gInstance->SyncRemoveAllCacheFiles(); michael@0: } michael@0: michael@0: nsRefPtr ioMan; michael@0: ioMan.swap(gInstance); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::ShutdownInternal() michael@0: { michael@0: LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]", this)); michael@0: michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread()); michael@0: michael@0: // No new handles can be created after this flag is set michael@0: mShuttingDown = true; michael@0: michael@0: // close all handles and delete all associated files michael@0: nsTArray > handles; michael@0: mHandles.GetAllHandles(&handles); michael@0: handles.AppendElements(mSpecialHandles); michael@0: michael@0: for (uint32_t i=0 ; imClosed = true; michael@0: michael@0: h->Log(); michael@0: michael@0: // Close file handle michael@0: if (h->mFD) { michael@0: ReleaseNSPRHandleInternal(h); michael@0: } michael@0: michael@0: // Remove file if entry is doomed or invalid michael@0: if (h->mFileExists && (h->mIsDoomed || h->mInvalid)) { michael@0: LOG(("CacheFileIOManager::ShutdownInternal() - Removing file from disk")); michael@0: h->mFile->Remove(false); michael@0: } michael@0: michael@0: if (!h->IsSpecialFile() && !h->mIsDoomed && michael@0: (h->mInvalid || !h->mFileExists)) { michael@0: CacheIndex::RemoveEntry(h->Hash()); michael@0: } michael@0: michael@0: // Remove the handle from mHandles/mSpecialHandles michael@0: if (h->IsSpecialFile()) { michael@0: mSpecialHandles.RemoveElement(h); michael@0: } else { michael@0: mHandles.RemoveHandle(h); michael@0: } michael@0: } michael@0: michael@0: // Assert the table is empty. When we are here, no new handles can be added michael@0: // and handles will no longer remove them self from this table and we don't michael@0: // want to keep invalid handles here. Also, there is no lookup after this michael@0: // point to happen. michael@0: MOZ_ASSERT(mHandles.HandleCount() == 0); michael@0: michael@0: // Release trash directory enumerator michael@0: if (mTrashDirEnumerator) { michael@0: mTrashDirEnumerator->Close(); michael@0: mTrashDirEnumerator = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::OnProfile() michael@0: { michael@0: LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance)); michael@0: michael@0: nsRefPtr ioMan = gInstance; michael@0: if (!ioMan) { michael@0: // CacheFileIOManager::Init() failed, probably could not create the IO michael@0: // thread, just go with it... michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr directory; michael@0: michael@0: CacheObserver::ParentDirOverride(getter_AddRefs(directory)); michael@0: michael@0: #if defined(MOZ_WIDGET_ANDROID) michael@0: char* cachePath = getenv("CACHE_DIRECTORY"); michael@0: if (!directory && cachePath && *cachePath) { michael@0: rv = NS_NewNativeLocalFile(nsDependentCString(cachePath), michael@0: true, getter_AddRefs(directory)); michael@0: } michael@0: #endif michael@0: michael@0: if (!directory) { michael@0: rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR, michael@0: getter_AddRefs(directory)); michael@0: } michael@0: michael@0: if (!directory) { michael@0: rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, michael@0: getter_AddRefs(directory)); michael@0: } michael@0: michael@0: if (directory) { michael@0: rv = directory->Append(NS_LITERAL_STRING("cache2")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // All functions return a clone. michael@0: ioMan->mCacheDirectory.swap(directory); michael@0: michael@0: if (ioMan->mCacheDirectory) { michael@0: CacheIndex::Init(ioMan->mCacheDirectory); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: CacheFileIOManager::IOTarget() michael@0: { michael@0: nsCOMPtr target; michael@0: if (gInstance && gInstance->mIOThread) { michael@0: target = gInstance->mIOThread->Target(); michael@0: } michael@0: michael@0: return target.forget(); michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: CacheFileIOManager::IOThread() michael@0: { michael@0: nsRefPtr thread; michael@0: if (gInstance) { michael@0: thread = gInstance->mIOThread; michael@0: } michael@0: michael@0: return thread.forget(); michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: CacheFileIOManager::IsOnIOThread() michael@0: { michael@0: nsRefPtr ioMan = gInstance; michael@0: if (ioMan && ioMan->mIOThread) { michael@0: return ioMan->mIOThread->IsCurrentThread(); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: CacheFileIOManager::IsOnIOThreadOrCeased() michael@0: { michael@0: nsRefPtr ioMan = gInstance; michael@0: if (ioMan && ioMan->mIOThread) { michael@0: return ioMan->mIOThread->IsCurrentThread(); michael@0: } michael@0: michael@0: // Ceased... michael@0: return true; michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: CacheFileIOManager::IsShutdown() michael@0: { michael@0: if (!gInstance) { michael@0: return true; michael@0: } michael@0: return gInstance->mShuttingDown; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::ScheduleMetadataWrite(CacheFile * aFile) michael@0: { michael@0: nsRefPtr ioMan = gInstance; michael@0: NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsRefPtr event = new MetadataWriteScheduleEvent( michael@0: ioMan, aFile, MetadataWriteScheduleEvent::SCHEDULE); michael@0: nsCOMPtr target = ioMan->IOTarget(); michael@0: NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); michael@0: return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::ScheduleMetadataWriteInternal(CacheFile * aFile) michael@0: { michael@0: MOZ_ASSERT(IsOnIOThreadOrCeased()); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!mMetadataWritesTimer) { michael@0: mMetadataWritesTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mMetadataWritesTimer->InitWithCallback( michael@0: this, kMetadataWriteDelay, nsITimer::TYPE_ONE_SHOT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (mScheduledMetadataWrites.IndexOf(aFile) != michael@0: mScheduledMetadataWrites.NoIndex) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mScheduledMetadataWrites.AppendElement(aFile); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::UnscheduleMetadataWrite(CacheFile * aFile) michael@0: { michael@0: nsRefPtr ioMan = gInstance; michael@0: NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsRefPtr event = new MetadataWriteScheduleEvent( michael@0: ioMan, aFile, MetadataWriteScheduleEvent::UNSCHEDULE); michael@0: nsCOMPtr target = ioMan->IOTarget(); michael@0: NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); michael@0: return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile * aFile) michael@0: { michael@0: MOZ_ASSERT(IsOnIOThreadOrCeased()); michael@0: michael@0: mScheduledMetadataWrites.RemoveElement(aFile); michael@0: michael@0: if (mScheduledMetadataWrites.Length() == 0 && michael@0: mMetadataWritesTimer) { michael@0: mMetadataWritesTimer->Cancel(); michael@0: mMetadataWritesTimer = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::ShutdownMetadataWriteScheduling() michael@0: { michael@0: nsRefPtr ioMan = gInstance; michael@0: NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsRefPtr event = new MetadataWriteScheduleEvent( michael@0: ioMan, nullptr, MetadataWriteScheduleEvent::SHUTDOWN); michael@0: nsCOMPtr target = ioMan->IOTarget(); michael@0: NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); michael@0: return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::ShutdownMetadataWriteSchedulingInternal() michael@0: { michael@0: MOZ_ASSERT(IsOnIOThreadOrCeased()); michael@0: michael@0: nsTArray > files; michael@0: files.SwapElements(mScheduledMetadataWrites); michael@0: for (uint32_t i = 0; i < files.Length(); ++i) { michael@0: CacheFile * file = files[i]; michael@0: file->WriteMetadataIfNeeded(); michael@0: } michael@0: michael@0: if (mMetadataWritesTimer) { michael@0: mMetadataWritesTimer->Cancel(); michael@0: mMetadataWritesTimer = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CacheFileIOManager::Notify(nsITimer * aTimer) michael@0: { michael@0: MOZ_ASSERT(IsOnIOThreadOrCeased()); michael@0: MOZ_ASSERT(mMetadataWritesTimer == aTimer); michael@0: michael@0: mMetadataWritesTimer = nullptr; michael@0: michael@0: nsTArray > files; michael@0: files.SwapElements(mScheduledMetadataWrites); michael@0: for (uint32_t i = 0; i < files.Length(); ++i) { michael@0: CacheFile * file = files[i]; michael@0: file->WriteMetadataIfNeeded(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::OpenFile(const nsACString &aKey, michael@0: uint32_t aFlags, bool aResultOnAnyThread, michael@0: CacheFileIOListener *aCallback) michael@0: { michael@0: LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]", michael@0: PromiseFlatCString(aKey).get(), aFlags, aCallback)); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (!ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: bool priority = aFlags & CacheFileIOManager::PRIORITY; michael@0: nsRefPtr ev = new OpenFileEvent(aKey, aFlags, aResultOnAnyThread, aCallback); michael@0: rv = ioMan->mIOThread->Dispatch(ev, priority michael@0: ? CacheIOThread::OPEN_PRIORITY michael@0: : CacheIOThread::OPEN); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash, michael@0: const nsACString &aKey, michael@0: uint32_t aFlags, michael@0: CacheFileHandle **_retval) michael@0: { michael@0: LOG(("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, " michael@0: "key=%s, flags=%d]", LOGSHA1(aHash), PromiseFlatCString(aKey).get(), michael@0: aFlags)); michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (mShuttingDown) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: if (!mTreeCreated) { michael@0: rv = CreateCacheTree(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: nsCOMPtr file; michael@0: rv = GetFile(aHash, getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsRefPtr handle; michael@0: mHandles.GetHandle(aHash, false, getter_AddRefs(handle)); michael@0: michael@0: if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) { michael@0: if (handle) { michael@0: rv = DoomFileInternal(handle); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: handle = nullptr; michael@0: } michael@0: michael@0: rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool exists; michael@0: rv = file->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists) { michael@0: CacheIndex::RemoveEntry(aHash); michael@0: michael@0: LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file from " michael@0: "disk")); michael@0: rv = file->Remove(false); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Cannot remove old entry from the disk"); michael@0: LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file failed" michael@0: ". [rv=0x%08x]", rv)); michael@0: } michael@0: } michael@0: michael@0: CacheIndex::AddEntry(aHash); michael@0: handle->mFile.swap(file); michael@0: handle->mFileSize = 0; michael@0: } michael@0: michael@0: if (handle) { michael@0: handle.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool exists; michael@0: rv = file->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists && mContextEvictor) { michael@0: if (mContextEvictor->ContextsCount() == 0) { michael@0: mContextEvictor = nullptr; michael@0: } else { michael@0: bool wasEvicted = false; michael@0: mContextEvictor->WasEvicted(aKey, file, &wasEvicted); michael@0: if (wasEvicted) { michael@0: LOG(("CacheFileIOManager::OpenFileInternal() - Removing file since the " michael@0: "entry was evicted by EvictByContext()")); michael@0: exists = false; michael@0: file->Remove(false); michael@0: CacheIndex::RemoveEntry(aHash); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists) { michael@0: rv = file->GetFileSize(&handle->mFileSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: handle->mFileExists = true; michael@0: michael@0: CacheIndex::EnsureEntryExists(aHash); michael@0: } else { michael@0: handle->mFileSize = 0; michael@0: michael@0: CacheIndex::AddEntry(aHash); michael@0: } michael@0: michael@0: handle->mFile.swap(file); michael@0: handle.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::OpenSpecialFileInternal(const nsACString &aKey, michael@0: uint32_t aFlags, michael@0: CacheFileHandle **_retval) michael@0: { michael@0: LOG(("CacheFileIOManager::OpenSpecialFileInternal() [key=%s, flags=%d]", michael@0: PromiseFlatCString(aKey).get(), aFlags)); michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (mShuttingDown) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: if (!mTreeCreated) { michael@0: rv = CreateCacheTree(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: nsCOMPtr file; michael@0: rv = GetSpecialFile(aKey, getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsRefPtr handle; michael@0: for (uint32_t i = 0 ; i < mSpecialHandles.Length() ; i++) { michael@0: if (!mSpecialHandles[i]->IsDoomed() && mSpecialHandles[i]->Key() == aKey) { michael@0: handle = mSpecialHandles[i]; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) { michael@0: if (handle) { michael@0: rv = DoomFileInternal(handle); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: handle = nullptr; michael@0: } michael@0: michael@0: handle = new CacheFileHandle(aKey, aFlags & PRIORITY); michael@0: mSpecialHandles.AppendElement(handle); michael@0: michael@0: bool exists; michael@0: rv = file->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists) { michael@0: LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from " michael@0: "disk")); michael@0: rv = file->Remove(false); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Cannot remove old entry from the disk"); michael@0: LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file " michael@0: "failed. [rv=0x%08x]", rv)); michael@0: } michael@0: } michael@0: michael@0: handle->mFile.swap(file); michael@0: handle->mFileSize = 0; michael@0: } michael@0: michael@0: if (handle) { michael@0: handle.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool exists; michael@0: rv = file->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: handle = new CacheFileHandle(aKey, aFlags & PRIORITY); michael@0: mSpecialHandles.AppendElement(handle); michael@0: michael@0: if (exists) { michael@0: rv = file->GetFileSize(&handle->mFileSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: handle->mFileExists = true; michael@0: } else { michael@0: handle->mFileSize = 0; michael@0: } michael@0: michael@0: handle->mFile.swap(file); michael@0: handle.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::CloseHandleInternal(CacheFileHandle *aHandle) michael@0: { michael@0: LOG(("CacheFileIOManager::CloseHandleInternal() [handle=%p]", aHandle)); michael@0: aHandle->Log(); michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: michael@0: // Close file handle michael@0: if (aHandle->mFD) { michael@0: ReleaseNSPRHandleInternal(aHandle); michael@0: } michael@0: michael@0: // Delete the file if the entry was doomed or invalid michael@0: if (aHandle->mIsDoomed || aHandle->mInvalid) { michael@0: LOG(("CacheFileIOManager::CloseHandleInternal() - Removing file from " michael@0: "disk")); michael@0: aHandle->mFile->Remove(false); michael@0: } michael@0: michael@0: if (!aHandle->IsSpecialFile() && !aHandle->mIsDoomed && michael@0: (aHandle->mInvalid || !aHandle->mFileExists)) { michael@0: CacheIndex::RemoveEntry(aHandle->Hash()); michael@0: } michael@0: michael@0: // Don't remove handles after shutdown michael@0: if (!mShuttingDown) { michael@0: if (aHandle->IsSpecialFile()) { michael@0: mSpecialHandles.RemoveElement(aHandle); michael@0: } else { michael@0: mHandles.RemoveHandle(aHandle); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::Read(CacheFileHandle *aHandle, int64_t aOffset, michael@0: char *aBuf, int32_t aCount, bool aResultOnAnyThread, michael@0: CacheFileIOListener *aCallback) michael@0: { michael@0: LOG(("CacheFileIOManager::Read() [handle=%p, offset=%lld, count=%d, " michael@0: "listener=%p]", aHandle, aOffset, aCount, aCallback)); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (aHandle->IsClosed() || !ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: nsRefPtr ev = new ReadEvent(aHandle, aOffset, aBuf, aCount, michael@0: aResultOnAnyThread, aCallback); michael@0: rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority() michael@0: ? CacheIOThread::READ_PRIORITY michael@0: : CacheIOThread::READ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::ReadInternal(CacheFileHandle *aHandle, int64_t aOffset, michael@0: char *aBuf, int32_t aCount) michael@0: { michael@0: LOG(("CacheFileIOManager::ReadInternal() [handle=%p, offset=%lld, count=%d]", michael@0: aHandle, aOffset, aCount)); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!aHandle->mFileExists) { michael@0: NS_WARNING("Trying to read from non-existent file"); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (!aHandle->mFD) { michael@0: rv = OpenNSPRHandle(aHandle); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: NSPRHandleUsed(aHandle); michael@0: } michael@0: michael@0: // Check again, OpenNSPRHandle could figure out the file was gone. michael@0: if (!aHandle->mFileExists) { michael@0: NS_WARNING("Trying to read from non-existent file"); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET); michael@0: if (offset == -1) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: int32_t bytesRead = PR_Read(aHandle->mFD, aBuf, aCount); michael@0: if (bytesRead != aCount) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::Write(CacheFileHandle *aHandle, int64_t aOffset, michael@0: const char *aBuf, int32_t aCount, bool aValidate, michael@0: CacheFileIOListener *aCallback) michael@0: { michael@0: LOG(("CacheFileIOManager::Write() [handle=%p, offset=%lld, count=%d, " michael@0: "validate=%d, listener=%p]", aHandle, aOffset, aCount, aValidate, michael@0: aCallback)); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (aHandle->IsClosed() || !ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: nsRefPtr ev = new WriteEvent(aHandle, aOffset, aBuf, aCount, michael@0: aValidate, aCallback); michael@0: rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::WriteInternal(CacheFileHandle *aHandle, int64_t aOffset, michael@0: const char *aBuf, int32_t aCount, michael@0: bool aValidate) michael@0: { michael@0: LOG(("CacheFileIOManager::WriteInternal() [handle=%p, offset=%lld, count=%d, " michael@0: "validate=%d]", aHandle, aOffset, aCount, aValidate)); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!aHandle->mFileExists) { michael@0: rv = CreateFile(aHandle); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (!aHandle->mFD) { michael@0: rv = OpenNSPRHandle(aHandle); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: NSPRHandleUsed(aHandle); michael@0: } michael@0: michael@0: // Check again, OpenNSPRHandle could figure out the file was gone. michael@0: if (!aHandle->mFileExists) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // Write invalidates the entry by default michael@0: aHandle->mInvalid = true; michael@0: michael@0: int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET); michael@0: if (offset == -1) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: int32_t bytesWritten = PR_Write(aHandle->mFD, aBuf, aCount); michael@0: michael@0: if (bytesWritten != -1 && aHandle->mFileSize < aOffset+bytesWritten) { michael@0: aHandle->mFileSize = aOffset+bytesWritten; michael@0: michael@0: if (!aHandle->IsDoomed() && !aHandle->IsSpecialFile()) { michael@0: uint32_t size = aHandle->FileSizeInK(); michael@0: CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, &size); michael@0: EvictIfOverLimitInternal(); michael@0: } michael@0: } michael@0: michael@0: if (bytesWritten != aCount) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Write was successful and this write validates the entry (i.e. metadata) michael@0: if (aValidate) { michael@0: aHandle->mInvalid = false; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::DoomFile(CacheFileHandle *aHandle, michael@0: CacheFileIOListener *aCallback) michael@0: { michael@0: LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]", michael@0: aHandle, aCallback)); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (aHandle->IsClosed() || !ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: nsRefPtr ev = new DoomFileEvent(aHandle, aCallback); michael@0: rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority() michael@0: ? CacheIOThread::OPEN_PRIORITY michael@0: : CacheIOThread::OPEN); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle) michael@0: { michael@0: LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle)); michael@0: aHandle->Log(); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (aHandle->IsDoomed()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aHandle->mFileExists) { michael@0: // we need to move the current file to the doomed directory michael@0: if (aHandle->mFD) { michael@0: ReleaseNSPRHandleInternal(aHandle); michael@0: } michael@0: michael@0: // find unused filename michael@0: nsCOMPtr file; michael@0: rv = GetDoomedFile(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr parentDir; michael@0: rv = file->GetParent(getter_AddRefs(parentDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString leafName; michael@0: rv = file->GetNativeLeafName(leafName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aHandle->mFile->MoveToNative(parentDir, leafName); michael@0: if (NS_ERROR_FILE_NOT_FOUND == rv || NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv) { michael@0: LOG((" file already removed under our hands")); michael@0: aHandle->mFileExists = false; michael@0: rv = NS_OK; michael@0: } else { michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: aHandle->mFile.swap(file); michael@0: } michael@0: } michael@0: michael@0: if (!aHandle->IsSpecialFile()) { michael@0: CacheIndex::RemoveEntry(aHandle->Hash()); michael@0: } michael@0: michael@0: aHandle->mIsDoomed = true; michael@0: michael@0: if (!aHandle->IsSpecialFile()) { michael@0: nsRefPtr storageService = CacheStorageService::Self(); michael@0: if (storageService) { michael@0: nsAutoCString idExtension, url; michael@0: nsCOMPtr info = michael@0: CacheFileUtils::ParseKey(aHandle->Key(), &idExtension, &url); michael@0: MOZ_ASSERT(info); michael@0: if (info) { michael@0: storageService->CacheFileDoomed(info, idExtension, url); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::DoomFileByKey(const nsACString &aKey, michael@0: CacheFileIOListener *aCallback) michael@0: { michael@0: LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]", michael@0: PromiseFlatCString(aKey).get(), aCallback)); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (!ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: nsRefPtr ev = new DoomFileByKeyEvent(aKey, aCallback); michael@0: rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash *aHash) michael@0: { michael@0: LOG(("CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x]" michael@0: , LOGSHA1(aHash))); michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (mShuttingDown) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: if (!mCacheDirectory) { michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: } michael@0: michael@0: // Find active handle michael@0: nsRefPtr handle; michael@0: mHandles.GetHandle(aHash, true, getter_AddRefs(handle)); michael@0: michael@0: if (handle) { michael@0: handle->Log(); michael@0: michael@0: if (handle->IsDoomed()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: return DoomFileInternal(handle); michael@0: } michael@0: michael@0: // There is no handle for this file, delete the file if exists michael@0: nsCOMPtr file; michael@0: rv = GetFile(aHash, getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool exists; michael@0: rv = file->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!exists) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: LOG(("CacheFileIOManager::DoomFileByKeyInternal() - Removing file from " michael@0: "disk")); michael@0: rv = file->Remove(false); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Cannot remove old entry from the disk"); michael@0: LOG(("CacheFileIOManager::DoomFileByKeyInternal() - Removing file failed. " michael@0: "[rv=0x%08x]", rv)); michael@0: } michael@0: michael@0: CacheIndex::RemoveEntry(aHash); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle *aHandle) michael@0: { michael@0: LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle)); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (aHandle->IsClosed() || !ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: nsRefPtr ev = new ReleaseNSPRHandleEvent(aHandle); michael@0: rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::CLOSE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::ReleaseNSPRHandleInternal(CacheFileHandle *aHandle) michael@0: { michael@0: LOG(("CacheFileIOManager::ReleaseNSPRHandleInternal() [handle=%p]", aHandle)); michael@0: michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: MOZ_ASSERT(aHandle->mFD); michael@0: michael@0: DebugOnly found; michael@0: found = mHandlesByLastUsed.RemoveElement(aHandle); michael@0: MOZ_ASSERT(found); michael@0: michael@0: PR_Close(aHandle->mFD); michael@0: aHandle->mFD = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::TruncateSeekSetEOF(CacheFileHandle *aHandle, michael@0: int64_t aTruncatePos, int64_t aEOFPos, michael@0: CacheFileIOListener *aCallback) michael@0: { michael@0: LOG(("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, truncatePos=%lld, " michael@0: "EOFPos=%lld, listener=%p]", aHandle, aTruncatePos, aEOFPos, aCallback)); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (aHandle->IsClosed() || !ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: nsRefPtr ev = new TruncateSeekSetEOFEvent( michael@0: aHandle, aTruncatePos, aEOFPos, michael@0: aCallback); michael@0: rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: void CacheFileIOManager::GetCacheDirectory(nsIFile** result) michael@0: { michael@0: *result = nullptr; michael@0: michael@0: nsRefPtr ioMan = gInstance; michael@0: if (!ioMan) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr file = ioMan->mCacheDirectory; michael@0: file.forget(result); michael@0: } michael@0: michael@0: static nsresult michael@0: TruncFile(PRFileDesc *aFD, uint32_t aEOF) michael@0: { michael@0: #if defined(XP_UNIX) michael@0: if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) { michael@0: NS_ERROR("ftruncate failed"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: #elif defined(XP_WIN) michael@0: int32_t cnt = PR_Seek(aFD, aEOF, PR_SEEK_SET); michael@0: if (cnt == -1) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(aFD))) { michael@0: NS_ERROR("SetEndOfFile failed"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: #else michael@0: MOZ_ASSERT(false, "Not implemented!"); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::TruncateSeekSetEOFInternal(CacheFileHandle *aHandle, michael@0: int64_t aTruncatePos, michael@0: int64_t aEOFPos) michael@0: { michael@0: LOG(("CacheFileIOManager::TruncateSeekSetEOFInternal() [handle=%p, " michael@0: "truncatePos=%lld, EOFPos=%lld]", aHandle, aTruncatePos, aEOFPos)); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!aHandle->mFileExists) { michael@0: rv = CreateFile(aHandle); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (!aHandle->mFD) { michael@0: rv = OpenNSPRHandle(aHandle); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: NSPRHandleUsed(aHandle); michael@0: } michael@0: michael@0: // Check again, OpenNSPRHandle could figure out the file was gone. michael@0: if (!aHandle->mFileExists) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // This operation always invalidates the entry michael@0: aHandle->mInvalid = true; michael@0: michael@0: rv = TruncFile(aHandle->mFD, static_cast(aTruncatePos)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = TruncFile(aHandle->mFD, static_cast(aEOFPos)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::RenameFile(CacheFileHandle *aHandle, michael@0: const nsACString &aNewName, michael@0: CacheFileIOListener *aCallback) michael@0: { michael@0: LOG(("CacheFileIOManager::RenameFile() [handle=%p, newName=%s, listener=%p]", michael@0: aHandle, PromiseFlatCString(aNewName).get(), aCallback)); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (aHandle->IsClosed() || !ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: if (!aHandle->IsSpecialFile()) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsRefPtr ev = new RenameFileEvent(aHandle, aNewName, michael@0: aCallback); michael@0: rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::RenameFileInternal(CacheFileHandle *aHandle, michael@0: const nsACString &aNewName) michael@0: { michael@0: LOG(("CacheFileIOManager::RenameFileInternal() [handle=%p, newName=%s]", michael@0: aHandle, PromiseFlatCString(aNewName).get())); michael@0: michael@0: nsresult rv; michael@0: michael@0: MOZ_ASSERT(aHandle->IsSpecialFile()); michael@0: michael@0: if (aHandle->IsDoomed()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // Doom old handle if it exists and is not doomed michael@0: for (uint32_t i = 0 ; i < mSpecialHandles.Length() ; i++) { michael@0: if (!mSpecialHandles[i]->IsDoomed() && michael@0: mSpecialHandles[i]->Key() == aNewName) { michael@0: MOZ_ASSERT(aHandle != mSpecialHandles[i]); michael@0: rv = DoomFileInternal(mSpecialHandles[i]); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr file; michael@0: rv = GetSpecialFile(aNewName, getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool exists; michael@0: rv = file->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists) { michael@0: LOG(("CacheFileIOManager::RenameFileInternal() - Removing old file from " michael@0: "disk")); michael@0: rv = file->Remove(false); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Cannot remove file from the disk"); michael@0: LOG(("CacheFileIOManager::RenameFileInternal() - Removing old file failed" michael@0: ". [rv=0x%08x]", rv)); michael@0: } michael@0: } michael@0: michael@0: if (!aHandle->FileExists()) { michael@0: aHandle->mKey = aNewName; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aHandle->mFD) { michael@0: ReleaseNSPRHandleInternal(aHandle); michael@0: } michael@0: michael@0: rv = aHandle->mFile->MoveToNative(nullptr, aNewName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aHandle->mKey = aNewName; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::EvictIfOverLimit() michael@0: { michael@0: LOG(("CacheFileIOManager::EvictIfOverLimit()")); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (!ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: nsCOMPtr ev; michael@0: ev = NS_NewRunnableMethod(ioMan, michael@0: &CacheFileIOManager::EvictIfOverLimitInternal); michael@0: michael@0: rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::EVICT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::EvictIfOverLimitInternal() michael@0: { michael@0: LOG(("CacheFileIOManager::EvictIfOverLimitInternal()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread()); michael@0: michael@0: if (mShuttingDown) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: if (mOverLimitEvicting) { michael@0: LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Eviction already " michael@0: "running.")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: UpdateSmartCacheSize(); michael@0: michael@0: uint32_t cacheUsage; michael@0: rv = CacheIndex::GetCacheSize(&cacheUsage); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t cacheLimit = CacheObserver::DiskCacheCapacity() >> 10; michael@0: if (cacheUsage <= cacheLimit) { michael@0: LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size under " michael@0: "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size exceeded " michael@0: "limit. Starting overlimit eviction. [cacheSize=%u, limit=%u]", michael@0: cacheUsage, cacheLimit)); michael@0: michael@0: nsCOMPtr ev; michael@0: ev = NS_NewRunnableMethod(this, michael@0: &CacheFileIOManager::OverLimitEvictionInternal); michael@0: michael@0: rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mOverLimitEvicting = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::OverLimitEvictionInternal() michael@0: { michael@0: LOG(("CacheFileIOManager::OverLimitEvictionInternal()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread()); michael@0: michael@0: // mOverLimitEvicting is accessed only on IO thread, so we can set it to false michael@0: // here and set it to true again once we dispatch another event that will michael@0: // continue with the eviction. The reason why we do so is that we can fail michael@0: // early anywhere in this method and the variable will contain a correct michael@0: // value. Otherwise we would need to set it to false on every failing place. michael@0: mOverLimitEvicting = false; michael@0: michael@0: if (mShuttingDown) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: UpdateSmartCacheSize(); michael@0: michael@0: while (true) { michael@0: uint32_t cacheUsage; michael@0: rv = CacheIndex::GetCacheSize(&cacheUsage); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t cacheLimit = CacheObserver::DiskCacheCapacity() >> 10; michael@0: if (cacheUsage <= cacheLimit) { michael@0: LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size under " michael@0: "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over " michael@0: "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit)); michael@0: michael@0: if (CacheIOThread::YieldAndRerun()) { michael@0: LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop " michael@0: "for higher level events.")); michael@0: mOverLimitEvicting = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: SHA1Sum::Hash hash; michael@0: uint32_t cnt; michael@0: static uint32_t consecutiveFailures = 0; michael@0: rv = CacheIndex::GetEntryForEviction(&hash, &cnt); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = DoomFileByKeyInternal(&hash); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: consecutiveFailures = 0; michael@0: } else if (rv == NS_ERROR_NOT_AVAILABLE) { michael@0: LOG(("CacheFileIOManager::OverLimitEvictionInternal() - " michael@0: "DoomFileByKeyInternal() failed. [rv=0x%08x]", rv)); michael@0: // TODO index is outdated, start update michael@0: michael@0: // Make sure index won't return the same entry again michael@0: CacheIndex::RemoveEntry(&hash); michael@0: consecutiveFailures = 0; michael@0: } else { michael@0: // This shouldn't normally happen, but the eviction must not fail michael@0: // completely if we ever encounter this problem. michael@0: NS_WARNING("CacheFileIOManager::OverLimitEvictionInternal() - Unexpected " michael@0: "failure of DoomFileByKeyInternal()"); michael@0: michael@0: LOG(("CacheFileIOManager::OverLimitEvictionInternal() - " michael@0: "DoomFileByKeyInternal() failed. [rv=0x%08x]", rv)); michael@0: michael@0: // Normally, CacheIndex::UpdateEntry() is called only to update newly michael@0: // created/opened entries which are always fresh and UpdateEntry() expects michael@0: // and checks this flag. The way we use UpdateEntry() here is a kind of michael@0: // hack and we must make sure the flag is set by calling michael@0: // EnsureEntryExists(). michael@0: rv = CacheIndex::EnsureEntryExists(&hash); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Move the entry at the end of both lists to make sure we won't end up michael@0: // failing on one entry forever. michael@0: uint32_t frecency = 0; michael@0: uint32_t expTime = nsICacheEntry::NO_EXPIRATION_TIME; michael@0: rv = CacheIndex::UpdateEntry(&hash, &frecency, &expTime, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: consecutiveFailures++; michael@0: if (consecutiveFailures >= cnt) { michael@0: // This doesn't necessarily mean that we've tried to doom every entry michael@0: // but we've reached a sane number of tries. It is likely that another michael@0: // eviction will start soon. And as said earlier, this normally doesn't michael@0: // happen at all. michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_NOTREACHED("We should never get here"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::EvictAll() michael@0: { michael@0: LOG(("CacheFileIOManager::EvictAll()")); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (!ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: nsCOMPtr ev; michael@0: ev = NS_NewRunnableMethod(ioMan, &CacheFileIOManager::EvictAllInternal); michael@0: michael@0: rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class EvictionNotifierRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: NS_DECL_NSIRUNNABLE michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: EvictionNotifierRunnable::Run() michael@0: { michael@0: nsCOMPtr obsSvc = mozilla::services::GetObserverService(); michael@0: if (obsSvc) { michael@0: obsSvc->NotifyObservers(nullptr, "cacheservice:empty-cache", nullptr); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: nsresult michael@0: CacheFileIOManager::EvictAllInternal() michael@0: { michael@0: LOG(("CacheFileIOManager::EvictAllInternal()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread()); michael@0: michael@0: nsRefPtr r = new EvictionNotifierRunnable(); michael@0: michael@0: if (!mCacheDirectory) { michael@0: // This is a kind of hack. Somebody called EvictAll() without a profile. michael@0: // This happens in xpcshell tests that use cache without profile. We need michael@0: // to notify observers in this case since the tests are waiting for it. michael@0: NS_DispatchToMainThread(r); michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: } michael@0: michael@0: if (mShuttingDown) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: if (!mTreeCreated) { michael@0: rv = CreateCacheTree(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // Doom all active handles michael@0: nsTArray > handles; michael@0: mHandles.GetActiveHandles(&handles); michael@0: michael@0: for (uint32_t i = 0; i < handles.Length(); ++i) { michael@0: rv = DoomFileInternal(handles[i]); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: LOG(("CacheFileIOManager::EvictAllInternal() - Cannot doom handle " michael@0: "[handle=%p]", handles[i].get())); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr file; michael@0: rv = mCacheDirectory->Clone(getter_AddRefs(file)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: rv = file->AppendNative(NS_LITERAL_CSTRING(kEntriesDir)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: // Trash current entries directory michael@0: rv = TrashDirectory(file); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: // Files are now inaccessible in entries directory, notify observers. michael@0: NS_DispatchToMainThread(r); michael@0: michael@0: // Create a new empty entries directory michael@0: rv = CheckAndCreateDir(mCacheDirectory, kEntriesDir, false); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: CacheIndex::RemoveAll(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo) michael@0: { michael@0: LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]", michael@0: aLoadContextInfo)); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (!ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: nsCOMPtr ev; michael@0: ev = NS_NewRunnableMethodWithArg > michael@0: (ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo); michael@0: michael@0: rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo) michael@0: { michael@0: LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, " michael@0: "anonymous=%u, inBrowser=%u, appId=%u]", aLoadContextInfo, michael@0: aLoadContextInfo->IsAnonymous(), aLoadContextInfo->IsInBrowserElement(), michael@0: aLoadContextInfo->AppId())); michael@0: michael@0: nsresult rv; michael@0: michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread()); michael@0: michael@0: MOZ_ASSERT(!aLoadContextInfo->IsPrivate()); michael@0: if (aLoadContextInfo->IsPrivate()) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: if (!mCacheDirectory) { michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: } michael@0: michael@0: if (mShuttingDown) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: if (!mTreeCreated) { michael@0: rv = CreateCacheTree(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // Doom all active handles that matches the load context michael@0: nsTArray > handles; michael@0: mHandles.GetActiveHandles(&handles); michael@0: michael@0: for (uint32_t i = 0; i < handles.Length(); ++i) { michael@0: bool equals; michael@0: rv = CacheFileUtils::KeyMatchesLoadContextInfo(handles[i]->Key(), michael@0: aLoadContextInfo, michael@0: &equals); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in " michael@0: "handle! [handle=%p, key=%s]", handles[i].get(), michael@0: handles[i]->Key().get())); michael@0: MOZ_CRASH("Unexpected error!"); michael@0: } michael@0: michael@0: if (equals) { michael@0: rv = DoomFileInternal(handles[i]); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle" michael@0: " [handle=%p]", handles[i].get())); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!mContextEvictor) { michael@0: mContextEvictor = new CacheFileContextEvictor(); michael@0: mContextEvictor->Init(mCacheDirectory); michael@0: } michael@0: michael@0: mContextEvictor->AddContext(aLoadContextInfo); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::CacheIndexStateChanged() michael@0: { michael@0: LOG(("CacheFileIOManager::CacheIndexStateChanged()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: // CacheFileIOManager lives longer than CacheIndex so gInstance must be michael@0: // non-null here. michael@0: MOZ_ASSERT(gInstance); michael@0: michael@0: // We have to re-distatch even if we are on IO thread to prevent reentering michael@0: // the lock in CacheIndex michael@0: nsCOMPtr ev; michael@0: ev = NS_NewRunnableMethod( michael@0: gInstance, &CacheFileIOManager::CacheIndexStateChangedInternal); michael@0: michael@0: nsCOMPtr ioTarget = IOTarget(); michael@0: MOZ_ASSERT(ioTarget); michael@0: michael@0: rv = ioTarget->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::CacheIndexStateChangedInternal() michael@0: { michael@0: if (mShuttingDown) { michael@0: // ignore notification during shutdown michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mContextEvictor) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mContextEvictor->CacheIndexStateChanged(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::TrashDirectory(nsIFile *aFile) michael@0: { michael@0: #ifdef PR_LOGGING michael@0: nsAutoCString path; michael@0: aFile->GetNativePath(path); michael@0: #endif michael@0: LOG(("CacheFileIOManager::TrashDirectory() [file=%s]", path.get())); michael@0: michael@0: nsresult rv; michael@0: michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread()); michael@0: MOZ_ASSERT(mCacheDirectory); michael@0: michael@0: // When the directory is empty, it is cheaper to remove it directly instead of michael@0: // using the trash mechanism. michael@0: bool isEmpty; michael@0: rv = IsEmptyDirectory(aFile, &isEmpty); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (isEmpty) { michael@0: rv = aFile->Remove(false); michael@0: LOG(("CacheFileIOManager::TrashDirectory() - Directory removed [rv=0x%08x]", michael@0: rv)); michael@0: return rv; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: nsCOMPtr dirCheck; michael@0: rv = aFile->GetParent(getter_AddRefs(dirCheck)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool equals = false; michael@0: rv = dirCheck->Equals(mCacheDirectory, &equals); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MOZ_ASSERT(equals); michael@0: #endif michael@0: michael@0: nsCOMPtr dir, trash; michael@0: nsAutoCString leaf; michael@0: michael@0: rv = aFile->Clone(getter_AddRefs(dir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aFile->Clone(getter_AddRefs(trash)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: srand(static_cast(PR_Now())); michael@0: while (true) { michael@0: leaf = kTrashDir; michael@0: leaf.AppendInt(rand()); michael@0: rv = trash->SetNativeLeafName(leaf); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool exists; michael@0: if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: LOG(("CacheFileIOManager::TrashDirectory() - Renaming directory [leaf=%s]", michael@0: leaf.get())); michael@0: michael@0: rv = dir->MoveToNative(nullptr, leaf); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: StartRemovingTrash(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: CacheFileIOManager::OnTrashTimer(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: LOG(("CacheFileIOManager::OnTrashTimer() [timer=%p, closure=%p]", aTimer, michael@0: aClosure)); michael@0: michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (!ioMan) { michael@0: return; michael@0: } michael@0: michael@0: ioMan->mTrashTimer = nullptr; michael@0: ioMan->StartRemovingTrash(); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::StartRemovingTrash() michael@0: { michael@0: LOG(("CacheFileIOManager::StartRemovingTrash()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread()); michael@0: michael@0: if (mShuttingDown) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: if (!mCacheDirectory) { michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: } michael@0: michael@0: if (mTrashTimer) { michael@0: LOG(("CacheFileIOManager::StartRemovingTrash() - Trash timer exists.")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mRemovingTrashDirs) { michael@0: LOG(("CacheFileIOManager::StartRemovingTrash() - Trash removing in " michael@0: "progress.")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds(); michael@0: if (elapsed < kRemoveTrashStartDelay) { michael@0: nsCOMPtr timer = do_CreateInstance("@mozilla.org/timer;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr ioTarget = IOTarget(); michael@0: MOZ_ASSERT(ioTarget); michael@0: michael@0: rv = timer->SetTarget(ioTarget); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = timer->InitWithFuncCallback(CacheFileIOManager::OnTrashTimer, nullptr, michael@0: kRemoveTrashStartDelay - elapsed, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mTrashTimer.swap(timer); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr ev; michael@0: ev = NS_NewRunnableMethod(this, michael@0: &CacheFileIOManager::RemoveTrashInternal); michael@0: michael@0: rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mRemovingTrashDirs = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::RemoveTrashInternal() michael@0: { michael@0: LOG(("CacheFileIOManager::RemoveTrashInternal()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread()); michael@0: michael@0: if (mShuttingDown) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: MOZ_ASSERT(!mTrashTimer); michael@0: MOZ_ASSERT(mRemovingTrashDirs); michael@0: michael@0: if (!mTreeCreated) { michael@0: rv = CreateCacheTree(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // mRemovingTrashDirs is accessed only on IO thread, so we can drop the flag michael@0: // here and set it again once we dispatch a continuation event. By doing so, michael@0: // we don't have to drop the flag on any possible early return. michael@0: mRemovingTrashDirs = false; michael@0: michael@0: while (true) { michael@0: if (CacheIOThread::YieldAndRerun()) { michael@0: LOG(("CacheFileIOManager::RemoveTrashInternal() - Breaking loop for " michael@0: "higher level events.")); michael@0: mRemovingTrashDirs = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Find some trash directory michael@0: if (!mTrashDir) { michael@0: MOZ_ASSERT(!mTrashDirEnumerator); michael@0: michael@0: rv = FindTrashDirToRemove(); michael@0: if (rv == NS_ERROR_NOT_AVAILABLE) { michael@0: LOG(("CacheFileIOManager::RemoveTrashInternal() - No trash directory " michael@0: "found.")); michael@0: return NS_OK; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr enumerator; michael@0: rv = mTrashDir->GetDirectoryEntries(getter_AddRefs(enumerator)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mTrashDirEnumerator = do_QueryInterface(enumerator, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: continue; // check elapsed time michael@0: } michael@0: michael@0: // We null out mTrashDirEnumerator once we remove all files in the michael@0: // directory, so remove the trash directory if we don't have enumerator. michael@0: if (!mTrashDirEnumerator) { michael@0: rv = mTrashDir->Remove(false); michael@0: if (NS_FAILED(rv)) { michael@0: // There is no reason why removing an empty directory should fail, but michael@0: // if it does, we should continue and try to remove all other trash michael@0: // directories. michael@0: nsAutoCString leafName; michael@0: mTrashDir->GetNativeLeafName(leafName); michael@0: mFailedTrashDirs.AppendElement(leafName); michael@0: LOG(("CacheFileIOManager::RemoveTrashInternal() - Cannot remove " michael@0: "trashdir. [name=%s]", leafName.get())); michael@0: } michael@0: michael@0: mTrashDir = nullptr; michael@0: continue; // check elapsed time michael@0: } michael@0: michael@0: nsCOMPtr file; michael@0: rv = mTrashDirEnumerator->GetNextFile(getter_AddRefs(file)); michael@0: if (!file) { michael@0: mTrashDirEnumerator->Close(); michael@0: mTrashDirEnumerator = nullptr; michael@0: continue; // check elapsed time michael@0: } else { michael@0: bool isDir = false; michael@0: file->IsDirectory(&isDir); michael@0: if (isDir) { michael@0: NS_WARNING("Found a directory in a trash directory! It will be removed " michael@0: "recursively, but this can block IO thread for a while!"); michael@0: #ifdef PR_LOGGING michael@0: nsAutoCString path; michael@0: file->GetNativePath(path); michael@0: #endif michael@0: LOG(("CacheFileIOManager::RemoveTrashInternal() - Found a directory in a trash " michael@0: "directory! It will be removed recursively, but this can block IO " michael@0: "thread for a while! [file=%s]", path.get())); michael@0: } michael@0: file->Remove(isDir); michael@0: } michael@0: } michael@0: michael@0: NS_NOTREACHED("We should never get here"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::FindTrashDirToRemove() michael@0: { michael@0: LOG(("CacheFileIOManager::FindTrashDirToRemove()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: // We call this method on the main thread during shutdown when user wants to michael@0: // remove all cache files. michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread() || mShuttingDown); michael@0: michael@0: nsCOMPtr iter; michael@0: rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(iter)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool more; michael@0: nsCOMPtr elem; michael@0: michael@0: while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) { michael@0: rv = iter->GetNext(getter_AddRefs(elem)); michael@0: if (NS_FAILED(rv)) { michael@0: continue; michael@0: } michael@0: michael@0: nsCOMPtr file = do_QueryInterface(elem); michael@0: if (!file) { michael@0: continue; michael@0: } michael@0: michael@0: bool isDir = false; michael@0: file->IsDirectory(&isDir); michael@0: if (!isDir) { michael@0: continue; michael@0: } michael@0: michael@0: nsAutoCString leafName; michael@0: rv = file->GetNativeLeafName(leafName); michael@0: if (NS_FAILED(rv)) { michael@0: continue; michael@0: } michael@0: michael@0: if (leafName.Length() < strlen(kTrashDir)) { michael@0: continue; michael@0: } michael@0: michael@0: if (!StringBeginsWith(leafName, NS_LITERAL_CSTRING(kTrashDir))) { michael@0: continue; michael@0: } michael@0: michael@0: if (mFailedTrashDirs.Contains(leafName)) { michael@0: continue; michael@0: } michael@0: michael@0: LOG(("CacheFileIOManager::FindTrashDirToRemove() - Returning directory %s", michael@0: leafName.get())); michael@0: michael@0: mTrashDir = file; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // When we're here we've tried to delete all trash directories. Clear michael@0: // mFailedTrashDirs so we will try to delete them again when we start removing michael@0: // trash directories next time. michael@0: mFailedTrashDirs.Clear(); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::InitIndexEntry(CacheFileHandle *aHandle, michael@0: uint32_t aAppId, michael@0: bool aAnonymous, michael@0: bool aInBrowser) michael@0: { michael@0: LOG(("CacheFileIOManager::InitIndexEntry() [handle=%p, appId=%u, anonymous=%d" michael@0: ", inBrowser=%d]", aHandle, aAppId, aAnonymous, aInBrowser)); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (aHandle->IsClosed() || !ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: if (aHandle->IsSpecialFile()) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsRefPtr ev = michael@0: new InitIndexEntryEvent(aHandle, aAppId, aAnonymous, aInBrowser); michael@0: rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::UpdateIndexEntry(CacheFileHandle *aHandle, michael@0: const uint32_t *aFrecency, michael@0: const uint32_t *aExpirationTime) michael@0: { michael@0: LOG(("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, " michael@0: "expirationTime=%s]", aHandle, michael@0: aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "", michael@0: aExpirationTime ? nsPrintfCString("%u", *aExpirationTime).get() : "")); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ioMan = gInstance; michael@0: michael@0: if (aHandle->IsClosed() || !ioMan) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: if (aHandle->IsSpecialFile()) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsRefPtr ev = michael@0: new UpdateIndexEntryEvent(aHandle, aFrecency, aExpirationTime); michael@0: rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::CreateFile(CacheFileHandle *aHandle) michael@0: { michael@0: MOZ_ASSERT(!aHandle->mFD); michael@0: MOZ_ASSERT(aHandle->mFile); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (aHandle->IsDoomed()) { michael@0: nsCOMPtr file; michael@0: michael@0: rv = GetDoomedFile(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aHandle->mFile.swap(file); michael@0: } else { michael@0: bool exists; michael@0: if (NS_SUCCEEDED(aHandle->mFile->Exists(&exists)) && exists) { michael@0: NS_WARNING("Found a file that should not exist!"); michael@0: } michael@0: } michael@0: michael@0: rv = OpenNSPRHandle(aHandle, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aHandle->mFileSize = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: CacheFileIOManager::HashToStr(const SHA1Sum::Hash *aHash, nsACString &_retval) michael@0: { michael@0: _retval.Assign(""); michael@0: const char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', michael@0: '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; michael@0: for (uint32_t i=0 ; i> 4]); michael@0: _retval.Append(hexChars[(*aHash)[i] & 0xF]); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheFileIOManager::StrToHash(const nsACString &aHash, SHA1Sum::Hash *_retval) michael@0: { michael@0: if (aHash.Length() != 2*sizeof(SHA1Sum::Hash)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: for (uint32_t i=0 ; i= '0' && aHash[i] <= '9') { michael@0: value = aHash[i] - '0'; michael@0: } else if (aHash[i] >= 'A' && aHash[i] <= 'F') { michael@0: value = aHash[i] - 'A' + 10; michael@0: } else if (aHash[i] >= 'a' && aHash[i] <= 'f') { michael@0: value = aHash[i] - 'a' + 10; michael@0: } else { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: if (i%2 == 0) { michael@0: (reinterpret_cast(_retval))[i/2] = value << 4; michael@0: } else { michael@0: (reinterpret_cast(_retval))[i/2] += value; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::GetFile(const SHA1Sum::Hash *aHash, nsIFile **_retval) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr file; michael@0: rv = mCacheDirectory->Clone(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = file->AppendNative(NS_LITERAL_CSTRING(kEntriesDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString leafName; michael@0: HashToStr(aHash, leafName); michael@0: michael@0: rv = file->AppendNative(leafName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: file.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::GetSpecialFile(const nsACString &aKey, nsIFile **_retval) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr file; michael@0: rv = mCacheDirectory->Clone(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = file->AppendNative(aKey); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: file.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::GetDoomedFile(nsIFile **_retval) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr file; michael@0: rv = mCacheDirectory->Clone(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = file->AppendNative(NS_LITERAL_CSTRING(kDoomedDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = file->AppendNative(NS_LITERAL_CSTRING("dummyleaf")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: srand(static_cast(PR_Now())); michael@0: nsAutoCString leafName; michael@0: uint32_t iter=0; michael@0: while (true) { michael@0: iter++; michael@0: leafName.AppendInt(rand()); michael@0: rv = file->SetNativeLeafName(leafName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool exists; michael@0: if (NS_SUCCEEDED(file->Exists(&exists)) && !exists) { michael@0: break; michael@0: } michael@0: michael@0: leafName.Truncate(); michael@0: } michael@0: michael@0: // Telemetry::Accumulate(Telemetry::DISK_CACHE_GETDOOMEDFILE_ITERATIONS, iter); michael@0: michael@0: file.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::IsEmptyDirectory(nsIFile *aFile, bool *_retval) michael@0: { michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread()); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr enumerator; michael@0: rv = aFile->GetDirectoryEntries(getter_AddRefs(enumerator)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasMoreElements = false; michael@0: rv = enumerator->HasMoreElements(&hasMoreElements); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *_retval = !hasMoreElements; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::CheckAndCreateDir(nsIFile *aFile, const char *aDir, michael@0: bool aEnsureEmptyDir) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr file; michael@0: if (!aDir) { michael@0: file = aFile; michael@0: } else { michael@0: nsAutoCString dir(aDir); michael@0: rv = aFile->Clone(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = file->AppendNative(dir); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: bool exists = false; michael@0: rv = file->Exists(&exists); michael@0: if (NS_SUCCEEDED(rv) && exists) { michael@0: bool isDirectory = false; michael@0: rv = file->IsDirectory(&isDirectory); michael@0: if (NS_FAILED(rv) || !isDirectory) { michael@0: // Try to remove the file michael@0: rv = file->Remove(false); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: exists = false; michael@0: } michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (aEnsureEmptyDir && NS_SUCCEEDED(rv) && exists) { michael@0: bool isEmpty; michael@0: rv = IsEmptyDirectory(file, &isEmpty); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!isEmpty) { michael@0: rv = TrashDirectory(file); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: exists = false; michael@0: } michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv) && !exists) { michael@0: rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700); michael@0: } michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Cannot create directory"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::CreateCacheTree() michael@0: { michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread()); michael@0: MOZ_ASSERT(!mTreeCreated); michael@0: michael@0: if (!mCacheDirectory) { michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: // ensure parent directory exists michael@0: nsCOMPtr parentDir; michael@0: rv = mCacheDirectory->GetParent(getter_AddRefs(parentDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = CheckAndCreateDir(parentDir, nullptr, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // ensure cache directory exists michael@0: rv = CheckAndCreateDir(mCacheDirectory, nullptr, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // ensure entries directory exists michael@0: rv = CheckAndCreateDir(mCacheDirectory, kEntriesDir, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // ensure doomed directory exists michael@0: rv = CheckAndCreateDir(mCacheDirectory, kDoomedDir, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mTreeCreated = true; michael@0: michael@0: if (!mContextEvictor) { michael@0: nsRefPtr contextEvictor; michael@0: contextEvictor = new CacheFileContextEvictor(); michael@0: michael@0: // Init() method will try to load unfinished contexts from the disk. Store michael@0: // the evictor as a member only when there is some unfinished job. michael@0: contextEvictor->Init(mCacheDirectory); michael@0: if (contextEvictor->ContextsCount()) { michael@0: contextEvictor.swap(mContextEvictor); michael@0: } michael@0: } michael@0: michael@0: StartRemovingTrash(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate) michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: MOZ_ASSERT(!aHandle->mFD); michael@0: MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex); michael@0: MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit); michael@0: MOZ_ASSERT((aCreate && !aHandle->mFileExists) || michael@0: (!aCreate && aHandle->mFileExists)); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) { michael@0: // close handle that hasn't been used for the longest time michael@0: rv = ReleaseNSPRHandleInternal(mHandlesByLastUsed[0]); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (aCreate) { michael@0: rv = aHandle->mFile->OpenNSPRFileDesc( michael@0: PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aHandle->mFileExists = true; michael@0: } else { michael@0: rv = aHandle->mFile->OpenNSPRFileDesc(PR_RDWR, 0600, &aHandle->mFD); michael@0: if (NS_ERROR_FILE_NOT_FOUND == rv) { michael@0: LOG((" file doesn't exists")); michael@0: aHandle->mFileExists = false; michael@0: return DoomFileInternal(aHandle); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mHandlesByLastUsed.AppendElement(aHandle); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CacheFileIOManager::NSPRHandleUsed(CacheFileHandle *aHandle) michael@0: { michael@0: MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); michael@0: MOZ_ASSERT(aHandle->mFD); michael@0: michael@0: DebugOnly found; michael@0: found = mHandlesByLastUsed.RemoveElement(aHandle); michael@0: MOZ_ASSERT(found); michael@0: michael@0: mHandlesByLastUsed.AppendElement(aHandle); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::SyncRemoveDir(nsIFile *aFile, const char *aDir) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr file; michael@0: michael@0: if (!aDir) { michael@0: file = aFile; michael@0: } else { michael@0: rv = aFile->Clone(getter_AddRefs(file)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: rv = file->AppendNative(nsDependentCString(aDir)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: nsAutoCString path; michael@0: file->GetNativePath(path); michael@0: #endif michael@0: michael@0: LOG(("CacheFileIOManager::SyncRemoveDir() - Removing directory %s", michael@0: path.get())); michael@0: michael@0: rv = file->Remove(true); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: LOG(("CacheFileIOManager::SyncRemoveDir() - Removing failed! [rv=0x%08x]", michael@0: rv)); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: CacheFileIOManager::SyncRemoveAllCacheFiles() michael@0: { michael@0: LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles()")); michael@0: michael@0: nsresult rv; michael@0: michael@0: SyncRemoveDir(mCacheDirectory, kEntriesDir); michael@0: SyncRemoveDir(mCacheDirectory, kDoomedDir); michael@0: michael@0: // Clear any intermediate state of trash dir enumeration. michael@0: mFailedTrashDirs.Clear(); michael@0: mTrashDir = nullptr; michael@0: michael@0: while (true) { michael@0: // FindTrashDirToRemove() fills mTrashDir if there is any trash directory. michael@0: rv = FindTrashDirToRemove(); michael@0: if (rv == NS_ERROR_NOT_AVAILABLE) { michael@0: LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles() - No trash directory " michael@0: "found.")); michael@0: break; michael@0: } michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles() - " michael@0: "FindTrashDirToRemove() returned an unexpected error. [rv=0x%08x]", michael@0: rv)); michael@0: break; michael@0: } michael@0: michael@0: rv = SyncRemoveDir(mTrashDir, nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: nsAutoCString leafName; michael@0: mTrashDir->GetNativeLeafName(leafName); michael@0: mFailedTrashDirs.AppendElement(leafName); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Returns default ("smart") size (in KB) of cache, given available disk space michael@0: // (also in KB) michael@0: static uint32_t michael@0: SmartCacheSize(const uint32_t availKB) michael@0: { michael@0: uint32_t maxSize = kMaxCacheSizeKB; michael@0: michael@0: if (availKB > 100 * 1024 * 1024) { michael@0: return maxSize; // skip computing if we're over 100 GB michael@0: } michael@0: michael@0: // Grow/shrink in 10 MB units, deliberately, so that in the common case we michael@0: // don't shrink cache and evict items every time we startup (it's important michael@0: // that we don't slow down startup benchmarks). michael@0: uint32_t sz10MBs = 0; michael@0: uint32_t avail10MBs = availKB / (1024*10); michael@0: michael@0: // .5% of space above 25 GB michael@0: if (avail10MBs > 2500) { michael@0: sz10MBs += static_cast((avail10MBs - 2500)*.005); michael@0: avail10MBs = 2500; michael@0: } michael@0: // 1% of space between 7GB -> 25 GB michael@0: if (avail10MBs > 700) { michael@0: sz10MBs += static_cast((avail10MBs - 700)*.01); michael@0: avail10MBs = 700; michael@0: } michael@0: // 5% of space between 500 MB -> 7 GB michael@0: if (avail10MBs > 50) { michael@0: sz10MBs += static_cast((avail10MBs - 50)*.05); michael@0: avail10MBs = 50; michael@0: } michael@0: michael@0: #ifdef ANDROID michael@0: // On Android, smaller/older devices may have very little storage and michael@0: // device owners may be sensitive to storage footprint: Use a smaller michael@0: // percentage of available space and a smaller minimum. michael@0: michael@0: // 20% of space up to 500 MB (10 MB min) michael@0: sz10MBs += std::max(1, static_cast(avail10MBs * .2)); michael@0: #else michael@0: // 40% of space up to 500 MB (50 MB min) michael@0: sz10MBs += std::max(5, static_cast(avail10MBs * .4)); michael@0: #endif michael@0: michael@0: return std::min(maxSize, sz10MBs * 10 * 1024); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileIOManager::UpdateSmartCacheSize() michael@0: { michael@0: MOZ_ASSERT(mIOThread->IsCurrentThread()); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!CacheObserver::UseNewCache()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (!CacheObserver::SmartCacheSizeEnabled()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // Wait at least kSmartSizeUpdateInterval before recomputing smart size. michael@0: static const TimeDuration kUpdateLimit = michael@0: TimeDuration::FromMilliseconds(kSmartSizeUpdateInterval); michael@0: if (!mLastSmartSizeTime.IsNull() && michael@0: (TimeStamp::NowLoRes() - mLastSmartSizeTime) < kUpdateLimit) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Do not compute smart size when cache size is not reliable. michael@0: bool isUpToDate = false; michael@0: CacheIndex::IsUpToDate(&isUpToDate); michael@0: if (!isUpToDate) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: uint32_t cacheUsage; michael@0: rv = CacheIndex::GetCacheSize(&cacheUsage); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: LOG(("CacheFileIOManager::UpdateSmartCacheSize() - Cannot get cacheUsage! " michael@0: "[rv=0x%08x]", rv)); michael@0: return rv; michael@0: } michael@0: michael@0: int64_t avail; michael@0: rv = mCacheDirectory->GetDiskSpaceAvailable(&avail); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: // Do not change smart size. michael@0: LOG(("CacheFileIOManager::UpdateSmartCacheSize() - GetDiskSpaceAvailable() " michael@0: "failed! [rv=0x%08x]", rv)); michael@0: return rv; michael@0: } michael@0: michael@0: mLastSmartSizeTime = TimeStamp::NowLoRes(); michael@0: michael@0: uint32_t smartSize = SmartCacheSize(static_cast(avail / 1024) + michael@0: cacheUsage); michael@0: michael@0: if (smartSize == (CacheObserver::DiskCacheCapacity() >> 10)) { michael@0: // Smart size has not changed. michael@0: return NS_OK; michael@0: } michael@0: michael@0: CacheObserver::SetDiskCacheCapacity(smartSize << 10); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Memory reporting michael@0: michael@0: namespace { // anon michael@0: michael@0: // A helper class that dispatches and waits for an event that gets result of michael@0: // CacheFileIOManager->mHandles.SizeOfExcludingThis() on the I/O thread michael@0: // to safely get handles memory report. michael@0: // We must do this, since the handle list is only accessed and managed w/o michael@0: // locking on the I/O thread. That is by design. michael@0: class SizeOfHandlesRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: SizeOfHandlesRunnable(mozilla::MallocSizeOf mallocSizeOf, michael@0: CacheFileHandles const &handles, michael@0: nsTArray const &specialHandles) michael@0: : mMonitor("SizeOfHandlesRunnable.mMonitor") michael@0: , mMallocSizeOf(mallocSizeOf) michael@0: , mHandles(handles) michael@0: , mSpecialHandles(specialHandles) michael@0: { michael@0: } michael@0: michael@0: size_t Get(CacheIOThread* thread) michael@0: { michael@0: nsCOMPtr target = thread->Target(); michael@0: if (!target) { michael@0: NS_ERROR("If we have the I/O thread we also must have the I/O target"); michael@0: return 0; michael@0: } michael@0: michael@0: mozilla::MonitorAutoLock mon(mMonitor); michael@0: nsresult rv = target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: NS_ERROR("Dispatch failed, cannot do memory report of CacheFileHandles"); michael@0: return 0; michael@0: } michael@0: michael@0: mon.Wait(); michael@0: return mSize; michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: mozilla::MonitorAutoLock mon(mMonitor); michael@0: // Excluding this since the object itself is a member of CacheFileIOManager michael@0: // reported in CacheFileIOManager::SizeOfIncludingThis as part of |this|. michael@0: mSize = mHandles.SizeOfExcludingThis(mMallocSizeOf); michael@0: for (uint32_t i = 0; i < mSpecialHandles.Length(); ++i) { michael@0: mSize += mSpecialHandles[i]->SizeOfIncludingThis(mMallocSizeOf); michael@0: } michael@0: michael@0: mon.Notify(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: mozilla::Monitor mMonitor; michael@0: mozilla::MallocSizeOf mMallocSizeOf; michael@0: CacheFileHandles const &mHandles; michael@0: nsTArray const &mSpecialHandles; michael@0: size_t mSize; michael@0: }; michael@0: michael@0: } // anon michael@0: michael@0: size_t michael@0: CacheFileIOManager::SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: size_t n = 0; michael@0: nsCOMPtr sizeOf; michael@0: michael@0: if (mIOThread) { michael@0: n += mIOThread->SizeOfIncludingThis(mallocSizeOf); michael@0: michael@0: // mHandles and mSpecialHandles must be accessed only on the I/O thread, michael@0: // must sync dispatch. michael@0: nsRefPtr sizeOfHandlesRunnable = michael@0: new SizeOfHandlesRunnable(mallocSizeOf, mHandles, mSpecialHandles); michael@0: n += sizeOfHandlesRunnable->Get(mIOThread); michael@0: } michael@0: michael@0: // mHandlesByLastUsed just refers handles reported by mHandles. michael@0: michael@0: sizeOf = do_QueryInterface(mCacheDirectory); michael@0: if (sizeOf) michael@0: n += sizeOf->SizeOfIncludingThis(mallocSizeOf); michael@0: michael@0: sizeOf = do_QueryInterface(mMetadataWritesTimer); michael@0: if (sizeOf) michael@0: n += sizeOf->SizeOfIncludingThis(mallocSizeOf); michael@0: michael@0: sizeOf = do_QueryInterface(mTrashTimer); michael@0: if (sizeOf) michael@0: n += sizeOf->SizeOfIncludingThis(mallocSizeOf); michael@0: michael@0: sizeOf = do_QueryInterface(mTrashDir); michael@0: if (sizeOf) michael@0: n += sizeOf->SizeOfIncludingThis(mallocSizeOf); michael@0: michael@0: for (uint32_t i = 0; i < mFailedTrashDirs.Length(); ++i) { michael@0: n += mFailedTrashDirs[i].SizeOfExcludingThisIfUnshared(mallocSizeOf); michael@0: } michael@0: michael@0: return n; michael@0: } michael@0: michael@0: // static michael@0: size_t michael@0: CacheFileIOManager::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) michael@0: { michael@0: if (!gInstance) michael@0: return 0; michael@0: michael@0: return gInstance->SizeOfExcludingThisInternal(mallocSizeOf); michael@0: } michael@0: michael@0: // static michael@0: size_t michael@0: CacheFileIOManager::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) michael@0: { michael@0: return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: } // net michael@0: } // mozilla