When writing to alt-data output stream fails for whatever reason, we now try to truncate alternative data and keep the original data instead of dooming the whole entry. The patch also changes how is the predicted size passed to the cache. Instead of a dedicated method it's now an argument of openOutputStream and openAlternativeOutputStream methods which fail in case the entry would exceed the allowed limit.
937 lines
23 KiB
C++
937 lines
23 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "CacheLog.h"
|
|
#include "CacheFileChunk.h"
|
|
|
|
#include "CacheFile.h"
|
|
#include "nsThreadUtils.h"
|
|
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
|
|
namespace mozilla {
|
|
namespace 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;
|
|
}
|
|
|
|
MOZ_MUST_USE 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;
|
|
}
|
|
|
|
NS_PRECONDITION(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 = new 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();
|
|
|
|
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);
|
|
}
|
|
|
|
nsresult
|
|
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
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
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();
|
|
|
|
// 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();
|
|
}
|
|
|
|
nsresult
|
|
CacheFileChunk::Truncate(uint32_t aOffset)
|
|
{
|
|
MOZ_RELEASE_ASSERT(mState == READY || mState == WRITING || mState == READING);
|
|
|
|
if (mState == READING) {
|
|
mIsDirty = true;
|
|
}
|
|
|
|
mBuf->SetDataSize(aOffset);
|
|
return NS_OK;
|
|
}
|
|
|
|
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));
|
|
|
|
uint32_t limit = CacheObserver::MaxDiskChunksMemoryUsage(mIsPriority);
|
|
if (limit == 0) {
|
|
return true;
|
|
}
|
|
|
|
uint32_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 net
|
|
} // namespace mozilla
|