summaryrefslogtreecommitdiffstats
path: root/netwerk/cache2/CacheFileChunk.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /netwerk/cache2/CacheFileChunk.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--netwerk/cache2/CacheFileChunk.cpp840
1 files changed, 840 insertions, 0 deletions
diff --git a/netwerk/cache2/CacheFileChunk.cpp b/netwerk/cache2/CacheFileChunk.cpp
new file mode 100644
index 0000000000..7dc0d9078c
--- /dev/null
+++ b/netwerk/cache2/CacheFileChunk.cpp
@@ -0,0 +1,840 @@
+/* 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<char*>(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<bool> 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<CacheFileChunkListener> mCallback;
+ RefPtr<CacheFileChunk> 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<CacheFileChunkBuffer> 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<CacheFileChunkReadHandle>(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 = GetMainThreadSerialEventTarget();
+ }
+ 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<NotifyUpdateListenerEvent> 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<int64_t>(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<uint32_t>(aResult)));
+
+ nsCOMPtr<CacheFileChunkListener> 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<uint32_t>(aResult)));
+
+ nsCOMPtr<CacheFileChunkListener> 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<CacheFileChunkBuffer> 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<uint32_t>(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<CacheFileChunkBuffer> 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<uint32_t>(ChunksMemoryUsage()), this));
+}
+
+mozilla::Atomic<uint32_t, ReleaseAcquire>& CacheFileChunk::ChunksMemoryUsage()
+ const {
+ static mozilla::Atomic<uint32_t, ReleaseAcquire> chunksMemoryUsage(0);
+ static mozilla::Atomic<uint32_t, ReleaseAcquire> prioChunksMemoryUsage(0);
+ return mIsPriority ? prioChunksMemoryUsage : chunksMemoryUsage;
+}
+
+} // namespace mozilla::net