/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CacheLog.h" #include "CacheFileChunk.h" #include "CacheFile.h" #include "nsThreadUtils.h" #include "mozilla/IntegerPrintfMacros.h" namespace mozilla::net { #define kMinBufSize 512 CacheFileChunkBuffer::CacheFileChunkBuffer(CacheFileChunk* aChunk) : mChunk(aChunk), mBuf(nullptr), mBufSize(0), mDataSize(0), mReadHandlesCount(0), mWriteHandleExists(false) {} CacheFileChunkBuffer::~CacheFileChunkBuffer() { if (mBuf) { CacheFileUtils::FreeBuffer(mBuf); mBuf = nullptr; mChunk->BuffersAllocationChanged(mBufSize, 0); mBufSize = 0; } } void CacheFileChunkBuffer::CopyFrom(CacheFileChunkBuffer* aOther) { MOZ_RELEASE_ASSERT(mBufSize >= aOther->mDataSize); mDataSize = aOther->mDataSize; memcpy(mBuf, aOther->mBuf, mDataSize); } nsresult CacheFileChunkBuffer::FillInvalidRanges( CacheFileChunkBuffer* aOther, CacheFileUtils::ValidityMap* aMap) { nsresult rv; rv = EnsureBufSize(aOther->mDataSize); if (NS_FAILED(rv)) { return rv; } uint32_t invalidOffset = 0; uint32_t invalidLength; for (uint32_t i = 0; i < aMap->Length(); ++i) { uint32_t validOffset = (*aMap)[i].Offset(); uint32_t validLength = (*aMap)[i].Len(); MOZ_RELEASE_ASSERT(invalidOffset <= validOffset); invalidLength = validOffset - invalidOffset; if (invalidLength > 0) { MOZ_RELEASE_ASSERT(invalidOffset + invalidLength <= aOther->mDataSize); memcpy(mBuf + invalidOffset, aOther->mBuf + invalidOffset, invalidLength); } invalidOffset = validOffset + validLength; } if (invalidOffset < aOther->mDataSize) { invalidLength = aOther->mDataSize - invalidOffset; memcpy(mBuf + invalidOffset, aOther->mBuf + invalidOffset, invalidLength); } return NS_OK; } [[nodiscard]] nsresult CacheFileChunkBuffer::EnsureBufSize(uint32_t aBufSize) { AssertOwnsLock(); if (mBufSize >= aBufSize) { return NS_OK; } // find smallest power of 2 greater than or equal to aBufSize aBufSize--; aBufSize |= aBufSize >> 1; aBufSize |= aBufSize >> 2; aBufSize |= aBufSize >> 4; aBufSize |= aBufSize >> 8; aBufSize |= aBufSize >> 16; aBufSize++; const uint32_t minBufSize = kMinBufSize; const uint32_t maxBufSize = kChunkSize; aBufSize = clamped(aBufSize, minBufSize, maxBufSize); if (!mChunk->CanAllocate(aBufSize - mBufSize)) { return NS_ERROR_OUT_OF_MEMORY; } char* newBuf = static_cast(realloc(mBuf, aBufSize)); if (!newBuf) { return NS_ERROR_OUT_OF_MEMORY; } mChunk->BuffersAllocationChanged(mBufSize, aBufSize); mBuf = newBuf; mBufSize = aBufSize; return NS_OK; } void CacheFileChunkBuffer::SetDataSize(uint32_t aDataSize) { MOZ_RELEASE_ASSERT( // EnsureBufSize must be called before SetDataSize, so the new data size // is guaranteed to be smaller than or equal to mBufSize. aDataSize <= mBufSize || // The only exception is an optimization when we read the data from the // disk. The data is read to a separate buffer and CacheFileChunk::mBuf is // empty (see CacheFileChunk::Read). We need to set mBuf::mDataSize // accordingly so that DataSize() methods return correct value, but we // don't want to allocate the buffer since it wouldn't be used in most // cases. (mBufSize == 0 && mChunk->mState == CacheFileChunk::READING)); mDataSize = aDataSize; } void CacheFileChunkBuffer::AssertOwnsLock() const { mChunk->AssertOwnsLock(); } void CacheFileChunkBuffer::RemoveReadHandle() { AssertOwnsLock(); MOZ_RELEASE_ASSERT(mReadHandlesCount); MOZ_RELEASE_ASSERT(!mWriteHandleExists); mReadHandlesCount--; if (mReadHandlesCount == 0 && mChunk->mBuf != this) { DebugOnly removed = mChunk->mOldBufs.RemoveElement(this); MOZ_ASSERT(removed); } } void CacheFileChunkBuffer::RemoveWriteHandle() { AssertOwnsLock(); MOZ_RELEASE_ASSERT(mReadHandlesCount == 0); MOZ_RELEASE_ASSERT(mWriteHandleExists); mWriteHandleExists = false; } size_t CacheFileChunkBuffer::SizeOfIncludingThis( mozilla::MallocSizeOf mallocSizeOf) const { size_t n = mallocSizeOf(this); if (mBuf) { n += mallocSizeOf(mBuf); } return n; } uint32_t CacheFileChunkHandle::DataSize() { MOZ_ASSERT(mBuf, "Unexpected call on dummy handle"); mBuf->AssertOwnsLock(); return mBuf->mDataSize; } uint32_t CacheFileChunkHandle::Offset() { MOZ_ASSERT(mBuf, "Unexpected call on dummy handle"); mBuf->AssertOwnsLock(); return mBuf->mChunk->Index() * kChunkSize; } CacheFileChunkReadHandle::CacheFileChunkReadHandle(CacheFileChunkBuffer* aBuf) { mBuf = aBuf; mBuf->mReadHandlesCount++; } CacheFileChunkReadHandle::~CacheFileChunkReadHandle() { mBuf->RemoveReadHandle(); } const char* CacheFileChunkReadHandle::Buf() { return mBuf->mBuf; } CacheFileChunkWriteHandle::CacheFileChunkWriteHandle( CacheFileChunkBuffer* aBuf) { mBuf = aBuf; if (mBuf) { MOZ_ASSERT(!mBuf->mWriteHandleExists); mBuf->mWriteHandleExists = true; } } CacheFileChunkWriteHandle::~CacheFileChunkWriteHandle() { if (mBuf) { mBuf->RemoveWriteHandle(); } } char* CacheFileChunkWriteHandle::Buf() { return mBuf ? mBuf->mBuf : nullptr; } void CacheFileChunkWriteHandle::UpdateDataSize(uint32_t aOffset, uint32_t aLen) { MOZ_ASSERT(mBuf, "Write performed on dummy handle?"); MOZ_ASSERT(aOffset <= mBuf->mDataSize); MOZ_ASSERT(aOffset + aLen <= mBuf->mBufSize); if (aOffset + aLen > mBuf->mDataSize) { mBuf->mDataSize = aOffset + aLen; } mBuf->mChunk->UpdateDataSize(aOffset, aLen); } class NotifyUpdateListenerEvent : public Runnable { public: NotifyUpdateListenerEvent(CacheFileChunkListener* aCallback, CacheFileChunk* aChunk) : Runnable("net::NotifyUpdateListenerEvent"), mCallback(aCallback), mChunk(aChunk) { LOG(("NotifyUpdateListenerEvent::NotifyUpdateListenerEvent() [this=%p]", this)); } protected: ~NotifyUpdateListenerEvent() { LOG(("NotifyUpdateListenerEvent::~NotifyUpdateListenerEvent() [this=%p]", this)); } public: NS_IMETHOD Run() override { LOG(("NotifyUpdateListenerEvent::Run() [this=%p]", this)); mCallback->OnChunkUpdated(mChunk); return NS_OK; } protected: nsCOMPtr mCallback; RefPtr mChunk; }; bool CacheFileChunk::DispatchRelease() { if (NS_IsMainThread()) { return false; } NS_DispatchToMainThread(NewNonOwningRunnableMethod( "net::CacheFileChunk::Release", this, &CacheFileChunk::Release)); return true; } NS_IMPL_ADDREF(CacheFileChunk) NS_IMETHODIMP_(MozExternalRefCountType) CacheFileChunk::Release() { nsrefcnt count = mRefCnt - 1; if (DispatchRelease()) { // Redispatched to the main thread. return count; } MOZ_ASSERT(0 != mRefCnt, "dup release"); count = --mRefCnt; NS_LOG_RELEASE(this, count, "CacheFileChunk"); if (0 == count) { mRefCnt = 1; delete (this); return 0; } // We can safely access this chunk after decreasing mRefCnt since we re-post // all calls to Release() happening off the main thread to the main thread. // I.e. no other Release() that would delete the object could be run before // we call CacheFile::DeactivateChunk(). // // NOTE: we don't grab the CacheFile's lock, so the chunk might be addrefed // on another thread before CacheFile::DeactivateChunk() grabs the lock on // this thread. To make sure we won't deactivate chunk that was just returned // to a new consumer we check mRefCnt once again in // CacheFile::DeactivateChunk() after we grab the lock. if (mActiveChunk && count == 1) { mFile->DeactivateChunk(this); } return count; } NS_INTERFACE_MAP_BEGIN(CacheFileChunk) NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END CacheFileChunk::CacheFileChunk(CacheFile* aFile, uint32_t aIndex, bool aInitByWriter) : CacheMemoryConsumer(aFile->mOpenAsMemoryOnly ? MEMORY_ONLY : DONT_REPORT), mIndex(aIndex), mState(INITIAL), mStatus(NS_OK), mActiveChunk(false), mIsDirty(false), mDiscardedChunk(false), mBuffersSize(0), mLimitAllocation(!aFile->mOpenAsMemoryOnly && aInitByWriter), mIsPriority(aFile->mPriority), mExpectedHash(0), mFile(aFile) { LOG(("CacheFileChunk::CacheFileChunk() [this=%p, index=%u, initByWriter=%d]", this, aIndex, aInitByWriter)); mBuf = new CacheFileChunkBuffer(this); } CacheFileChunk::~CacheFileChunk() { LOG(("CacheFileChunk::~CacheFileChunk() [this=%p]", this)); } void CacheFileChunk::AssertOwnsLock() const { mFile->AssertOwnsLock(); } void CacheFileChunk::InitNew() { AssertOwnsLock(); LOG(("CacheFileChunk::InitNew() [this=%p]", this)); MOZ_ASSERT(mState == INITIAL); MOZ_ASSERT(NS_SUCCEEDED(mStatus)); MOZ_ASSERT(!mBuf->Buf()); MOZ_ASSERT(!mWritingStateHandle); MOZ_ASSERT(!mReadingStateBuf); MOZ_ASSERT(!mIsDirty); mBuf = new CacheFileChunkBuffer(this); mState = READY; } nsresult CacheFileChunk::Read(CacheFileHandle* aHandle, uint32_t aLen, CacheHash::Hash16_t aHash, CacheFileChunkListener* aCallback) { AssertOwnsLock(); LOG(("CacheFileChunk::Read() [this=%p, handle=%p, len=%d, listener=%p]", this, aHandle, aLen, aCallback)); MOZ_ASSERT(mState == INITIAL); MOZ_ASSERT(NS_SUCCEEDED(mStatus)); MOZ_ASSERT(!mBuf->Buf()); MOZ_ASSERT(!mWritingStateHandle); MOZ_ASSERT(!mReadingStateBuf); MOZ_ASSERT(aLen); nsresult rv; mState = READING; RefPtr tmpBuf = new CacheFileChunkBuffer(this); rv = tmpBuf->EnsureBufSize(aLen); if (NS_FAILED(rv)) { SetError(rv); return mStatus; } tmpBuf->SetDataSize(aLen); rv = CacheFileIOManager::Read(aHandle, mIndex * kChunkSize, tmpBuf->Buf(), aLen, this); if (NS_WARN_IF(NS_FAILED(rv))) { rv = mIndex ? NS_ERROR_FILE_CORRUPTED : NS_ERROR_FILE_NOT_FOUND; SetError(rv); } else { mReadingStateBuf.swap(tmpBuf); mListener = aCallback; // mBuf contains no data but we set datasize to size of the data that will // be read from the disk. No handle is allowed to access the non-existent // data until reading finishes, but data can be appended or overwritten. // These pieces are tracked in mValidityMap and will be merged with the data // read from disk in OnDataRead(). mBuf->SetDataSize(aLen); mExpectedHash = aHash; } return rv; } nsresult CacheFileChunk::Write(CacheFileHandle* aHandle, CacheFileChunkListener* aCallback) { AssertOwnsLock(); LOG(("CacheFileChunk::Write() [this=%p, handle=%p, listener=%p]", this, aHandle, aCallback)); MOZ_ASSERT(mState == READY); MOZ_ASSERT(NS_SUCCEEDED(mStatus)); MOZ_ASSERT(!mWritingStateHandle); MOZ_ASSERT(mBuf->DataSize()); // Don't write chunk when it is empty MOZ_ASSERT(mBuf->ReadHandlesCount() == 0); MOZ_ASSERT(!mBuf->WriteHandleExists()); nsresult rv; mState = WRITING; mWritingStateHandle = MakeUnique(mBuf); rv = CacheFileIOManager::Write( aHandle, mIndex * kChunkSize, mWritingStateHandle->Buf(), mWritingStateHandle->DataSize(), false, false, this); if (NS_WARN_IF(NS_FAILED(rv))) { mWritingStateHandle = nullptr; SetError(rv); } else { mListener = aCallback; mIsDirty = false; } return rv; } void CacheFileChunk::WaitForUpdate(CacheFileChunkListener* aCallback) { AssertOwnsLock(); mFile->AssertOwnsLock(); // For thread-safety analysis LOG(("CacheFileChunk::WaitForUpdate() [this=%p, listener=%p]", this, aCallback)); MOZ_ASSERT(mFile->mOutput); MOZ_ASSERT(IsReady()); #ifdef DEBUG for (uint32_t i = 0; i < mUpdateListeners.Length(); i++) { MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback); } #endif ChunkListenerItem* item = new ChunkListenerItem(); item->mTarget = CacheFileIOManager::IOTarget(); if (!item->mTarget) { LOG( ("CacheFileChunk::WaitForUpdate() - Cannot get Cache I/O thread! Using " "main thread for callback.")); item->mTarget = GetMainThreadEventTarget(); } item->mCallback = aCallback; MOZ_ASSERT(item->mTarget); item->mCallback = aCallback; mUpdateListeners.AppendElement(item); } void CacheFileChunk::CancelWait(CacheFileChunkListener* aCallback) { AssertOwnsLock(); LOG(("CacheFileChunk::CancelWait() [this=%p, listener=%p]", this, aCallback)); MOZ_ASSERT(IsReady()); uint32_t i; for (i = 0; i < mUpdateListeners.Length(); i++) { ChunkListenerItem* item = mUpdateListeners[i]; if (item->mCallback == aCallback) { mUpdateListeners.RemoveElementAt(i); delete item; break; } } #ifdef DEBUG for (; i < mUpdateListeners.Length(); i++) { MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback); } #endif } nsresult CacheFileChunk::NotifyUpdateListeners() { AssertOwnsLock(); LOG(("CacheFileChunk::NotifyUpdateListeners() [this=%p]", this)); MOZ_ASSERT(IsReady()); nsresult rv, rv2; rv = NS_OK; for (uint32_t i = 0; i < mUpdateListeners.Length(); i++) { ChunkListenerItem* item = mUpdateListeners[i]; LOG( ("CacheFileChunk::NotifyUpdateListeners() - Notifying listener %p " "[this=%p]", item->mCallback.get(), this)); RefPtr ev; ev = new NotifyUpdateListenerEvent(item->mCallback, this); rv2 = item->mTarget->Dispatch(ev, NS_DISPATCH_NORMAL); if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) rv = rv2; delete item; } mUpdateListeners.Clear(); return rv; } uint32_t CacheFileChunk::Index() const { return mIndex; } CacheHash::Hash16_t CacheFileChunk::Hash() const { MOZ_ASSERT(IsReady()); return CacheHash::Hash16(mBuf->Buf(), mBuf->DataSize()); } uint32_t CacheFileChunk::DataSize() const { return mBuf->DataSize(); } void CacheFileChunk::UpdateDataSize(uint32_t aOffset, uint32_t aLen) { AssertOwnsLock(); mFile->AssertOwnsLock(); // For thread-safety analysis // UpdateDataSize() is called only when we've written some data to the chunk // and we never write data anymore once some error occurs. MOZ_ASSERT(NS_SUCCEEDED(mStatus)); LOG(("CacheFileChunk::UpdateDataSize() [this=%p, offset=%d, len=%d]", this, aOffset, aLen)); mIsDirty = true; int64_t fileSize = static_cast(kChunkSize) * mIndex + aOffset + aLen; bool notify = false; if (fileSize > mFile->mDataSize) { mFile->mDataSize = fileSize; notify = true; } if (mState == READY || mState == WRITING) { MOZ_ASSERT(mValidityMap.Length() == 0); if (notify) { NotifyUpdateListeners(); } return; } // We're still waiting for data from the disk. This chunk cannot be used by // input stream, so there must be no update listener. We also need to keep // track of where the data is written so that we can correctly merge the new // data with the old one. MOZ_ASSERT(mUpdateListeners.Length() == 0); MOZ_ASSERT(mState == READING); mValidityMap.AddPair(aOffset, aLen); mValidityMap.Log(); } void CacheFileChunk::Truncate(uint32_t aOffset) { MOZ_RELEASE_ASSERT(mState == READY || mState == WRITING || mState == READING); if (mState == READING) { mIsDirty = true; } mBuf->SetDataSize(aOffset); } nsresult CacheFileChunk::OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) { MOZ_CRASH("CacheFileChunk::OnFileOpened should not be called!"); return NS_ERROR_UNEXPECTED; } nsresult CacheFileChunk::OnDataWritten(CacheFileHandle* aHandle, const char* aBuf, nsresult aResult) { LOG(( "CacheFileChunk::OnDataWritten() [this=%p, handle=%p, result=0x%08" PRIx32 "]", this, aHandle, static_cast(aResult))); nsCOMPtr listener; { CacheFileAutoLock lock(mFile); MOZ_ASSERT(mState == WRITING); MOZ_ASSERT(mListener); mWritingStateHandle = nullptr; if (NS_WARN_IF(NS_FAILED(aResult))) { SetError(aResult); } mState = READY; mListener.swap(listener); } listener->OnChunkWritten(aResult, this); return NS_OK; } nsresult CacheFileChunk::OnDataRead(CacheFileHandle* aHandle, char* aBuf, nsresult aResult) { LOG(("CacheFileChunk::OnDataRead() [this=%p, handle=%p, result=0x%08" PRIx32 "]", this, aHandle, static_cast(aResult))); nsCOMPtr listener; { CacheFileAutoLock lock(mFile); MOZ_ASSERT(mState == READING); MOZ_ASSERT(mListener); MOZ_ASSERT(mReadingStateBuf); MOZ_RELEASE_ASSERT(mBuf->ReadHandlesCount() == 0); MOZ_RELEASE_ASSERT(!mBuf->WriteHandleExists()); RefPtr tmpBuf; tmpBuf.swap(mReadingStateBuf); if (NS_SUCCEEDED(aResult)) { CacheHash::Hash16_t hash = CacheHash::Hash16(tmpBuf->Buf(), tmpBuf->DataSize()); if (hash != mExpectedHash) { LOG( ("CacheFileChunk::OnDataRead() - Hash mismatch! Hash of the data is" " %hx, hash in metadata is %hx. [this=%p, idx=%d]", hash, mExpectedHash, this, mIndex)); aResult = NS_ERROR_FILE_CORRUPTED; } else { if (mBuf->DataSize() < tmpBuf->DataSize()) { // Truncate() was called while the data was being read. tmpBuf->SetDataSize(mBuf->DataSize()); } if (!mBuf->Buf()) { // Just swap the buffers if mBuf is still empty mBuf.swap(tmpBuf); } else { LOG(("CacheFileChunk::OnDataRead() - Merging buffers. [this=%p]", this)); mValidityMap.Log(); aResult = mBuf->FillInvalidRanges(tmpBuf, &mValidityMap); mValidityMap.Clear(); } } } if (NS_FAILED(aResult)) { aResult = mIndex ? NS_ERROR_FILE_CORRUPTED : NS_ERROR_FILE_NOT_FOUND; SetError(aResult); mBuf->SetDataSize(0); } mState = READY; mListener.swap(listener); } listener->OnChunkRead(aResult, this); return NS_OK; } nsresult CacheFileChunk::OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) { MOZ_CRASH("CacheFileChunk::OnFileDoomed should not be called!"); return NS_ERROR_UNEXPECTED; } nsresult CacheFileChunk::OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) { MOZ_CRASH("CacheFileChunk::OnEOFSet should not be called!"); return NS_ERROR_UNEXPECTED; } nsresult CacheFileChunk::OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) { MOZ_CRASH("CacheFileChunk::OnFileRenamed should not be called!"); return NS_ERROR_UNEXPECTED; } bool CacheFileChunk::IsKilled() { return mFile->IsKilled(); } bool CacheFileChunk::IsReady() const { return (NS_SUCCEEDED(mStatus) && (mState == READY || mState == WRITING)); } bool CacheFileChunk::IsDirty() const { AssertOwnsLock(); return mIsDirty; } nsresult CacheFileChunk::GetStatus() { return mStatus; } void CacheFileChunk::SetError(nsresult aStatus) { LOG(("CacheFileChunk::SetError() [this=%p, status=0x%08" PRIx32 "]", this, static_cast(aStatus))); MOZ_ASSERT(NS_FAILED(aStatus)); if (NS_FAILED(mStatus)) { // Remember only the first error code. return; } mStatus = aStatus; } CacheFileChunkReadHandle CacheFileChunk::GetReadHandle() { LOG(("CacheFileChunk::GetReadHandle() [this=%p]", this)); AssertOwnsLock(); MOZ_RELEASE_ASSERT(mState == READY || mState == WRITING); // We don't release the lock when writing the data and CacheFileOutputStream // doesn't get the read handle, so there cannot be a write handle when read // handle is obtained. MOZ_RELEASE_ASSERT(!mBuf->WriteHandleExists()); return CacheFileChunkReadHandle(mBuf); } CacheFileChunkWriteHandle CacheFileChunk::GetWriteHandle( uint32_t aEnsuredBufSize) { LOG(("CacheFileChunk::GetWriteHandle() [this=%p, ensuredBufSize=%u]", this, aEnsuredBufSize)); AssertOwnsLock(); if (NS_FAILED(mStatus)) { return CacheFileChunkWriteHandle(nullptr); // dummy handle } nsresult rv; // We don't support multiple write handles MOZ_RELEASE_ASSERT(!mBuf->WriteHandleExists()); if (mBuf->ReadHandlesCount()) { LOG( ("CacheFileChunk::GetWriteHandle() - cloning buffer because of existing" " read handle")); MOZ_RELEASE_ASSERT(mState != READING); RefPtr newBuf = new CacheFileChunkBuffer(this); rv = newBuf->EnsureBufSize(std::max(aEnsuredBufSize, mBuf->DataSize())); if (NS_SUCCEEDED(rv)) { newBuf->CopyFrom(mBuf); mOldBufs.AppendElement(mBuf); mBuf = newBuf; } } else { rv = mBuf->EnsureBufSize(aEnsuredBufSize); } if (NS_FAILED(rv)) { SetError(NS_ERROR_OUT_OF_MEMORY); return CacheFileChunkWriteHandle(nullptr); // dummy handle } return CacheFileChunkWriteHandle(mBuf); } // Memory reporting size_t CacheFileChunk::SizeOfExcludingThis( mozilla::MallocSizeOf mallocSizeOf) const { size_t n = mBuf->SizeOfIncludingThis(mallocSizeOf); if (mReadingStateBuf) { n += mReadingStateBuf->SizeOfIncludingThis(mallocSizeOf); } for (uint32_t i = 0; i < mOldBufs.Length(); ++i) { n += mOldBufs[i]->SizeOfIncludingThis(mallocSizeOf); } n += mValidityMap.SizeOfExcludingThis(mallocSizeOf); return n; } size_t CacheFileChunk::SizeOfIncludingThis( mozilla::MallocSizeOf mallocSizeOf) const { return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); } bool CacheFileChunk::CanAllocate(uint32_t aSize) const { if (!mLimitAllocation) { return true; } LOG(("CacheFileChunk::CanAllocate() [this=%p, size=%u]", this, aSize)); int64_t limit = CacheObserver::MaxDiskChunksMemoryUsage(mIsPriority); if (limit == 0) { return true; } limit <<= 10; if (limit > UINT32_MAX) { limit = UINT32_MAX; } int64_t usage = ChunksMemoryUsage(); if (usage + aSize > limit) { LOG(("CacheFileChunk::CanAllocate() - Returning false. [this=%p]", this)); return false; } return true; } void CacheFileChunk::BuffersAllocationChanged(uint32_t aFreed, uint32_t aAllocated) { uint32_t oldBuffersSize = mBuffersSize; mBuffersSize += aAllocated; mBuffersSize -= aFreed; DoMemoryReport(sizeof(CacheFileChunk) + mBuffersSize); if (!mLimitAllocation) { return; } ChunksMemoryUsage() -= oldBuffersSize; ChunksMemoryUsage() += mBuffersSize; LOG( ("CacheFileChunk::BuffersAllocationChanged() - %s chunks usage %u " "[this=%p]", mIsPriority ? "Priority" : "Normal", static_cast(ChunksMemoryUsage()), this)); } mozilla::Atomic& CacheFileChunk::ChunksMemoryUsage() const { static mozilla::Atomic chunksMemoryUsage(0); static mozilla::Atomic prioChunksMemoryUsage(0); return mIsPriority ? prioChunksMemoryUsage : chunksMemoryUsage; } } // namespace mozilla::net