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 "CacheFile.h" michael@0: michael@0: #include "CacheFileChunk.h" michael@0: #include "CacheFileInputStream.h" michael@0: #include "CacheFileOutputStream.h" michael@0: #include "CacheIndex.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsProxyRelease.h" michael@0: michael@0: // When CACHE_CHUNKS is defined we always cache unused chunks in mCacheChunks. michael@0: // When it is not defined, we always release the chunks ASAP, i.e. we cache michael@0: // unused chunks only when: michael@0: // - CacheFile is memory-only michael@0: // - CacheFile is still waiting for the handle michael@0: michael@0: //#define CACHE_CHUNKS michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: class NotifyCacheFileListenerEvent : public nsRunnable { michael@0: public: michael@0: NotifyCacheFileListenerEvent(CacheFileListener *aCallback, michael@0: nsresult aResult, michael@0: bool aIsNew) michael@0: : mCallback(aCallback) michael@0: , mRV(aResult) michael@0: , mIsNew(aIsNew) michael@0: { michael@0: LOG(("NotifyCacheFileListenerEvent::NotifyCacheFileListenerEvent() " michael@0: "[this=%p]", this)); michael@0: MOZ_COUNT_CTOR(NotifyCacheFileListenerEvent); michael@0: } michael@0: michael@0: ~NotifyCacheFileListenerEvent() michael@0: { michael@0: LOG(("NotifyCacheFileListenerEvent::~NotifyCacheFileListenerEvent() " michael@0: "[this=%p]", this)); michael@0: MOZ_COUNT_DTOR(NotifyCacheFileListenerEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: LOG(("NotifyCacheFileListenerEvent::Run() [this=%p]", this)); michael@0: michael@0: mCallback->OnFileReady(mRV, mIsNew); michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: nsCOMPtr mCallback; michael@0: nsresult mRV; michael@0: bool mIsNew; michael@0: }; michael@0: michael@0: class NotifyChunkListenerEvent : public nsRunnable { michael@0: public: michael@0: NotifyChunkListenerEvent(CacheFileChunkListener *aCallback, michael@0: nsresult aResult, michael@0: uint32_t aChunkIdx, michael@0: CacheFileChunk *aChunk) michael@0: : mCallback(aCallback) michael@0: , mRV(aResult) michael@0: , mChunkIdx(aChunkIdx) michael@0: , mChunk(aChunk) michael@0: { michael@0: LOG(("NotifyChunkListenerEvent::NotifyChunkListenerEvent() [this=%p]", michael@0: this)); michael@0: MOZ_COUNT_CTOR(NotifyChunkListenerEvent); michael@0: } michael@0: michael@0: ~NotifyChunkListenerEvent() michael@0: { michael@0: LOG(("NotifyChunkListenerEvent::~NotifyChunkListenerEvent() [this=%p]", michael@0: this)); michael@0: MOZ_COUNT_DTOR(NotifyChunkListenerEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: LOG(("NotifyChunkListenerEvent::Run() [this=%p]", this)); michael@0: michael@0: mCallback->OnChunkAvailable(mRV, mChunkIdx, mChunk); michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: nsCOMPtr mCallback; michael@0: nsresult mRV; michael@0: uint32_t mChunkIdx; michael@0: nsRefPtr mChunk; michael@0: }; michael@0: michael@0: michael@0: class DoomFileHelper : public CacheFileIOListener michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: DoomFileHelper(CacheFileListener *aListener) michael@0: : mListener(aListener) michael@0: { michael@0: MOZ_COUNT_CTOR(DoomFileHelper); michael@0: } michael@0: michael@0: michael@0: NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("DoomFileHelper::OnFileOpened should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, michael@0: nsresult aResult) michael@0: { michael@0: MOZ_CRASH("DoomFileHelper::OnDataWritten should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("DoomFileHelper::OnDataRead should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: if (mListener) michael@0: mListener->OnFileDoomed(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("DoomFileHelper::OnEOFSet should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("DoomFileHelper::OnFileRenamed should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: private: michael@0: virtual ~DoomFileHelper() michael@0: { michael@0: MOZ_COUNT_DTOR(DoomFileHelper); michael@0: } michael@0: michael@0: nsCOMPtr mListener; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(DoomFileHelper, CacheFileIOListener) michael@0: michael@0: michael@0: NS_IMPL_ADDREF(CacheFile) michael@0: NS_IMPL_RELEASE(CacheFile) michael@0: NS_INTERFACE_MAP_BEGIN(CacheFile) michael@0: NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener) michael@0: NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener) michael@0: NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileMetadataListener) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, michael@0: mozilla::net::CacheFileChunkListener) michael@0: NS_INTERFACE_MAP_END_THREADSAFE michael@0: michael@0: CacheFile::CacheFile() michael@0: : mLock("CacheFile.mLock") michael@0: , mOpeningFile(false) michael@0: , mReady(false) michael@0: , mMemoryOnly(false) michael@0: , mOpenAsMemoryOnly(false) michael@0: , mDataAccessed(false) michael@0: , mDataIsDirty(false) michael@0: , mWritingMetadata(false) michael@0: , mStatus(NS_OK) michael@0: , mDataSize(-1) michael@0: , mOutput(nullptr) michael@0: { michael@0: LOG(("CacheFile::CacheFile() [this=%p]", this)); michael@0: } michael@0: michael@0: CacheFile::~CacheFile() michael@0: { michael@0: LOG(("CacheFile::~CacheFile() [this=%p]", this)); michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: if (!mMemoryOnly && mReady) { michael@0: // mReady flag indicates we have metadata plus in a valid state. michael@0: WriteMetadataIfNeededLocked(true); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::Init(const nsACString &aKey, michael@0: bool aCreateNew, michael@0: bool aMemoryOnly, michael@0: bool aPriority, michael@0: CacheFileListener *aCallback) michael@0: { michael@0: MOZ_ASSERT(!mListener); michael@0: MOZ_ASSERT(!mHandle); michael@0: michael@0: nsresult rv; michael@0: michael@0: mKey = aKey; michael@0: mOpenAsMemoryOnly = mMemoryOnly = aMemoryOnly; michael@0: michael@0: LOG(("CacheFile::Init() [this=%p, key=%s, createNew=%d, memoryOnly=%d, " michael@0: "listener=%p]", this, mKey.get(), aCreateNew, aMemoryOnly, aCallback)); michael@0: michael@0: if (mMemoryOnly) { michael@0: MOZ_ASSERT(!aCallback); michael@0: michael@0: mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey); michael@0: mReady = true; michael@0: mDataSize = mMetadata->Offset(); michael@0: return NS_OK; michael@0: } michael@0: else { michael@0: uint32_t flags; michael@0: if (aCreateNew) { michael@0: MOZ_ASSERT(!aCallback); michael@0: flags = CacheFileIOManager::CREATE_NEW; michael@0: michael@0: // make sure we can use this entry immediately michael@0: mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey); michael@0: mReady = true; michael@0: mDataSize = mMetadata->Offset(); michael@0: } michael@0: else { michael@0: flags = CacheFileIOManager::CREATE; michael@0: michael@0: // Have a look into index and change to CREATE_NEW when we are sure michael@0: // that the entry does not exist. michael@0: CacheIndex::EntryStatus status; michael@0: rv = CacheIndex::HasEntry(mKey, &status); michael@0: if (status == CacheIndex::DOES_NOT_EXIST) { michael@0: LOG(("CacheFile::Init() - Forcing CREATE_NEW flag since we don't have" michael@0: " this entry according to index")); michael@0: flags = CacheFileIOManager::CREATE_NEW; michael@0: michael@0: // make sure we can use this entry immediately michael@0: mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey); michael@0: mReady = true; michael@0: mDataSize = mMetadata->Offset(); michael@0: michael@0: // Notify callback now and don't store it in mListener, no further michael@0: // operation can change the result. michael@0: nsRefPtr ev; michael@0: ev = new NotifyCacheFileListenerEvent(aCallback, NS_OK, true); michael@0: rv = NS_DispatchToCurrentThread(ev); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aCallback = nullptr; michael@0: } michael@0: } michael@0: michael@0: if (aPriority) michael@0: flags |= CacheFileIOManager::PRIORITY; michael@0: michael@0: mOpeningFile = true; michael@0: mListener = aCallback; michael@0: rv = CacheFileIOManager::OpenFile(mKey, flags, true, this); michael@0: if (NS_FAILED(rv)) { michael@0: mListener = nullptr; michael@0: mOpeningFile = false; michael@0: michael@0: if (aCreateNew) { michael@0: NS_WARNING("Forcing memory-only entry since OpenFile failed"); michael@0: LOG(("CacheFile::Init() - CacheFileIOManager::OpenFile() failed " michael@0: "synchronously. We can continue in memory-only mode since " michael@0: "aCreateNew == true. [this=%p]", this)); michael@0: michael@0: mMemoryOnly = true; michael@0: } michael@0: else if (rv == NS_ERROR_NOT_INITIALIZED) { michael@0: NS_WARNING("Forcing memory-only entry since CacheIOManager isn't " michael@0: "initialized."); michael@0: LOG(("CacheFile::Init() - CacheFileIOManager isn't initialized, " michael@0: "initializing entry as memory-only. [this=%p]", this)); michael@0: michael@0: mMemoryOnly = true; michael@0: mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey); michael@0: mReady = true; michael@0: mDataSize = mMetadata->Offset(); michael@0: michael@0: nsRefPtr ev; michael@0: ev = new NotifyCacheFileListenerEvent(aCallback, NS_OK, true); michael@0: rv = NS_DispatchToCurrentThread(ev); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: nsresult rv; michael@0: michael@0: uint32_t index = aChunk->Index(); michael@0: michael@0: LOG(("CacheFile::OnChunkRead() [this=%p, rv=0x%08x, chunk=%p, idx=%d]", michael@0: this, aResult, aChunk, index)); michael@0: michael@0: if (NS_FAILED(aResult)) { michael@0: SetError(aResult); michael@0: CacheFileIOManager::DoomFile(mHandle, nullptr); michael@0: } michael@0: michael@0: if (HaveChunkListeners(index)) { michael@0: rv = NotifyChunkListeners(index, aResult, aChunk); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: nsresult rv; michael@0: michael@0: LOG(("CacheFile::OnChunkWritten() [this=%p, rv=0x%08x, chunk=%p, idx=%d]", michael@0: this, aResult, aChunk, aChunk->Index())); michael@0: michael@0: MOZ_ASSERT(!mMemoryOnly); michael@0: MOZ_ASSERT(!mOpeningFile); michael@0: MOZ_ASSERT(mHandle); michael@0: michael@0: if (NS_FAILED(aResult)) { michael@0: SetError(aResult); michael@0: CacheFileIOManager::DoomFile(mHandle, nullptr); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(aResult) && !aChunk->IsDirty()) { michael@0: // update hash value in metadata michael@0: mMetadata->SetHash(aChunk->Index(), aChunk->Hash()); michael@0: } michael@0: michael@0: // notify listeners if there is any michael@0: if (HaveChunkListeners(aChunk->Index())) { michael@0: // don't release the chunk since there are some listeners queued michael@0: rv = NotifyChunkListeners(aChunk->Index(), aResult, aChunk); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: MOZ_ASSERT(aChunk->mRefCnt != 2); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: if (aChunk->mRefCnt != 2) { michael@0: LOG(("CacheFile::OnChunkWritten() - Chunk is still used [this=%p, chunk=%p," michael@0: " refcnt=%d]", this, aChunk, aChunk->mRefCnt.get())); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef CACHE_CHUNKS michael@0: if (NS_SUCCEEDED(aResult)) { michael@0: LOG(("CacheFile::OnChunkWritten() - Caching unused chunk [this=%p, " michael@0: "chunk=%p]", this, aChunk)); michael@0: } else { michael@0: LOG(("CacheFile::OnChunkWritten() - Removing failed chunk [this=%p, " michael@0: "chunk=%p]", this, aChunk)); michael@0: } michael@0: #else michael@0: LOG(("CacheFile::OnChunkWritten() - Releasing %s chunk [this=%p, chunk=%p]", michael@0: NS_SUCCEEDED(aResult) ? "unused" : "failed", this, aChunk)); michael@0: #endif michael@0: michael@0: RemoveChunkInternal(aChunk, michael@0: #ifdef CACHE_CHUNKS michael@0: NS_SUCCEEDED(aResult)); michael@0: #else michael@0: false); michael@0: #endif michael@0: michael@0: WriteMetadataIfNeededLocked(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx, michael@0: CacheFileChunk *aChunk) michael@0: { michael@0: MOZ_CRASH("CacheFile::OnChunkAvailable should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnChunkUpdated(CacheFileChunk *aChunk) michael@0: { michael@0: MOZ_CRASH("CacheFile::OnChunkUpdated should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Using an 'auto' class to perform doom or fail the listener michael@0: // outside the CacheFile's lock. michael@0: class AutoFailDoomListener michael@0: { michael@0: public: michael@0: AutoFailDoomListener(CacheFileHandle *aHandle) michael@0: : mHandle(aHandle) michael@0: , mAlreadyDoomed(false) michael@0: {} michael@0: ~AutoFailDoomListener() michael@0: { michael@0: if (!mListener) michael@0: return; michael@0: michael@0: if (mHandle) { michael@0: if (mAlreadyDoomed) { michael@0: mListener->OnFileDoomed(mHandle, NS_OK); michael@0: } else { michael@0: CacheFileIOManager::DoomFile(mHandle, mListener); michael@0: } michael@0: } else { michael@0: mListener->OnFileDoomed(nullptr, NS_ERROR_NOT_AVAILABLE); michael@0: } michael@0: } michael@0: michael@0: CacheFileHandle* mHandle; michael@0: nsCOMPtr mListener; michael@0: bool mAlreadyDoomed; michael@0: } autoDoom(aHandle); michael@0: michael@0: nsCOMPtr listener; michael@0: bool isNew = false; michael@0: nsresult retval = NS_OK; michael@0: michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: MOZ_ASSERT(mOpeningFile); michael@0: MOZ_ASSERT((NS_SUCCEEDED(aResult) && aHandle) || michael@0: (NS_FAILED(aResult) && !aHandle)); michael@0: MOZ_ASSERT((mListener && !mMetadata) || // !createNew michael@0: (!mListener && mMetadata)); // createNew michael@0: MOZ_ASSERT(!mMemoryOnly || mMetadata); // memory-only was set on new entry michael@0: michael@0: LOG(("CacheFile::OnFileOpened() [this=%p, rv=0x%08x, handle=%p]", michael@0: this, aResult, aHandle)); michael@0: michael@0: mOpeningFile = false; michael@0: michael@0: autoDoom.mListener.swap(mDoomAfterOpenListener); michael@0: michael@0: if (mMemoryOnly) { michael@0: // We can be here only in case the entry was initilized as createNew and michael@0: // SetMemoryOnly() was called. michael@0: michael@0: // Just don't store the handle into mHandle and exit michael@0: autoDoom.mAlreadyDoomed = true; michael@0: return NS_OK; michael@0: } michael@0: else if (NS_FAILED(aResult)) { michael@0: if (mMetadata) { michael@0: // This entry was initialized as createNew, just switch to memory-only michael@0: // mode. michael@0: NS_WARNING("Forcing memory-only entry since OpenFile failed"); michael@0: LOG(("CacheFile::OnFileOpened() - CacheFileIOManager::OpenFile() " michael@0: "failed asynchronously. We can continue in memory-only mode since " michael@0: "aCreateNew == true. [this=%p]", this)); michael@0: michael@0: mMemoryOnly = true; michael@0: return NS_OK; michael@0: } michael@0: else if (aResult == NS_ERROR_FILE_INVALID_PATH) { michael@0: // CacheFileIOManager doesn't have mCacheDirectory, switch to michael@0: // memory-only mode. michael@0: NS_WARNING("Forcing memory-only entry since CacheFileIOManager doesn't " michael@0: "have mCacheDirectory."); michael@0: LOG(("CacheFile::OnFileOpened() - CacheFileIOManager doesn't have " michael@0: "mCacheDirectory, initializing entry as memory-only. [this=%p]", michael@0: this)); michael@0: michael@0: mMemoryOnly = true; michael@0: mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey); michael@0: mReady = true; michael@0: mDataSize = mMetadata->Offset(); michael@0: michael@0: isNew = true; michael@0: retval = NS_OK; michael@0: } michael@0: else { michael@0: // CacheFileIOManager::OpenFile() failed for another reason. michael@0: isNew = false; michael@0: retval = aResult; michael@0: } michael@0: michael@0: mListener.swap(listener); michael@0: } michael@0: else { michael@0: mHandle = aHandle; michael@0: michael@0: if (mMetadata) { michael@0: InitIndexEntry(); michael@0: michael@0: // The entry was initialized as createNew, don't try to read metadata. michael@0: mMetadata->SetHandle(mHandle); michael@0: michael@0: // Write all cached chunks, otherwise they may stay unwritten. michael@0: mCachedChunks.Enumerate(&CacheFile::WriteAllCachedChunks, this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (listener) { michael@0: listener->OnFileReady(retval, isNew); michael@0: return NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(NS_SUCCEEDED(aResult)); michael@0: MOZ_ASSERT(!mMetadata); michael@0: MOZ_ASSERT(mListener); michael@0: michael@0: mMetadata = new CacheFileMetadata(mHandle, mKey); michael@0: michael@0: rv = mMetadata->ReadMetadata(this); michael@0: if (NS_FAILED(rv)) { michael@0: mListener.swap(listener); michael@0: listener->OnFileReady(rv, false); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, michael@0: nsresult aResult) michael@0: { michael@0: MOZ_CRASH("CacheFile::OnDataWritten should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("CacheFile::OnDataRead should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnMetadataRead(nsresult aResult) michael@0: { michael@0: MOZ_ASSERT(mListener); michael@0: michael@0: LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08x]", this, aResult)); michael@0: michael@0: bool isNew = false; michael@0: if (NS_SUCCEEDED(aResult)) { michael@0: mReady = true; michael@0: mDataSize = mMetadata->Offset(); michael@0: if (mDataSize == 0 && mMetadata->ElementsSize() == 0) { michael@0: isNew = true; michael@0: mMetadata->MarkDirty(); michael@0: } michael@0: michael@0: InitIndexEntry(); michael@0: } michael@0: michael@0: nsCOMPtr listener; michael@0: mListener.swap(listener); michael@0: listener->OnFileReady(aResult, isNew); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnMetadataWritten(nsresult aResult) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: LOG(("CacheFile::OnMetadataWritten() [this=%p, rv=0x%08x]", this, aResult)); michael@0: michael@0: MOZ_ASSERT(mWritingMetadata); michael@0: mWritingMetadata = false; michael@0: michael@0: MOZ_ASSERT(!mMemoryOnly); michael@0: MOZ_ASSERT(!mOpeningFile); michael@0: michael@0: if (NS_FAILED(aResult)) { michael@0: // TODO close streams with an error ??? michael@0: } michael@0: michael@0: if (mOutput || mInputs.Length() || mChunks.Count()) michael@0: return NS_OK; michael@0: michael@0: if (IsDirty()) michael@0: WriteMetadataIfNeededLocked(); michael@0: michael@0: if (!mWritingMetadata) { michael@0: LOG(("CacheFile::OnMetadataWritten() - Releasing file handle [this=%p]", michael@0: this)); michael@0: CacheFileIOManager::ReleaseNSPRHandle(mHandle); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: nsCOMPtr listener; michael@0: michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: MOZ_ASSERT(mListener); michael@0: michael@0: LOG(("CacheFile::OnFileDoomed() [this=%p, rv=0x%08x, handle=%p]", michael@0: this, aResult, aHandle)); michael@0: michael@0: mListener.swap(listener); michael@0: } michael@0: michael@0: listener->OnFileDoomed(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("CacheFile::OnEOFSet should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("CacheFile::OnFileRenamed should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OpenInputStream(nsIInputStream **_retval) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile); michael@0: michael@0: if (!mReady) { michael@0: LOG(("CacheFile::OpenInputStream() - CacheFile is not ready [this=%p]", michael@0: this)); michael@0: michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: CacheFileInputStream *input = new CacheFileInputStream(this); michael@0: michael@0: LOG(("CacheFile::OpenInputStream() - Creating new input stream %p [this=%p]", michael@0: input, this)); michael@0: michael@0: mInputs.AppendElement(input); michael@0: NS_ADDREF(input); michael@0: michael@0: mDataAccessed = true; michael@0: NS_ADDREF(*_retval = input); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::OpenOutputStream(CacheOutputCloseListener *aCloseListener, nsIOutputStream **_retval) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile); michael@0: michael@0: if (!mReady) { michael@0: LOG(("CacheFile::OpenOutputStream() - CacheFile is not ready [this=%p]", michael@0: this)); michael@0: michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (mOutput) { michael@0: LOG(("CacheFile::OpenOutputStream() - We already have output stream %p " michael@0: "[this=%p]", mOutput, this)); michael@0: michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: mOutput = new CacheFileOutputStream(this, aCloseListener); michael@0: michael@0: LOG(("CacheFile::OpenOutputStream() - Creating new output stream %p " michael@0: "[this=%p]", mOutput, this)); michael@0: michael@0: mDataAccessed = true; michael@0: NS_ADDREF(*_retval = mOutput); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::SetMemoryOnly() michael@0: { michael@0: LOG(("CacheFile::SetMemoryOnly() mMemoryOnly=%d [this=%p]", michael@0: mMemoryOnly, this)); michael@0: michael@0: if (mMemoryOnly) michael@0: return NS_OK; michael@0: michael@0: MOZ_ASSERT(mReady); michael@0: michael@0: if (!mReady) { michael@0: LOG(("CacheFile::SetMemoryOnly() - CacheFile is not ready [this=%p]", michael@0: this)); michael@0: michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (mDataAccessed) { michael@0: LOG(("CacheFile::SetMemoryOnly() - Data was already accessed [this=%p]", this)); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // TODO what to do when this isn't a new entry and has an existing metadata??? michael@0: mMemoryOnly = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::Doom(CacheFileListener *aCallback) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile); michael@0: michael@0: LOG(("CacheFile::Doom() [this=%p, listener=%p]", this, aCallback)); michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (mMemoryOnly) { michael@0: return NS_ERROR_FILE_NOT_FOUND; michael@0: } michael@0: michael@0: if (mHandle && mHandle->IsDoomed()) { michael@0: return NS_ERROR_FILE_NOT_FOUND; michael@0: } michael@0: michael@0: nsCOMPtr listener; michael@0: if (aCallback || !mHandle) { michael@0: listener = new DoomFileHelper(aCallback); michael@0: } michael@0: if (mHandle) { michael@0: rv = CacheFileIOManager::DoomFile(mHandle, listener); michael@0: } else if (mOpeningFile) { michael@0: mDoomAfterOpenListener = listener; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::ThrowMemoryCachedData() michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: LOG(("CacheFile::ThrowMemoryCachedData() [this=%p]", this)); michael@0: michael@0: if (mMemoryOnly) { michael@0: // This method should not be called when the CacheFile was initialized as michael@0: // memory-only, but it can be called when CacheFile end up as memory-only michael@0: // due to e.g. IO failure since CacheEntry doesn't know it. michael@0: LOG(("CacheFile::ThrowMemoryCachedData() - Ignoring request because the " michael@0: "entry is memory-only. [this=%p]", this)); michael@0: michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (mOpeningFile) { michael@0: // mayhemer, note: we shouldn't get here, since CacheEntry prevents loading michael@0: // entries from being purged. michael@0: michael@0: LOG(("CacheFile::ThrowMemoryCachedData() - Ignoring request because the " michael@0: "entry is still opening the file [this=%p]", this)); michael@0: michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: #ifdef CACHE_CHUNKS michael@0: mCachedChunks.Clear(); michael@0: #else michael@0: // If we don't cache all chunks, mCachedChunks must be empty. michael@0: MOZ_ASSERT(mCachedChunks.Count() == 0); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::GetElement(const char *aKey, char **_retval) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: MOZ_ASSERT(mMetadata); michael@0: NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); michael@0: michael@0: const char *value; michael@0: value = mMetadata->GetElement(aKey); michael@0: if (!value) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *_retval = NS_strdup(value); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::SetElement(const char *aKey, const char *aValue) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: MOZ_ASSERT(mMetadata); michael@0: NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); michael@0: michael@0: PostWriteTimer(); michael@0: return mMetadata->SetElement(aKey, aValue); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::ElementsSize(uint32_t *_retval) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: if (!mMetadata) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *_retval = mMetadata->ElementsSize(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::SetExpirationTime(uint32_t aExpirationTime) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: MOZ_ASSERT(mMetadata); michael@0: NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); michael@0: michael@0: PostWriteTimer(); michael@0: michael@0: if (mHandle && !mHandle->IsDoomed()) michael@0: CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, &aExpirationTime); michael@0: michael@0: return mMetadata->SetExpirationTime(aExpirationTime); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::GetExpirationTime(uint32_t *_retval) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: MOZ_ASSERT(mMetadata); michael@0: NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); michael@0: michael@0: return mMetadata->GetExpirationTime(_retval); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::SetLastModified(uint32_t aLastModified) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: MOZ_ASSERT(mMetadata); michael@0: NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); michael@0: michael@0: PostWriteTimer(); michael@0: return mMetadata->SetLastModified(aLastModified); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::GetLastModified(uint32_t *_retval) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: MOZ_ASSERT(mMetadata); michael@0: NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); michael@0: michael@0: return mMetadata->GetLastModified(_retval); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::SetFrecency(uint32_t aFrecency) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: MOZ_ASSERT(mMetadata); michael@0: NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); michael@0: michael@0: PostWriteTimer(); michael@0: michael@0: if (mHandle && !mHandle->IsDoomed()) michael@0: CacheFileIOManager::UpdateIndexEntry(mHandle, &aFrecency, nullptr); michael@0: michael@0: return mMetadata->SetFrecency(aFrecency); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::GetFrecency(uint32_t *_retval) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: MOZ_ASSERT(mMetadata); michael@0: NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); michael@0: michael@0: return mMetadata->GetFrecency(_retval); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::GetLastFetched(uint32_t *_retval) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: MOZ_ASSERT(mMetadata); michael@0: NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); michael@0: michael@0: return mMetadata->GetLastFetched(_retval); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::GetFetchCount(uint32_t *_retval) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: MOZ_ASSERT(mMetadata); michael@0: NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); michael@0: michael@0: return mMetadata->GetFetchCount(_retval); michael@0: } michael@0: michael@0: void michael@0: CacheFile::Lock() michael@0: { michael@0: mLock.Lock(); michael@0: } michael@0: michael@0: void michael@0: CacheFile::Unlock() michael@0: { michael@0: nsTArray objs; michael@0: objs.SwapElements(mObjsToRelease); michael@0: michael@0: mLock.Unlock(); michael@0: michael@0: for (uint32_t i = 0; i < objs.Length(); i++) michael@0: objs[i]->Release(); michael@0: } michael@0: michael@0: void michael@0: CacheFile::AssertOwnsLock() const michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: } michael@0: michael@0: void michael@0: CacheFile::ReleaseOutsideLock(nsISupports *aObject) michael@0: { michael@0: AssertOwnsLock(); michael@0: michael@0: mObjsToRelease.AppendElement(aObject); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::GetChunk(uint32_t aIndex, bool aWriter, michael@0: CacheFileChunkListener *aCallback, CacheFileChunk **_retval) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: return GetChunkLocked(aIndex, aWriter, aCallback, _retval); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::GetChunkLocked(uint32_t aIndex, bool aWriter, michael@0: CacheFileChunkListener *aCallback, michael@0: CacheFileChunk **_retval) michael@0: { michael@0: AssertOwnsLock(); michael@0: michael@0: LOG(("CacheFile::GetChunkLocked() [this=%p, idx=%d, writer=%d, listener=%p]", michael@0: this, aIndex, aWriter, aCallback)); michael@0: michael@0: MOZ_ASSERT(mReady); michael@0: MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile); michael@0: MOZ_ASSERT((aWriter && !aCallback) || (!aWriter && aCallback)); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsRefPtr chunk; michael@0: if (mChunks.Get(aIndex, getter_AddRefs(chunk))) { michael@0: LOG(("CacheFile::GetChunkLocked() - Found chunk %p in mChunks [this=%p]", michael@0: chunk.get(), this)); michael@0: michael@0: // We might get failed chunk between releasing the lock in michael@0: // CacheFileChunk::OnDataWritten/Read and CacheFile::OnChunkWritten/Read michael@0: rv = chunk->GetStatus(); michael@0: if (NS_FAILED(rv)) { michael@0: SetError(rv); michael@0: LOG(("CacheFile::GetChunkLocked() - Found failed chunk in mChunks " michael@0: "[this=%p]", this)); michael@0: return rv; michael@0: } michael@0: michael@0: if (chunk->IsReady() || aWriter) { michael@0: chunk.swap(*_retval); michael@0: } michael@0: else { michael@0: rv = QueueChunkListener(aIndex, aCallback); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mCachedChunks.Get(aIndex, getter_AddRefs(chunk))) { michael@0: #ifndef CACHE_CHUNKS michael@0: // We don't cache all chunks, so we must not have handle and we must be michael@0: // either waiting for the handle, or this is memory-only entry. michael@0: MOZ_ASSERT(!mHandle && (mMemoryOnly || mOpeningFile)); michael@0: #endif michael@0: LOG(("CacheFile::GetChunkLocked() - Reusing cached chunk %p [this=%p]", michael@0: chunk.get(), this)); michael@0: michael@0: mChunks.Put(aIndex, chunk); michael@0: mCachedChunks.Remove(aIndex); michael@0: chunk->mFile = this; michael@0: chunk->mRemovingChunk = false; michael@0: michael@0: MOZ_ASSERT(chunk->IsReady()); michael@0: michael@0: chunk.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: int64_t off = aIndex * kChunkSize; michael@0: michael@0: if (off < mDataSize) { michael@0: // We cannot be here if this is memory only entry since the chunk must exist michael@0: MOZ_ASSERT(!mMemoryOnly); michael@0: if (mMemoryOnly) { michael@0: // If this ever really happen it is better to fail rather than crashing on michael@0: // a null handle. michael@0: LOG(("CacheFile::GetChunkLocked() - Unexpected state! Offset < mDataSize " michael@0: "for memory-only entry. [this=%p, off=%lld, mDataSize=%lld]", michael@0: this, off, mDataSize)); michael@0: michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: chunk = new CacheFileChunk(this, aIndex); michael@0: mChunks.Put(aIndex, chunk); michael@0: michael@0: LOG(("CacheFile::GetChunkLocked() - Reading newly created chunk %p from " michael@0: "the disk [this=%p]", chunk.get(), this)); michael@0: michael@0: // Read the chunk from the disk michael@0: rv = chunk->Read(mHandle, std::min(static_cast(mDataSize - off), michael@0: static_cast(kChunkSize)), michael@0: mMetadata->GetHash(aIndex), this); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: RemoveChunkInternal(chunk, false); michael@0: return rv; michael@0: } michael@0: michael@0: if (aWriter) { michael@0: chunk.swap(*_retval); michael@0: } michael@0: else { michael@0: rv = QueueChunkListener(aIndex, aCallback); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: else if (off == mDataSize) { michael@0: if (aWriter) { michael@0: // this listener is going to write to the chunk michael@0: chunk = new CacheFileChunk(this, aIndex); michael@0: mChunks.Put(aIndex, chunk); michael@0: michael@0: LOG(("CacheFile::GetChunkLocked() - Created new empty chunk %p [this=%p]", michael@0: chunk.get(), this)); michael@0: michael@0: chunk->InitNew(this); michael@0: mMetadata->SetHash(aIndex, chunk->Hash()); michael@0: michael@0: if (HaveChunkListeners(aIndex)) { michael@0: rv = NotifyChunkListeners(aIndex, NS_OK, chunk); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: chunk.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: else { michael@0: if (aWriter) { michael@0: // this chunk was requested by writer, but we need to fill the gap first michael@0: michael@0: // Fill with zero the last chunk if it is incomplete michael@0: if (mDataSize % kChunkSize) { michael@0: rv = PadChunkWithZeroes(mDataSize / kChunkSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MOZ_ASSERT(!(mDataSize % kChunkSize)); michael@0: } michael@0: michael@0: uint32_t startChunk = mDataSize / kChunkSize; michael@0: michael@0: if (mMemoryOnly) { michael@0: // We need to create all missing CacheFileChunks if this is memory-only michael@0: // entry michael@0: for (uint32_t i = startChunk ; i < aIndex ; i++) { michael@0: rv = PadChunkWithZeroes(i); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: else { michael@0: // We don't need to create CacheFileChunk for other empty chunks unless michael@0: // there is some input stream waiting for this chunk. michael@0: michael@0: if (startChunk != aIndex) { michael@0: // Make sure the file contains zeroes at the end of the file michael@0: rv = CacheFileIOManager::TruncateSeekSetEOF(mHandle, michael@0: startChunk * kChunkSize, michael@0: aIndex * kChunkSize, michael@0: nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: for (uint32_t i = startChunk ; i < aIndex ; i++) { michael@0: if (HaveChunkListeners(i)) { michael@0: rv = PadChunkWithZeroes(i); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: mMetadata->SetHash(i, kEmptyChunkHash); michael@0: mDataSize = (i + 1) * kChunkSize; michael@0: } michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSERT(mDataSize == off); michael@0: rv = GetChunkLocked(aIndex, true, nullptr, getter_AddRefs(chunk)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: chunk.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: if (mOutput) { michael@0: // the chunk doesn't exist but mOutput may create it michael@0: rv = QueueChunkListener(aIndex, aCallback); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::RemoveChunk(CacheFileChunk *aChunk) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Avoid lock reentrancy by increasing the RefCnt michael@0: nsRefPtr chunk = aChunk; michael@0: michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: LOG(("CacheFile::RemoveChunk() [this=%p, chunk=%p, idx=%d]", michael@0: this, aChunk, aChunk->Index())); michael@0: michael@0: MOZ_ASSERT(mReady); michael@0: MOZ_ASSERT((mHandle && !mMemoryOnly && !mOpeningFile) || michael@0: (!mHandle && mMemoryOnly && !mOpeningFile) || michael@0: (!mHandle && !mMemoryOnly && mOpeningFile)); michael@0: michael@0: if (aChunk->mRefCnt != 2) { michael@0: LOG(("CacheFile::RemoveChunk() - Chunk is still used [this=%p, chunk=%p, " michael@0: "refcnt=%d]", this, aChunk, aChunk->mRefCnt.get())); michael@0: michael@0: // somebody got the reference before the lock was acquired michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: // We can be here iff the chunk is in the hash table michael@0: nsRefPtr chunkCheck; michael@0: mChunks.Get(chunk->Index(), getter_AddRefs(chunkCheck)); michael@0: MOZ_ASSERT(chunkCheck == chunk); michael@0: michael@0: // We also shouldn't have any queued listener for this chunk michael@0: ChunkListeners *listeners; michael@0: mChunkListeners.Get(chunk->Index(), &listeners); michael@0: MOZ_ASSERT(!listeners); michael@0: } michael@0: #endif michael@0: michael@0: if (NS_FAILED(mStatus)) { michael@0: // Don't write any chunk to disk since this entry will be doomed michael@0: LOG(("CacheFile::RemoveChunk() - Removing chunk because of status " michael@0: "[this=%p, chunk=%p, mStatus=0x%08x]", this, chunk.get(), mStatus)); michael@0: michael@0: RemoveChunkInternal(chunk, false); michael@0: return mStatus; michael@0: } michael@0: michael@0: if (chunk->IsDirty() && !mMemoryOnly && !mOpeningFile) { michael@0: LOG(("CacheFile::RemoveChunk() - Writing dirty chunk to the disk " michael@0: "[this=%p]", this)); michael@0: michael@0: mDataIsDirty = true; michael@0: michael@0: rv = chunk->Write(mHandle, this); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheFile::RemoveChunk() - CacheFileChunk::Write() failed " michael@0: "synchronously. Removing it. [this=%p, chunk=%p, rv=0x%08x]", michael@0: this, chunk.get(), rv)); michael@0: michael@0: RemoveChunkInternal(chunk, false); michael@0: michael@0: SetError(rv); michael@0: CacheFileIOManager::DoomFile(mHandle, nullptr); michael@0: return rv; michael@0: } michael@0: else { michael@0: // Chunk will be removed in OnChunkWritten if it is still unused michael@0: michael@0: // chunk needs to be released under the lock to be able to rely on michael@0: // CacheFileChunk::mRefCnt in CacheFile::OnChunkWritten() michael@0: chunk = nullptr; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: #ifdef CACHE_CHUNKS michael@0: LOG(("CacheFile::RemoveChunk() - Caching unused chunk [this=%p, chunk=%p]", michael@0: this, chunk.get())); michael@0: #else michael@0: if (mMemoryOnly || mOpeningFile) { michael@0: LOG(("CacheFile::RemoveChunk() - Caching unused chunk [this=%p, chunk=%p," michael@0: " reason=%s]", this, chunk.get(), michael@0: mMemoryOnly ? "memory-only" : "opening-file")); michael@0: } else { michael@0: LOG(("CacheFile::RemoveChunk() - Releasing unused chunk [this=%p, " michael@0: "chunk=%p]", this, chunk.get())); michael@0: } michael@0: #endif michael@0: michael@0: RemoveChunkInternal(chunk, michael@0: #ifdef CACHE_CHUNKS michael@0: true); michael@0: #else michael@0: // Cache the chunk only when we have a reason to do so michael@0: mMemoryOnly || mOpeningFile); michael@0: #endif michael@0: michael@0: if (!mMemoryOnly) michael@0: WriteMetadataIfNeededLocked(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CacheFile::RemoveChunkInternal(CacheFileChunk *aChunk, bool aCacheChunk) michael@0: { michael@0: aChunk->mRemovingChunk = true; michael@0: ReleaseOutsideLock(static_cast( michael@0: aChunk->mFile.forget().take())); michael@0: michael@0: if (aCacheChunk) { michael@0: mCachedChunks.Put(aChunk->Index(), aChunk); michael@0: } michael@0: michael@0: mChunks.Remove(aChunk->Index()); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::RemoveInput(CacheFileInputStream *aInput) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: LOG(("CacheFile::RemoveInput() [this=%p, input=%p]", this, aInput)); michael@0: michael@0: DebugOnly found; michael@0: found = mInputs.RemoveElement(aInput); michael@0: MOZ_ASSERT(found); michael@0: michael@0: ReleaseOutsideLock(static_cast(aInput)); michael@0: michael@0: if (!mMemoryOnly) michael@0: WriteMetadataIfNeededLocked(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::RemoveOutput(CacheFileOutputStream *aOutput) michael@0: { michael@0: AssertOwnsLock(); michael@0: michael@0: LOG(("CacheFile::RemoveOutput() [this=%p, output=%p]", this, aOutput)); michael@0: michael@0: if (mOutput != aOutput) { michael@0: LOG(("CacheFile::RemoveOutput() - This output was already removed, ignoring" michael@0: " call [this=%p]", this)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mOutput = nullptr; michael@0: michael@0: // Cancel all queued chunk and update listeners that cannot be satisfied michael@0: NotifyListenersAboutOutputRemoval(); michael@0: michael@0: if (!mMemoryOnly) michael@0: WriteMetadataIfNeededLocked(); michael@0: michael@0: // Notify close listener as the last action michael@0: aOutput->NotifyCloseListener(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::NotifyChunkListener(CacheFileChunkListener *aCallback, michael@0: nsIEventTarget *aTarget, michael@0: nsresult aResult, michael@0: uint32_t aChunkIdx, michael@0: CacheFileChunk *aChunk) michael@0: { michael@0: LOG(("CacheFile::NotifyChunkListener() [this=%p, listener=%p, target=%p, " michael@0: "rv=0x%08x, idx=%d, chunk=%p]", this, aCallback, aTarget, aResult, michael@0: aChunkIdx, aChunk)); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr ev; michael@0: ev = new NotifyChunkListenerEvent(aCallback, aResult, aChunkIdx, aChunk); michael@0: if (aTarget) michael@0: rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL); michael@0: else michael@0: rv = NS_DispatchToCurrentThread(ev); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::QueueChunkListener(uint32_t aIndex, michael@0: CacheFileChunkListener *aCallback) michael@0: { michael@0: LOG(("CacheFile::QueueChunkListener() [this=%p, idx=%d, listener=%p]", michael@0: this, aIndex, aCallback)); michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: MOZ_ASSERT(aCallback); michael@0: michael@0: ChunkListenerItem *item = new ChunkListenerItem(); michael@0: item->mTarget = NS_GetCurrentThread(); michael@0: item->mCallback = aCallback; michael@0: michael@0: ChunkListeners *listeners; michael@0: if (!mChunkListeners.Get(aIndex, &listeners)) { michael@0: listeners = new ChunkListeners(); michael@0: mChunkListeners.Put(aIndex, listeners); michael@0: } michael@0: michael@0: listeners->mItems.AppendElement(item); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::NotifyChunkListeners(uint32_t aIndex, nsresult aResult, michael@0: CacheFileChunk *aChunk) michael@0: { michael@0: LOG(("CacheFile::NotifyChunkListeners() [this=%p, idx=%d, rv=0x%08x, " michael@0: "chunk=%p]", this, aIndex, aResult, aChunk)); michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: nsresult rv, rv2; michael@0: michael@0: ChunkListeners *listeners; michael@0: mChunkListeners.Get(aIndex, &listeners); michael@0: MOZ_ASSERT(listeners); michael@0: michael@0: rv = NS_OK; michael@0: for (uint32_t i = 0 ; i < listeners->mItems.Length() ; i++) { michael@0: ChunkListenerItem *item = listeners->mItems[i]; michael@0: rv2 = NotifyChunkListener(item->mCallback, item->mTarget, aResult, aIndex, michael@0: aChunk); michael@0: if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) michael@0: rv = rv2; michael@0: delete item; michael@0: } michael@0: michael@0: mChunkListeners.Remove(aIndex); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: CacheFile::HaveChunkListeners(uint32_t aIndex) michael@0: { michael@0: ChunkListeners *listeners; michael@0: mChunkListeners.Get(aIndex, &listeners); michael@0: return !!listeners; michael@0: } michael@0: michael@0: void michael@0: CacheFile::NotifyListenersAboutOutputRemoval() michael@0: { michael@0: LOG(("CacheFile::NotifyListenersAboutOutputRemoval() [this=%p]", this)); michael@0: michael@0: AssertOwnsLock(); michael@0: michael@0: // First fail all chunk listeners that wait for non-existent chunk michael@0: mChunkListeners.Enumerate(&CacheFile::FailListenersIfNonExistentChunk, michael@0: this); michael@0: michael@0: // Fail all update listeners michael@0: mChunks.Enumerate(&CacheFile::FailUpdateListeners, this); michael@0: } michael@0: michael@0: bool michael@0: CacheFile::DataSize(int64_t* aSize) michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: if (mOutput) michael@0: return false; michael@0: michael@0: *aSize = mDataSize; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CacheFile::IsDoomed() michael@0: { michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: if (!mHandle) michael@0: return false; michael@0: michael@0: return mHandle->IsDoomed(); michael@0: } michael@0: michael@0: bool michael@0: CacheFile::IsWriteInProgress() michael@0: { michael@0: // Returns true when there is a potentially unfinished write operation. michael@0: // Not using lock for performance reasons. mMetadata is never released michael@0: // during life time of CacheFile. michael@0: return michael@0: mDataIsDirty || michael@0: (mMetadata && mMetadata->IsDirty()) || michael@0: mWritingMetadata || michael@0: mOpeningFile || michael@0: mOutput || michael@0: mChunks.Count(); michael@0: } michael@0: michael@0: bool michael@0: CacheFile::IsDirty() michael@0: { michael@0: return mDataIsDirty || mMetadata->IsDirty(); michael@0: } michael@0: michael@0: void michael@0: CacheFile::WriteMetadataIfNeeded() michael@0: { michael@0: LOG(("CacheFile::WriteMetadataIfNeeded() [this=%p]", this)); michael@0: michael@0: CacheFileAutoLock lock(this); michael@0: michael@0: if (!mMemoryOnly) michael@0: WriteMetadataIfNeededLocked(); michael@0: } michael@0: michael@0: void michael@0: CacheFile::WriteMetadataIfNeededLocked(bool aFireAndForget) michael@0: { michael@0: // When aFireAndForget is set to true, we are called from dtor. michael@0: // |this| must not be referenced after this method returns! michael@0: michael@0: LOG(("CacheFile::WriteMetadataIfNeededLocked() [this=%p]", this)); michael@0: michael@0: nsresult rv; michael@0: michael@0: AssertOwnsLock(); michael@0: MOZ_ASSERT(!mMemoryOnly); michael@0: michael@0: if (!mMetadata) { michael@0: MOZ_CRASH("Must have metadata here"); michael@0: return; michael@0: } michael@0: michael@0: if (!aFireAndForget) { michael@0: // if aFireAndForget is set, we are called from dtor. Write michael@0: // scheduler hard-refers CacheFile otherwise, so we cannot be here. michael@0: CacheFileIOManager::UnscheduleMetadataWrite(this); michael@0: } michael@0: michael@0: if (NS_FAILED(mStatus)) michael@0: return; michael@0: michael@0: if (!IsDirty() || mOutput || mInputs.Length() || mChunks.Count() || michael@0: mWritingMetadata || mOpeningFile) michael@0: return; michael@0: michael@0: LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing metadata [this=%p]", michael@0: this)); michael@0: michael@0: rv = mMetadata->WriteMetadata(mDataSize, aFireAndForget ? nullptr : this); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mWritingMetadata = true; michael@0: mDataIsDirty = false; michael@0: } else { michael@0: LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing synchronously " michael@0: "failed [this=%p]", this)); michael@0: // TODO: close streams with error michael@0: SetError(rv); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheFile::PostWriteTimer() michael@0: { michael@0: LOG(("CacheFile::PostWriteTimer() [this=%p]", this)); michael@0: michael@0: CacheFileIOManager::ScheduleMetadataWrite(this); michael@0: } michael@0: michael@0: PLDHashOperator michael@0: CacheFile::WriteAllCachedChunks(const uint32_t& aIdx, michael@0: nsRefPtr& aChunk, michael@0: void* aClosure) michael@0: { michael@0: CacheFile *file = static_cast(aClosure); michael@0: michael@0: LOG(("CacheFile::WriteAllCachedChunks() [this=%p, idx=%d, chunk=%p]", michael@0: file, aIdx, aChunk.get())); michael@0: michael@0: file->mChunks.Put(aIdx, aChunk); michael@0: aChunk->mFile = file; michael@0: aChunk->mRemovingChunk = false; michael@0: michael@0: MOZ_ASSERT(aChunk->IsReady()); michael@0: michael@0: NS_ADDREF(aChunk); michael@0: file->ReleaseOutsideLock(aChunk); michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: CacheFile::FailListenersIfNonExistentChunk( michael@0: const uint32_t& aIdx, michael@0: nsAutoPtr& aListeners, michael@0: void* aClosure) michael@0: { michael@0: CacheFile *file = static_cast(aClosure); michael@0: michael@0: LOG(("CacheFile::FailListenersIfNonExistentChunk() [this=%p, idx=%d]", michael@0: file, aIdx)); michael@0: michael@0: nsRefPtr chunk; michael@0: file->mChunks.Get(aIdx, getter_AddRefs(chunk)); michael@0: if (chunk) { michael@0: MOZ_ASSERT(!chunk->IsReady()); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: for (uint32_t i = 0 ; i < aListeners->mItems.Length() ; i++) { michael@0: ChunkListenerItem *item = aListeners->mItems[i]; michael@0: file->NotifyChunkListener(item->mCallback, item->mTarget, michael@0: NS_ERROR_NOT_AVAILABLE, aIdx, nullptr); michael@0: delete item; michael@0: } michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: CacheFile::FailUpdateListeners( michael@0: const uint32_t& aIdx, michael@0: nsRefPtr& aChunk, michael@0: void* aClosure) michael@0: { michael@0: #ifdef PR_LOGGING michael@0: CacheFile *file = static_cast(aClosure); michael@0: #endif michael@0: michael@0: LOG(("CacheFile::FailUpdateListeners() [this=%p, idx=%d]", michael@0: file, aIdx)); michael@0: michael@0: if (aChunk->IsReady()) { michael@0: aChunk->NotifyUpdateListeners(); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::PadChunkWithZeroes(uint32_t aChunkIdx) michael@0: { michael@0: AssertOwnsLock(); michael@0: michael@0: // This method is used to pad last incomplete chunk with zeroes or create michael@0: // a new chunk full of zeroes michael@0: MOZ_ASSERT(mDataSize / kChunkSize == aChunkIdx); michael@0: michael@0: nsresult rv; michael@0: nsRefPtr chunk; michael@0: rv = GetChunkLocked(aChunkIdx, true, nullptr, getter_AddRefs(chunk)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: LOG(("CacheFile::PadChunkWithZeroes() - Zeroing hole in chunk %d, range %d-%d" michael@0: " [this=%p]", aChunkIdx, chunk->DataSize(), kChunkSize - 1, this)); michael@0: michael@0: chunk->EnsureBufSize(kChunkSize); michael@0: memset(chunk->BufForWriting() + chunk->DataSize(), 0, kChunkSize - chunk->DataSize()); michael@0: michael@0: chunk->UpdateDataSize(chunk->DataSize(), kChunkSize - chunk->DataSize(), michael@0: false); michael@0: michael@0: ReleaseOutsideLock(chunk.forget().take()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CacheFile::SetError(nsresult aStatus) michael@0: { michael@0: if (NS_SUCCEEDED(mStatus)) { michael@0: mStatus = aStatus; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: CacheFile::InitIndexEntry() michael@0: { michael@0: MOZ_ASSERT(mHandle); michael@0: michael@0: if (mHandle->IsDoomed()) michael@0: return NS_OK; michael@0: michael@0: nsresult rv; michael@0: michael@0: rv = CacheFileIOManager::InitIndexEntry(mHandle, michael@0: mMetadata->AppId(), michael@0: mMetadata->IsAnonymous(), michael@0: mMetadata->IsInBrowser()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t expTime; michael@0: mMetadata->GetExpirationTime(&expTime); michael@0: michael@0: uint32_t frecency; michael@0: mMetadata->GetFrecency(&frecency); michael@0: michael@0: rv = CacheFileIOManager::UpdateIndexEntry(mHandle, &frecency, &expTime); michael@0: NS_ENSURE_SUCCESS(rv, rv); 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: size_t michael@0: CollectChunkSize(uint32_t const & aIdx, michael@0: nsRefPtr const & aChunk, michael@0: mozilla::MallocSizeOf mallocSizeOf, void* aClosure) michael@0: { michael@0: return aChunk->SizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: } // anon michael@0: michael@0: size_t michael@0: CacheFile::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: CacheFileAutoLock lock(const_cast(this)); michael@0: michael@0: size_t n = 0; michael@0: n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf); michael@0: n += mChunks.SizeOfExcludingThis(CollectChunkSize, mallocSizeOf); michael@0: n += mCachedChunks.SizeOfExcludingThis(CollectChunkSize, mallocSizeOf); michael@0: if (mMetadata) { michael@0: n += mMetadata->SizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: // Input streams are not elsewhere reported. michael@0: n += mInputs.SizeOfExcludingThis(mallocSizeOf); michael@0: for (uint32_t i = 0; i < mInputs.Length(); ++i) { michael@0: n += mInputs[i]->SizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: // Output streams are not elsewhere reported. michael@0: if (mOutput) { michael@0: n += mOutput->SizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: // The listeners are usually classes reported just above. michael@0: n += mChunkListeners.SizeOfExcludingThis(nullptr, mallocSizeOf); michael@0: n += mObjsToRelease.SizeOfExcludingThis(mallocSizeOf); michael@0: michael@0: // mHandle reported directly from CacheFileIOManager. michael@0: michael@0: return n; michael@0: } michael@0: michael@0: size_t michael@0: CacheFile::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: } // net michael@0: } // mozilla