1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "CacheFileChunk.h"
9 #include "CacheFileInputStream.h"
10 #include "CacheFileOutputStream.h"
11 #include "nsThreadUtils.h"
12 #include "mozilla/DebugOnly.h"
14 #include "nsComponentManagerUtils.h"
15 #include "nsProxyRelease.h"
16 #include "mozilla/Telemetry.h"
18 // When CACHE_CHUNKS is defined we always cache unused chunks in mCacheChunks.
19 // When it is not defined, we always release the chunks ASAP, i.e. we cache
20 // unused chunks only when:
21 // - CacheFile is memory-only
22 // - CacheFile is still waiting for the handle
23 // - the chunk is preloaded
25 //#define CACHE_CHUNKS
30 class NotifyCacheFileListenerEvent
: public nsRunnable
{
32 NotifyCacheFileListenerEvent(CacheFileListener
*aCallback
,
35 : mCallback(aCallback
)
39 LOG(("NotifyCacheFileListenerEvent::NotifyCacheFileListenerEvent() "
41 MOZ_COUNT_CTOR(NotifyCacheFileListenerEvent
);
45 ~NotifyCacheFileListenerEvent()
47 LOG(("NotifyCacheFileListenerEvent::~NotifyCacheFileListenerEvent() "
49 MOZ_COUNT_DTOR(NotifyCacheFileListenerEvent
);
55 LOG(("NotifyCacheFileListenerEvent::Run() [this=%p]", this));
57 mCallback
->OnFileReady(mRV
, mIsNew
);
62 nsCOMPtr
<CacheFileListener
> mCallback
;
67 class NotifyChunkListenerEvent
: public nsRunnable
{
69 NotifyChunkListenerEvent(CacheFileChunkListener
*aCallback
,
72 CacheFileChunk
*aChunk
)
73 : mCallback(aCallback
)
75 , mChunkIdx(aChunkIdx
)
78 LOG(("NotifyChunkListenerEvent::NotifyChunkListenerEvent() [this=%p]",
80 MOZ_COUNT_CTOR(NotifyChunkListenerEvent
);
84 ~NotifyChunkListenerEvent()
86 LOG(("NotifyChunkListenerEvent::~NotifyChunkListenerEvent() [this=%p]",
88 MOZ_COUNT_DTOR(NotifyChunkListenerEvent
);
94 LOG(("NotifyChunkListenerEvent::Run() [this=%p]", this));
96 mCallback
->OnChunkAvailable(mRV
, mChunkIdx
, mChunk
);
101 nsCOMPtr
<CacheFileChunkListener
> mCallback
;
104 nsRefPtr
<CacheFileChunk
> mChunk
;
108 class DoomFileHelper
: public CacheFileIOListener
111 NS_DECL_THREADSAFE_ISUPPORTS
113 explicit DoomFileHelper(CacheFileListener
*aListener
)
114 : mListener(aListener
)
116 MOZ_COUNT_CTOR(DoomFileHelper
);
120 NS_IMETHOD
OnFileOpened(CacheFileHandle
*aHandle
, nsresult aResult
) MOZ_OVERRIDE
122 MOZ_CRASH("DoomFileHelper::OnFileOpened should not be called!");
123 return NS_ERROR_UNEXPECTED
;
126 NS_IMETHOD
OnDataWritten(CacheFileHandle
*aHandle
, const char *aBuf
,
127 nsresult aResult
) MOZ_OVERRIDE
129 MOZ_CRASH("DoomFileHelper::OnDataWritten should not be called!");
130 return NS_ERROR_UNEXPECTED
;
133 NS_IMETHOD
OnDataRead(CacheFileHandle
*aHandle
, char *aBuf
, nsresult aResult
) MOZ_OVERRIDE
135 MOZ_CRASH("DoomFileHelper::OnDataRead should not be called!");
136 return NS_ERROR_UNEXPECTED
;
139 NS_IMETHOD
OnFileDoomed(CacheFileHandle
*aHandle
, nsresult aResult
) MOZ_OVERRIDE
142 mListener
->OnFileDoomed(aResult
);
146 NS_IMETHOD
OnEOFSet(CacheFileHandle
*aHandle
, nsresult aResult
) MOZ_OVERRIDE
148 MOZ_CRASH("DoomFileHelper::OnEOFSet should not be called!");
149 return NS_ERROR_UNEXPECTED
;
152 NS_IMETHOD
OnFileRenamed(CacheFileHandle
*aHandle
, nsresult aResult
) MOZ_OVERRIDE
154 MOZ_CRASH("DoomFileHelper::OnFileRenamed should not be called!");
155 return NS_ERROR_UNEXPECTED
;
159 virtual ~DoomFileHelper()
161 MOZ_COUNT_DTOR(DoomFileHelper
);
164 nsCOMPtr
<CacheFileListener
> mListener
;
167 NS_IMPL_ISUPPORTS(DoomFileHelper
, CacheFileIOListener
)
170 NS_IMPL_ADDREF(CacheFile
)
171 NS_IMPL_RELEASE(CacheFile
)
172 NS_INTERFACE_MAP_BEGIN(CacheFile
)
173 NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener
)
174 NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener
)
175 NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileMetadataListener
)
176 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
,
177 mozilla::net::CacheFileChunkListener
)
178 NS_INTERFACE_MAP_END_THREADSAFE
180 CacheFile::CacheFile()
181 : mLock("CacheFile.mLock")
182 , mOpeningFile(false)
185 , mOpenAsMemoryOnly(false)
187 , mDataAccessed(false)
188 , mDataIsDirty(false)
189 , mWritingMetadata(false)
190 , mPreloadWithoutInputStreams(true)
191 , mPreloadChunkCount(0)
196 LOG(("CacheFile::CacheFile() [this=%p]", this));
199 CacheFile::~CacheFile()
201 LOG(("CacheFile::~CacheFile() [this=%p]", this));
203 MutexAutoLock
lock(mLock
);
204 if (!mMemoryOnly
&& mReady
) {
205 // mReady flag indicates we have metadata plus in a valid state.
206 WriteMetadataIfNeededLocked(true);
211 CacheFile::Init(const nsACString
&aKey
,
215 CacheFileListener
*aCallback
)
217 MOZ_ASSERT(!mListener
);
218 MOZ_ASSERT(!mHandle
);
223 mOpenAsMemoryOnly
= mMemoryOnly
= aMemoryOnly
;
224 mPriority
= aPriority
;
226 // Some consumers (at least nsHTTPCompressConv) assume that Read() can read
227 // such amount of data that was announced by Available().
228 // CacheFileInputStream::Available() uses also preloaded chunks to compute
229 // number of available bytes in the input stream, so we have to make sure the
230 // preloadChunkCount won't change during CacheFile's lifetime since otherwise
231 // we could potentially release some cached chunks that was used to calculate
232 // available bytes but would not be available later during call to
233 // CacheFileInputStream::Read().
234 mPreloadChunkCount
= CacheObserver::PreloadChunkCount();
236 LOG(("CacheFile::Init() [this=%p, key=%s, createNew=%d, memoryOnly=%d, "
237 "priority=%d, listener=%p]", this, mKey
.get(), aCreateNew
, aMemoryOnly
,
238 aPriority
, aCallback
));
241 MOZ_ASSERT(!aCallback
);
243 mMetadata
= new CacheFileMetadata(mOpenAsMemoryOnly
, mKey
);
245 mDataSize
= mMetadata
->Offset();
251 MOZ_ASSERT(!aCallback
);
252 flags
= CacheFileIOManager::CREATE_NEW
;
254 // make sure we can use this entry immediately
255 mMetadata
= new CacheFileMetadata(mOpenAsMemoryOnly
, mKey
);
257 mDataSize
= mMetadata
->Offset();
259 flags
= CacheFileIOManager::CREATE
;
263 flags
|= CacheFileIOManager::PRIORITY
;
267 mListener
= aCallback
;
268 rv
= CacheFileIOManager::OpenFile(mKey
, flags
, this);
271 mOpeningFile
= false;
274 NS_WARNING("Forcing memory-only entry since OpenFile failed");
275 LOG(("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
276 "synchronously. We can continue in memory-only mode since "
277 "aCreateNew == true. [this=%p]", this));
281 else if (rv
== NS_ERROR_NOT_INITIALIZED
) {
282 NS_WARNING("Forcing memory-only entry since CacheIOManager isn't "
284 LOG(("CacheFile::Init() - CacheFileIOManager isn't initialized, "
285 "initializing entry as memory-only. [this=%p]", this));
288 mMetadata
= new CacheFileMetadata(mOpenAsMemoryOnly
, mKey
);
290 mDataSize
= mMetadata
->Offset();
292 nsRefPtr
<NotifyCacheFileListenerEvent
> ev
;
293 ev
= new NotifyCacheFileListenerEvent(aCallback
, NS_OK
, true);
294 rv
= NS_DispatchToCurrentThread(ev
);
295 NS_ENSURE_SUCCESS(rv
, rv
);
298 NS_ENSURE_SUCCESS(rv
, rv
);
307 CacheFile::OnChunkRead(nsresult aResult
, CacheFileChunk
*aChunk
)
309 CacheFileAutoLock
lock(this);
313 uint32_t index
= aChunk
->Index();
315 LOG(("CacheFile::OnChunkRead() [this=%p, rv=0x%08x, chunk=%p, idx=%u]",
316 this, aResult
, aChunk
, index
));
318 if (NS_FAILED(aResult
)) {
322 if (HaveChunkListeners(index
)) {
323 rv
= NotifyChunkListeners(index
, aResult
, aChunk
);
324 NS_ENSURE_SUCCESS(rv
, rv
);
331 CacheFile::OnChunkWritten(nsresult aResult
, CacheFileChunk
*aChunk
)
333 // In case the chunk was reused, made dirty and released between calls to
334 // CacheFileChunk::Write() and CacheFile::OnChunkWritten(), we must write
335 // the chunk to the disk again. When the chunk is unused and is dirty simply
336 // addref and release (outside the lock) the chunk which ensures that
337 // CacheFile::DeactivateChunk() will be called again.
338 nsRefPtr
<CacheFileChunk
> deactivateChunkAgain
;
340 CacheFileAutoLock
lock(this);
344 LOG(("CacheFile::OnChunkWritten() [this=%p, rv=0x%08x, chunk=%p, idx=%u]",
345 this, aResult
, aChunk
, aChunk
->Index()));
347 MOZ_ASSERT(!mMemoryOnly
);
348 MOZ_ASSERT(!mOpeningFile
);
351 if (NS_FAILED(aResult
)) {
355 if (NS_SUCCEEDED(aResult
) && !aChunk
->IsDirty()) {
356 // update hash value in metadata
357 mMetadata
->SetHash(aChunk
->Index(), aChunk
->Hash());
360 // notify listeners if there is any
361 if (HaveChunkListeners(aChunk
->Index())) {
362 // don't release the chunk since there are some listeners queued
363 rv
= NotifyChunkListeners(aChunk
->Index(), aResult
, aChunk
);
364 if (NS_SUCCEEDED(rv
)) {
365 MOZ_ASSERT(aChunk
->mRefCnt
!= 2);
370 if (aChunk
->mRefCnt
!= 2) {
371 LOG(("CacheFile::OnChunkWritten() - Chunk is still used [this=%p, chunk=%p,"
372 " refcnt=%d]", this, aChunk
, aChunk
->mRefCnt
.get()));
377 if (aChunk
->IsDirty()) {
378 LOG(("CacheFile::OnChunkWritten() - Unused chunk is dirty. We must go "
379 "through deactivation again. [this=%p, chunk=%p]", this, aChunk
));
381 deactivateChunkAgain
= aChunk
;
385 bool keepChunk
= false;
386 if (NS_SUCCEEDED(aResult
)) {
387 keepChunk
= ShouldCacheChunk(aChunk
->Index());
388 LOG(("CacheFile::OnChunkWritten() - %s unused chunk [this=%p, chunk=%p]",
389 keepChunk
? "Caching" : "Releasing", this, aChunk
));
391 LOG(("CacheFile::OnChunkWritten() - Releasing failed chunk [this=%p, "
392 "chunk=%p]", this, aChunk
));
395 RemoveChunkInternal(aChunk
, keepChunk
);
397 WriteMetadataIfNeededLocked();
403 CacheFile::OnChunkAvailable(nsresult aResult
, uint32_t aChunkIdx
,
404 CacheFileChunk
*aChunk
)
406 MOZ_CRASH("CacheFile::OnChunkAvailable should not be called!");
407 return NS_ERROR_UNEXPECTED
;
411 CacheFile::OnChunkUpdated(CacheFileChunk
*aChunk
)
413 MOZ_CRASH("CacheFile::OnChunkUpdated should not be called!");
414 return NS_ERROR_UNEXPECTED
;
418 CacheFile::OnFileOpened(CacheFileHandle
*aHandle
, nsresult aResult
)
422 // Using an 'auto' class to perform doom or fail the listener
423 // outside the CacheFile's lock.
424 class AutoFailDoomListener
427 explicit AutoFailDoomListener(CacheFileHandle
*aHandle
)
429 , mAlreadyDoomed(false)
431 ~AutoFailDoomListener()
437 if (mAlreadyDoomed
) {
438 mListener
->OnFileDoomed(mHandle
, NS_OK
);
440 CacheFileIOManager::DoomFile(mHandle
, mListener
);
443 mListener
->OnFileDoomed(nullptr, NS_ERROR_NOT_AVAILABLE
);
447 CacheFileHandle
* mHandle
;
448 nsCOMPtr
<CacheFileIOListener
> mListener
;
452 nsCOMPtr
<CacheFileListener
> listener
;
454 nsresult retval
= NS_OK
;
457 CacheFileAutoLock
lock(this);
459 MOZ_ASSERT(mOpeningFile
);
460 MOZ_ASSERT((NS_SUCCEEDED(aResult
) && aHandle
) ||
461 (NS_FAILED(aResult
) && !aHandle
));
462 MOZ_ASSERT((mListener
&& !mMetadata
) || // !createNew
463 (!mListener
&& mMetadata
)); // createNew
464 MOZ_ASSERT(!mMemoryOnly
|| mMetadata
); // memory-only was set on new entry
466 LOG(("CacheFile::OnFileOpened() [this=%p, rv=0x%08x, handle=%p]",
467 this, aResult
, aHandle
));
469 mOpeningFile
= false;
471 autoDoom
.mListener
.swap(mDoomAfterOpenListener
);
474 // We can be here only in case the entry was initilized as createNew and
475 // SetMemoryOnly() was called.
477 // Just don't store the handle into mHandle and exit
478 autoDoom
.mAlreadyDoomed
= true;
481 else if (NS_FAILED(aResult
)) {
483 // This entry was initialized as createNew, just switch to memory-only
485 NS_WARNING("Forcing memory-only entry since OpenFile failed");
486 LOG(("CacheFile::OnFileOpened() - CacheFileIOManager::OpenFile() "
487 "failed asynchronously. We can continue in memory-only mode since "
488 "aCreateNew == true. [this=%p]", this));
493 else if (aResult
== NS_ERROR_FILE_INVALID_PATH
) {
494 // CacheFileIOManager doesn't have mCacheDirectory, switch to
496 NS_WARNING("Forcing memory-only entry since CacheFileIOManager doesn't "
497 "have mCacheDirectory.");
498 LOG(("CacheFile::OnFileOpened() - CacheFileIOManager doesn't have "
499 "mCacheDirectory, initializing entry as memory-only. [this=%p]",
503 mMetadata
= new CacheFileMetadata(mOpenAsMemoryOnly
, mKey
);
505 mDataSize
= mMetadata
->Offset();
511 // CacheFileIOManager::OpenFile() failed for another reason.
516 mListener
.swap(listener
);
520 if (NS_FAILED(mStatus
)) {
521 CacheFileIOManager::DoomFile(mHandle
, nullptr);
527 // The entry was initialized as createNew, don't try to read metadata.
528 mMetadata
->SetHandle(mHandle
);
530 // Write all cached chunks, otherwise they may stay unwritten.
531 mCachedChunks
.Enumerate(&CacheFile::WriteAllCachedChunks
, this);
539 listener
->OnFileReady(retval
, isNew
);
543 MOZ_ASSERT(NS_SUCCEEDED(aResult
));
544 MOZ_ASSERT(!mMetadata
);
545 MOZ_ASSERT(mListener
);
547 mMetadata
= new CacheFileMetadata(mHandle
, mKey
);
549 rv
= mMetadata
->ReadMetadata(this);
551 mListener
.swap(listener
);
552 listener
->OnFileReady(rv
, false);
559 CacheFile::OnDataWritten(CacheFileHandle
*aHandle
, const char *aBuf
,
562 MOZ_CRASH("CacheFile::OnDataWritten should not be called!");
563 return NS_ERROR_UNEXPECTED
;
567 CacheFile::OnDataRead(CacheFileHandle
*aHandle
, char *aBuf
, nsresult aResult
)
569 MOZ_CRASH("CacheFile::OnDataRead should not be called!");
570 return NS_ERROR_UNEXPECTED
;
574 CacheFile::OnMetadataRead(nsresult aResult
)
576 MOZ_ASSERT(mListener
);
578 LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08x]", this, aResult
));
581 if (NS_SUCCEEDED(aResult
)) {
583 mDataSize
= mMetadata
->Offset();
584 if (mDataSize
== 0 && mMetadata
->ElementsSize() == 0) {
586 mMetadata
->MarkDirty();
588 CacheFileAutoLock
lock(this);
595 nsCOMPtr
<CacheFileListener
> listener
;
596 mListener
.swap(listener
);
597 listener
->OnFileReady(aResult
, isNew
);
602 CacheFile::OnMetadataWritten(nsresult aResult
)
604 CacheFileAutoLock
lock(this);
606 LOG(("CacheFile::OnMetadataWritten() [this=%p, rv=0x%08x]", this, aResult
));
608 MOZ_ASSERT(mWritingMetadata
);
609 mWritingMetadata
= false;
611 MOZ_ASSERT(!mMemoryOnly
);
612 MOZ_ASSERT(!mOpeningFile
);
614 if (NS_FAILED(aResult
)) {
615 // TODO close streams with an error ???
618 if (mOutput
|| mInputs
.Length() || mChunks
.Count())
622 WriteMetadataIfNeededLocked();
624 if (!mWritingMetadata
) {
625 LOG(("CacheFile::OnMetadataWritten() - Releasing file handle [this=%p]",
627 CacheFileIOManager::ReleaseNSPRHandle(mHandle
);
634 CacheFile::OnFileDoomed(CacheFileHandle
*aHandle
, nsresult aResult
)
636 nsCOMPtr
<CacheFileListener
> listener
;
639 CacheFileAutoLock
lock(this);
641 MOZ_ASSERT(mListener
);
643 LOG(("CacheFile::OnFileDoomed() [this=%p, rv=0x%08x, handle=%p]",
644 this, aResult
, aHandle
));
646 mListener
.swap(listener
);
649 listener
->OnFileDoomed(aResult
);
654 CacheFile::OnEOFSet(CacheFileHandle
*aHandle
, nsresult aResult
)
656 MOZ_CRASH("CacheFile::OnEOFSet should not be called!");
657 return NS_ERROR_UNEXPECTED
;
661 CacheFile::OnFileRenamed(CacheFileHandle
*aHandle
, nsresult aResult
)
663 MOZ_CRASH("CacheFile::OnFileRenamed should not be called!");
664 return NS_ERROR_UNEXPECTED
;
668 CacheFile::OpenInputStream(nsIInputStream
**_retval
)
670 CacheFileAutoLock
lock(this);
672 MOZ_ASSERT(mHandle
|| mMemoryOnly
|| mOpeningFile
);
675 LOG(("CacheFile::OpenInputStream() - CacheFile is not ready [this=%p]",
678 return NS_ERROR_NOT_AVAILABLE
;
681 if (NS_FAILED(mStatus
)) {
682 LOG(("CacheFile::OpenInputStream() - CacheFile is in a failure state "
683 "[this=%p, status=0x%08x]", this, mStatus
));
685 // Don't allow opening the input stream when this CacheFile is in
686 // a failed state. This is the only way to protect consumers correctly
687 // from reading a broken entry. When the file is in the failed state,
688 // it's also doomed, so reopening the entry won't make any difference -
689 // data will still be inaccessible anymore. Note that for just doomed
690 // files, we must allow reading the data.
694 // Once we open input stream we no longer allow preloading of chunks without
695 // input stream, i.e. we will no longer keep first few chunks preloaded when
696 // the last input stream is closed.
697 mPreloadWithoutInputStreams
= false;
699 CacheFileInputStream
*input
= new CacheFileInputStream(this);
701 LOG(("CacheFile::OpenInputStream() - Creating new input stream %p [this=%p]",
704 mInputs
.AppendElement(input
);
707 mDataAccessed
= true;
708 NS_ADDREF(*_retval
= input
);
713 CacheFile::OpenOutputStream(CacheOutputCloseListener
*aCloseListener
, nsIOutputStream
**_retval
)
715 CacheFileAutoLock
lock(this);
717 MOZ_ASSERT(mHandle
|| mMemoryOnly
|| mOpeningFile
);
720 LOG(("CacheFile::OpenOutputStream() - CacheFile is not ready [this=%p]",
723 return NS_ERROR_NOT_AVAILABLE
;
727 LOG(("CacheFile::OpenOutputStream() - We already have output stream %p "
728 "[this=%p]", mOutput
, this));
730 return NS_ERROR_NOT_AVAILABLE
;
733 // Once we open output stream we no longer allow preloading of chunks without
734 // input stream. There is no reason to believe that some input stream will be
735 // opened soon. Otherwise we would cache unused chunks of all newly created
736 // entries until the CacheFile is destroyed.
737 mPreloadWithoutInputStreams
= false;
739 mOutput
= new CacheFileOutputStream(this, aCloseListener
);
741 LOG(("CacheFile::OpenOutputStream() - Creating new output stream %p "
742 "[this=%p]", mOutput
, this));
744 mDataAccessed
= true;
745 NS_ADDREF(*_retval
= mOutput
);
750 CacheFile::SetMemoryOnly()
752 LOG(("CacheFile::SetMemoryOnly() mMemoryOnly=%d [this=%p]",
761 LOG(("CacheFile::SetMemoryOnly() - CacheFile is not ready [this=%p]",
764 return NS_ERROR_NOT_AVAILABLE
;
768 LOG(("CacheFile::SetMemoryOnly() - Data was already accessed [this=%p]", this));
769 return NS_ERROR_NOT_AVAILABLE
;
772 // TODO what to do when this isn't a new entry and has an existing metadata???
778 CacheFile::Doom(CacheFileListener
*aCallback
)
780 LOG(("CacheFile::Doom() [this=%p, listener=%p]", this, aCallback
));
782 CacheFileAutoLock
lock(this);
784 return DoomLocked(aCallback
);
788 CacheFile::DoomLocked(CacheFileListener
*aCallback
)
790 MOZ_ASSERT(mHandle
|| mMemoryOnly
|| mOpeningFile
);
792 LOG(("CacheFile::DoomLocked() [this=%p, listener=%p]", this, aCallback
));
797 return NS_ERROR_FILE_NOT_FOUND
;
800 if (mHandle
&& mHandle
->IsDoomed()) {
801 return NS_ERROR_FILE_NOT_FOUND
;
804 nsCOMPtr
<CacheFileIOListener
> listener
;
805 if (aCallback
|| !mHandle
) {
806 listener
= new DoomFileHelper(aCallback
);
809 rv
= CacheFileIOManager::DoomFile(mHandle
, listener
);
810 } else if (mOpeningFile
) {
811 mDoomAfterOpenListener
= listener
;
818 CacheFile::ThrowMemoryCachedData()
820 CacheFileAutoLock
lock(this);
822 LOG(("CacheFile::ThrowMemoryCachedData() [this=%p]", this));
825 // This method should not be called when the CacheFile was initialized as
826 // memory-only, but it can be called when CacheFile end up as memory-only
827 // due to e.g. IO failure since CacheEntry doesn't know it.
828 LOG(("CacheFile::ThrowMemoryCachedData() - Ignoring request because the "
829 "entry is memory-only. [this=%p]", this));
831 return NS_ERROR_NOT_AVAILABLE
;
835 // mayhemer, note: we shouldn't get here, since CacheEntry prevents loading
836 // entries from being purged.
838 LOG(("CacheFile::ThrowMemoryCachedData() - Ignoring request because the "
839 "entry is still opening the file [this=%p]", this));
841 return NS_ERROR_ABORT
;
844 // We cannot release all cached chunks since we need to keep preloaded chunks
845 // in memory. See initialization of mPreloadChunkCount for explanation.
846 mCachedChunks
.Enumerate(&CacheFile::CleanUpCachedChunks
, this);
852 CacheFile::GetElement(const char *aKey
, char **_retval
)
854 CacheFileAutoLock
lock(this);
855 MOZ_ASSERT(mMetadata
);
856 NS_ENSURE_TRUE(mMetadata
, NS_ERROR_UNEXPECTED
);
859 value
= mMetadata
->GetElement(aKey
);
861 return NS_ERROR_NOT_AVAILABLE
;
863 *_retval
= NS_strdup(value
);
868 CacheFile::SetElement(const char *aKey
, const char *aValue
)
870 CacheFileAutoLock
lock(this);
871 MOZ_ASSERT(mMetadata
);
872 NS_ENSURE_TRUE(mMetadata
, NS_ERROR_UNEXPECTED
);
875 return mMetadata
->SetElement(aKey
, aValue
);
879 CacheFile::VisitMetaData(nsICacheEntryMetaDataVisitor
*aVisitor
)
881 CacheFileAutoLock
lock(this);
882 MOZ_ASSERT(mMetadata
);
884 NS_ENSURE_TRUE(mMetadata
, NS_ERROR_UNEXPECTED
);
886 return mMetadata
->Visit(aVisitor
);
890 CacheFile::ElementsSize(uint32_t *_retval
)
892 CacheFileAutoLock
lock(this);
895 return NS_ERROR_NOT_AVAILABLE
;
897 *_retval
= mMetadata
->ElementsSize();
902 CacheFile::SetExpirationTime(uint32_t aExpirationTime
)
904 CacheFileAutoLock
lock(this);
905 MOZ_ASSERT(mMetadata
);
906 NS_ENSURE_TRUE(mMetadata
, NS_ERROR_UNEXPECTED
);
910 if (mHandle
&& !mHandle
->IsDoomed())
911 CacheFileIOManager::UpdateIndexEntry(mHandle
, nullptr, &aExpirationTime
);
913 return mMetadata
->SetExpirationTime(aExpirationTime
);
917 CacheFile::GetExpirationTime(uint32_t *_retval
)
919 CacheFileAutoLock
lock(this);
920 MOZ_ASSERT(mMetadata
);
921 NS_ENSURE_TRUE(mMetadata
, NS_ERROR_UNEXPECTED
);
923 return mMetadata
->GetExpirationTime(_retval
);
927 CacheFile::SetFrecency(uint32_t aFrecency
)
929 CacheFileAutoLock
lock(this);
930 MOZ_ASSERT(mMetadata
);
931 NS_ENSURE_TRUE(mMetadata
, NS_ERROR_UNEXPECTED
);
935 if (mHandle
&& !mHandle
->IsDoomed())
936 CacheFileIOManager::UpdateIndexEntry(mHandle
, &aFrecency
, nullptr);
938 return mMetadata
->SetFrecency(aFrecency
);
942 CacheFile::GetFrecency(uint32_t *_retval
)
944 CacheFileAutoLock
lock(this);
945 MOZ_ASSERT(mMetadata
);
946 NS_ENSURE_TRUE(mMetadata
, NS_ERROR_UNEXPECTED
);
948 return mMetadata
->GetFrecency(_retval
);
952 CacheFile::GetLastModified(uint32_t *_retval
)
954 CacheFileAutoLock
lock(this);
955 MOZ_ASSERT(mMetadata
);
956 NS_ENSURE_TRUE(mMetadata
, NS_ERROR_UNEXPECTED
);
958 return mMetadata
->GetLastModified(_retval
);
962 CacheFile::GetLastFetched(uint32_t *_retval
)
964 CacheFileAutoLock
lock(this);
965 MOZ_ASSERT(mMetadata
);
966 NS_ENSURE_TRUE(mMetadata
, NS_ERROR_UNEXPECTED
);
968 return mMetadata
->GetLastFetched(_retval
);
972 CacheFile::GetFetchCount(uint32_t *_retval
)
974 CacheFileAutoLock
lock(this);
975 MOZ_ASSERT(mMetadata
);
976 NS_ENSURE_TRUE(mMetadata
, NS_ERROR_UNEXPECTED
);
978 return mMetadata
->GetFetchCount(_retval
);
982 CacheFile::OnFetched()
984 CacheFileAutoLock
lock(this);
985 MOZ_ASSERT(mMetadata
);
986 NS_ENSURE_TRUE(mMetadata
, NS_ERROR_UNEXPECTED
);
990 return mMetadata
->OnFetched();
1002 nsTArray
<nsISupports
*> objs
;
1003 objs
.SwapElements(mObjsToRelease
);
1007 for (uint32_t i
= 0; i
< objs
.Length(); i
++)
1012 CacheFile::AssertOwnsLock() const
1014 mLock
.AssertCurrentThreadOwns();
1018 CacheFile::ReleaseOutsideLock(nsISupports
*aObject
)
1022 mObjsToRelease
.AppendElement(aObject
);
1026 CacheFile::GetChunk(uint32_t aIndex
, ECallerType aCaller
,
1027 CacheFileChunkListener
*aCallback
, CacheFileChunk
**_retval
)
1029 CacheFileAutoLock
lock(this);
1030 return GetChunkLocked(aIndex
, aCaller
, aCallback
, _retval
);
1034 CacheFile::GetChunkLocked(uint32_t aIndex
, ECallerType aCaller
,
1035 CacheFileChunkListener
*aCallback
,
1036 CacheFileChunk
**_retval
)
1040 LOG(("CacheFile::GetChunkLocked() [this=%p, idx=%u, caller=%d, listener=%p]",
1041 this, aIndex
, aCaller
, aCallback
));
1044 MOZ_ASSERT(mHandle
|| mMemoryOnly
|| mOpeningFile
);
1045 MOZ_ASSERT((aCaller
== READER
&& aCallback
) ||
1046 (aCaller
== WRITER
&& !aCallback
) ||
1047 (aCaller
== PRELOADER
&& !aCallback
));
1049 // Preload chunks from disk when this is disk backed entry and the listener
1051 bool preload
= !mMemoryOnly
&& (aCaller
== READER
);
1055 nsRefPtr
<CacheFileChunk
> chunk
;
1056 if (mChunks
.Get(aIndex
, getter_AddRefs(chunk
))) {
1057 LOG(("CacheFile::GetChunkLocked() - Found chunk %p in mChunks [this=%p]",
1058 chunk
.get(), this));
1060 // Preloader calls this method to preload only non-loaded chunks.
1061 MOZ_ASSERT(aCaller
!= PRELOADER
, "Unexpected!");
1063 // We might get failed chunk between releasing the lock in
1064 // CacheFileChunk::OnDataWritten/Read and CacheFile::OnChunkWritten/Read
1065 rv
= chunk
->GetStatus();
1066 if (NS_FAILED(rv
)) {
1068 LOG(("CacheFile::GetChunkLocked() - Found failed chunk in mChunks "
1069 "[this=%p]", this));
1073 if (chunk
->IsReady() || aCaller
== WRITER
) {
1074 chunk
.swap(*_retval
);
1076 rv
= QueueChunkListener(aIndex
, aCallback
);
1077 NS_ENSURE_SUCCESS(rv
, rv
);
1081 PreloadChunks(aIndex
+ 1);
1087 if (mCachedChunks
.Get(aIndex
, getter_AddRefs(chunk
))) {
1088 LOG(("CacheFile::GetChunkLocked() - Reusing cached chunk %p [this=%p]",
1089 chunk
.get(), this));
1091 // Preloader calls this method to preload only non-loaded chunks.
1092 MOZ_ASSERT(aCaller
!= PRELOADER
, "Unexpected!");
1094 mChunks
.Put(aIndex
, chunk
);
1095 mCachedChunks
.Remove(aIndex
);
1096 chunk
->mFile
= this;
1097 chunk
->mActiveChunk
= true;
1099 MOZ_ASSERT(chunk
->IsReady());
1101 chunk
.swap(*_retval
);
1104 PreloadChunks(aIndex
+ 1);
1110 int64_t off
= aIndex
* kChunkSize
;
1112 if (off
< mDataSize
) {
1113 // We cannot be here if this is memory only entry since the chunk must exist
1114 MOZ_ASSERT(!mMemoryOnly
);
1116 // If this ever really happen it is better to fail rather than crashing on
1118 LOG(("CacheFile::GetChunkLocked() - Unexpected state! Offset < mDataSize "
1119 "for memory-only entry. [this=%p, off=%lld, mDataSize=%lld]",
1120 this, off
, mDataSize
));
1122 return NS_ERROR_UNEXPECTED
;
1125 chunk
= new CacheFileChunk(this, aIndex
, aCaller
== WRITER
);
1126 mChunks
.Put(aIndex
, chunk
);
1127 chunk
->mActiveChunk
= true;
1129 LOG(("CacheFile::GetChunkLocked() - Reading newly created chunk %p from "
1130 "the disk [this=%p]", chunk
.get(), this));
1132 // Read the chunk from the disk
1133 rv
= chunk
->Read(mHandle
, std::min(static_cast<uint32_t>(mDataSize
- off
),
1134 static_cast<uint32_t>(kChunkSize
)),
1135 mMetadata
->GetHash(aIndex
), this);
1136 if (NS_WARN_IF(NS_FAILED(rv
))) {
1137 RemoveChunkInternal(chunk
, false);
1141 if (aCaller
== WRITER
) {
1142 chunk
.swap(*_retval
);
1143 } else if (aCaller
!= PRELOADER
) {
1144 rv
= QueueChunkListener(aIndex
, aCallback
);
1145 NS_ENSURE_SUCCESS(rv
, rv
);
1149 PreloadChunks(aIndex
+ 1);
1153 } else if (off
== mDataSize
) {
1154 if (aCaller
== WRITER
) {
1155 // this listener is going to write to the chunk
1156 chunk
= new CacheFileChunk(this, aIndex
, true);
1157 mChunks
.Put(aIndex
, chunk
);
1158 chunk
->mActiveChunk
= true;
1160 LOG(("CacheFile::GetChunkLocked() - Created new empty chunk %p [this=%p]",
1161 chunk
.get(), this));
1164 mMetadata
->SetHash(aIndex
, chunk
->Hash());
1166 if (HaveChunkListeners(aIndex
)) {
1167 rv
= NotifyChunkListeners(aIndex
, NS_OK
, chunk
);
1168 NS_ENSURE_SUCCESS(rv
, rv
);
1171 chunk
.swap(*_retval
);
1175 if (aCaller
== WRITER
) {
1176 // this chunk was requested by writer, but we need to fill the gap first
1178 // Fill with zero the last chunk if it is incomplete
1179 if (mDataSize
% kChunkSize
) {
1180 rv
= PadChunkWithZeroes(mDataSize
/ kChunkSize
);
1181 NS_ENSURE_SUCCESS(rv
, rv
);
1183 MOZ_ASSERT(!(mDataSize
% kChunkSize
));
1186 uint32_t startChunk
= mDataSize
/ kChunkSize
;
1189 // We need to create all missing CacheFileChunks if this is memory-only
1191 for (uint32_t i
= startChunk
; i
< aIndex
; i
++) {
1192 rv
= PadChunkWithZeroes(i
);
1193 NS_ENSURE_SUCCESS(rv
, rv
);
1196 // We don't need to create CacheFileChunk for other empty chunks unless
1197 // there is some input stream waiting for this chunk.
1199 if (startChunk
!= aIndex
) {
1200 // Make sure the file contains zeroes at the end of the file
1201 rv
= CacheFileIOManager::TruncateSeekSetEOF(mHandle
,
1202 startChunk
* kChunkSize
,
1203 aIndex
* kChunkSize
,
1205 NS_ENSURE_SUCCESS(rv
, rv
);
1208 for (uint32_t i
= startChunk
; i
< aIndex
; i
++) {
1209 if (HaveChunkListeners(i
)) {
1210 rv
= PadChunkWithZeroes(i
);
1211 NS_ENSURE_SUCCESS(rv
, rv
);
1213 mMetadata
->SetHash(i
, kEmptyChunkHash
);
1214 mDataSize
= (i
+ 1) * kChunkSize
;
1219 MOZ_ASSERT(mDataSize
== off
);
1220 rv
= GetChunkLocked(aIndex
, WRITER
, nullptr, getter_AddRefs(chunk
));
1221 NS_ENSURE_SUCCESS(rv
, rv
);
1223 chunk
.swap(*_retval
);
1228 // We can be here only if the caller is reader since writer always create a
1229 // new chunk above and preloader calls this method to preload only chunks that
1230 // are not loaded but that do exist.
1231 MOZ_ASSERT(aCaller
== READER
, "Unexpected!");
1234 // the chunk doesn't exist but mOutput may create it
1235 rv
= QueueChunkListener(aIndex
, aCallback
);
1236 NS_ENSURE_SUCCESS(rv
, rv
);
1238 return NS_ERROR_NOT_AVAILABLE
;
1245 CacheFile::PreloadChunks(uint32_t aIndex
)
1249 uint32_t limit
= aIndex
+ mPreloadChunkCount
;
1251 for (uint32_t i
= aIndex
; i
< limit
; ++i
) {
1252 int64_t off
= i
* kChunkSize
;
1254 if (off
>= mDataSize
) {
1255 // This chunk is beyond EOF.
1259 if (mChunks
.GetWeak(i
) || mCachedChunks
.GetWeak(i
)) {
1260 // This chunk is already in memory or is being read right now.
1264 LOG(("CacheFile::PreloadChunks() - Preloading chunk [this=%p, idx=%u]",
1267 nsRefPtr
<CacheFileChunk
> chunk
;
1268 GetChunkLocked(i
, PRELOADER
, nullptr, getter_AddRefs(chunk
));
1269 // We've checked that we don't have this chunk, so no chunk must be
1276 CacheFile::ShouldCacheChunk(uint32_t aIndex
)
1281 // We cache all chunks.
1285 if (mPreloadChunkCount
!= 0 && mInputs
.Length() == 0 &&
1286 mPreloadWithoutInputStreams
&& aIndex
< mPreloadChunkCount
) {
1287 // We don't have any input stream yet, but it is likely that some will be
1288 // opened soon. Keep first mPreloadChunkCount chunks in memory. The
1289 // condition is here instead of in MustKeepCachedChunk() since these
1290 // chunks should be preloaded and can be kept in memory as an optimization,
1291 // but they can be released at any time until they are considered as
1292 // preloaded chunks for any input stream.
1296 // Cache only chunks that we really need to keep.
1297 return MustKeepCachedChunk(aIndex
);
1302 CacheFile::MustKeepCachedChunk(uint32_t aIndex
)
1306 // We must keep the chunk when this is memory only entry or we don't have
1308 if (mMemoryOnly
|| mOpeningFile
) {
1312 if (mPreloadChunkCount
== 0) {
1313 // Preloading of chunks is disabled
1317 // Check whether this chunk should be considered as preloaded chunk for any
1318 // existing input stream.
1320 // maxPos is the position of the last byte in the given chunk
1321 int64_t maxPos
= static_cast<int64_t>(aIndex
+ 1) * kChunkSize
- 1;
1323 // minPos is the position of the first byte in a chunk that precedes the given
1324 // chunk by mPreloadChunkCount chunks
1326 if (mPreloadChunkCount
>= aIndex
) {
1329 minPos
= static_cast<int64_t>(aIndex
- mPreloadChunkCount
) * kChunkSize
;
1332 for (uint32_t i
= 0; i
< mInputs
.Length(); ++i
) {
1333 int64_t inputPos
= mInputs
[i
]->GetPosition();
1334 if (inputPos
>= minPos
&& inputPos
<= maxPos
) {
1343 CacheFile::DeactivateChunk(CacheFileChunk
*aChunk
)
1347 // Avoid lock reentrancy by increasing the RefCnt
1348 nsRefPtr
<CacheFileChunk
> chunk
= aChunk
;
1351 CacheFileAutoLock
lock(this);
1353 LOG(("CacheFile::DeactivateChunk() [this=%p, chunk=%p, idx=%u]",
1354 this, aChunk
, aChunk
->Index()));
1357 MOZ_ASSERT((mHandle
&& !mMemoryOnly
&& !mOpeningFile
) ||
1358 (!mHandle
&& mMemoryOnly
&& !mOpeningFile
) ||
1359 (!mHandle
&& !mMemoryOnly
&& mOpeningFile
));
1361 if (aChunk
->mRefCnt
!= 2) {
1362 LOG(("CacheFile::DeactivateChunk() - Chunk is still used [this=%p, "
1363 "chunk=%p, refcnt=%d]", this, aChunk
, aChunk
->mRefCnt
.get()));
1365 // somebody got the reference before the lock was acquired
1371 // We can be here iff the chunk is in the hash table
1372 nsRefPtr
<CacheFileChunk
> chunkCheck
;
1373 mChunks
.Get(chunk
->Index(), getter_AddRefs(chunkCheck
));
1374 MOZ_ASSERT(chunkCheck
== chunk
);
1376 // We also shouldn't have any queued listener for this chunk
1377 ChunkListeners
*listeners
;
1378 mChunkListeners
.Get(chunk
->Index(), &listeners
);
1379 MOZ_ASSERT(!listeners
);
1383 if (NS_FAILED(chunk
->GetStatus())) {
1384 SetError(chunk
->GetStatus());
1387 if (NS_FAILED(mStatus
)) {
1388 // Don't write any chunk to disk since this entry will be doomed
1389 LOG(("CacheFile::DeactivateChunk() - Releasing chunk because of status "
1390 "[this=%p, chunk=%p, mStatus=0x%08x]", this, chunk
.get(), mStatus
));
1392 RemoveChunkInternal(chunk
, false);
1396 if (chunk
->IsDirty() && !mMemoryOnly
&& !mOpeningFile
) {
1397 LOG(("CacheFile::DeactivateChunk() - Writing dirty chunk to the disk "
1398 "[this=%p]", this));
1400 mDataIsDirty
= true;
1402 rv
= chunk
->Write(mHandle
, this);
1403 if (NS_FAILED(rv
)) {
1404 LOG(("CacheFile::DeactivateChunk() - CacheFileChunk::Write() failed "
1405 "synchronously. Removing it. [this=%p, chunk=%p, rv=0x%08x]",
1406 this, chunk
.get(), rv
));
1408 RemoveChunkInternal(chunk
, false);
1414 // Chunk will be removed in OnChunkWritten if it is still unused
1416 // chunk needs to be released under the lock to be able to rely on
1417 // CacheFileChunk::mRefCnt in CacheFile::OnChunkWritten()
1422 bool keepChunk
= ShouldCacheChunk(aChunk
->Index());
1423 LOG(("CacheFile::DeactivateChunk() - %s unused chunk [this=%p, chunk=%p]",
1424 keepChunk
? "Caching" : "Releasing", this, chunk
.get()));
1426 RemoveChunkInternal(chunk
, keepChunk
);
1429 WriteMetadataIfNeededLocked();
1436 CacheFile::RemoveChunkInternal(CacheFileChunk
*aChunk
, bool aCacheChunk
)
1440 aChunk
->mActiveChunk
= false;
1441 ReleaseOutsideLock(static_cast<CacheFileChunkListener
*>(
1442 aChunk
->mFile
.forget().take()));
1445 mCachedChunks
.Put(aChunk
->Index(), aChunk
);
1448 mChunks
.Remove(aChunk
->Index());
1452 CacheFile::BytesFromChunk(uint32_t aIndex
)
1459 // Index of the last existing chunk.
1460 uint32_t lastChunk
= (mDataSize
- 1) / kChunkSize
;
1461 if (aIndex
> lastChunk
)
1464 // We can use only preloaded chunks for the given stream to calculate
1465 // available bytes if this is an entry stored on disk, since only those
1466 // chunks are guaranteed not to be released.
1467 uint32_t maxPreloadedChunk
;
1469 maxPreloadedChunk
= lastChunk
;
1471 maxPreloadedChunk
= std::min(aIndex
+ mPreloadChunkCount
, lastChunk
);
1475 for (i
= aIndex
; i
<= maxPreloadedChunk
; ++i
) {
1476 CacheFileChunk
* chunk
;
1478 chunk
= mChunks
.GetWeak(i
);
1480 MOZ_ASSERT(i
== lastChunk
|| chunk
->mDataSize
== kChunkSize
);
1481 if (chunk
->IsReady()) {
1485 // don't search this chunk in cached
1489 chunk
= mCachedChunks
.GetWeak(i
);
1491 MOZ_ASSERT(i
== lastChunk
|| chunk
->mDataSize
== kChunkSize
);
1498 // theoretic bytes in advance
1499 int64_t advance
= int64_t(i
- aIndex
) * kChunkSize
;
1500 // real bytes till the end of the file
1501 int64_t tail
= mDataSize
- (aIndex
* kChunkSize
);
1503 return std::min(advance
, tail
);
1507 StatusToTelemetryEnum(nsresult aStatus
)
1509 if (NS_SUCCEEDED(aStatus
)) {
1514 case NS_BASE_STREAM_CLOSED
:
1515 return 0; // Log this as a success
1516 case NS_ERROR_OUT_OF_MEMORY
:
1518 case NS_ERROR_FILE_DISK_FULL
:
1520 case NS_ERROR_FILE_CORRUPTED
:
1522 case NS_ERROR_FILE_NOT_FOUND
:
1524 case NS_BINDING_ABORTED
:
1527 return 1; // other error
1530 NS_NOTREACHED("We should never get here");
1534 CacheFile::RemoveInput(CacheFileInputStream
*aInput
, nsresult aStatus
)
1536 CacheFileAutoLock
lock(this);
1538 LOG(("CacheFile::RemoveInput() [this=%p, input=%p, status=0x%08x]", this,
1541 DebugOnly
<bool> found
;
1542 found
= mInputs
.RemoveElement(aInput
);
1545 ReleaseOutsideLock(static_cast<nsIInputStream
*>(aInput
));
1548 WriteMetadataIfNeededLocked();
1550 // If the input didn't read all data, there might be left some preloaded
1551 // chunks that won't be used anymore.
1552 mCachedChunks
.Enumerate(&CacheFile::CleanUpCachedChunks
, this);
1554 Telemetry::Accumulate(Telemetry::NETWORK_CACHE_V2_INPUT_STREAM_STATUS
,
1555 StatusToTelemetryEnum(aStatus
));
1561 CacheFile::RemoveOutput(CacheFileOutputStream
*aOutput
, nsresult aStatus
)
1565 LOG(("CacheFile::RemoveOutput() [this=%p, output=%p, status=0x%08x]", this,
1568 if (mOutput
!= aOutput
) {
1569 LOG(("CacheFile::RemoveOutput() - This output was already removed, ignoring"
1570 " call [this=%p]", this));
1576 // Cancel all queued chunk and update listeners that cannot be satisfied
1577 NotifyListenersAboutOutputRemoval();
1580 WriteMetadataIfNeededLocked();
1582 // Make sure the CacheFile status is set to a failure when the output stream
1583 // is closed with a fatal error. This way we propagate correctly and w/o any
1584 // windows the failure state of this entry to end consumers.
1585 if (NS_SUCCEEDED(mStatus
) && NS_FAILED(aStatus
) && aStatus
!= NS_BASE_STREAM_CLOSED
) {
1589 // Notify close listener as the last action
1590 aOutput
->NotifyCloseListener();
1592 Telemetry::Accumulate(Telemetry::NETWORK_CACHE_V2_OUTPUT_STREAM_STATUS
,
1593 StatusToTelemetryEnum(aStatus
));
1599 CacheFile::NotifyChunkListener(CacheFileChunkListener
*aCallback
,
1600 nsIEventTarget
*aTarget
,
1603 CacheFileChunk
*aChunk
)
1605 LOG(("CacheFile::NotifyChunkListener() [this=%p, listener=%p, target=%p, "
1606 "rv=0x%08x, idx=%u, chunk=%p]", this, aCallback
, aTarget
, aResult
,
1607 aChunkIdx
, aChunk
));
1610 nsRefPtr
<NotifyChunkListenerEvent
> ev
;
1611 ev
= new NotifyChunkListenerEvent(aCallback
, aResult
, aChunkIdx
, aChunk
);
1613 rv
= aTarget
->Dispatch(ev
, NS_DISPATCH_NORMAL
);
1615 rv
= NS_DispatchToCurrentThread(ev
);
1616 NS_ENSURE_SUCCESS(rv
, rv
);
1622 CacheFile::QueueChunkListener(uint32_t aIndex
,
1623 CacheFileChunkListener
*aCallback
)
1625 LOG(("CacheFile::QueueChunkListener() [this=%p, idx=%u, listener=%p]",
1626 this, aIndex
, aCallback
));
1630 MOZ_ASSERT(aCallback
);
1632 ChunkListenerItem
*item
= new ChunkListenerItem();
1633 item
->mTarget
= CacheFileIOManager::IOTarget();
1634 if (!item
->mTarget
) {
1635 LOG(("CacheFile::QueueChunkListener() - Cannot get Cache I/O thread! Using "
1636 "main thread for callback."));
1637 item
->mTarget
= do_GetMainThread();
1639 item
->mCallback
= aCallback
;
1641 ChunkListeners
*listeners
;
1642 if (!mChunkListeners
.Get(aIndex
, &listeners
)) {
1643 listeners
= new ChunkListeners();
1644 mChunkListeners
.Put(aIndex
, listeners
);
1647 listeners
->mItems
.AppendElement(item
);
1652 CacheFile::NotifyChunkListeners(uint32_t aIndex
, nsresult aResult
,
1653 CacheFileChunk
*aChunk
)
1655 LOG(("CacheFile::NotifyChunkListeners() [this=%p, idx=%u, rv=0x%08x, "
1656 "chunk=%p]", this, aIndex
, aResult
, aChunk
));
1662 ChunkListeners
*listeners
;
1663 mChunkListeners
.Get(aIndex
, &listeners
);
1664 MOZ_ASSERT(listeners
);
1667 for (uint32_t i
= 0 ; i
< listeners
->mItems
.Length() ; i
++) {
1668 ChunkListenerItem
*item
= listeners
->mItems
[i
];
1669 rv2
= NotifyChunkListener(item
->mCallback
, item
->mTarget
, aResult
, aIndex
,
1671 if (NS_FAILED(rv2
) && NS_SUCCEEDED(rv
))
1676 mChunkListeners
.Remove(aIndex
);
1682 CacheFile::HaveChunkListeners(uint32_t aIndex
)
1684 ChunkListeners
*listeners
;
1685 mChunkListeners
.Get(aIndex
, &listeners
);
1690 CacheFile::NotifyListenersAboutOutputRemoval()
1692 LOG(("CacheFile::NotifyListenersAboutOutputRemoval() [this=%p]", this));
1696 // First fail all chunk listeners that wait for non-existent chunk
1697 mChunkListeners
.Enumerate(&CacheFile::FailListenersIfNonExistentChunk
,
1700 // Fail all update listeners
1701 mChunks
.Enumerate(&CacheFile::FailUpdateListeners
, this);
1705 CacheFile::DataSize(int64_t* aSize
)
1707 CacheFileAutoLock
lock(this);
1717 CacheFile::IsDoomed()
1722 return mHandle
->IsDoomed();
1726 CacheFile::IsWriteInProgress()
1728 // Returns true when there is a potentially unfinished write operation.
1729 // Not using lock for performance reasons. mMetadata is never released
1730 // during life time of CacheFile.
1732 bool result
= false;
1735 result
= mDataIsDirty
||
1736 (mMetadata
&& mMetadata
->IsDirty()) ||
1749 CacheFile::IsDirty()
1751 return mDataIsDirty
|| mMetadata
->IsDirty();
1755 CacheFile::WriteMetadataIfNeeded()
1757 LOG(("CacheFile::WriteMetadataIfNeeded() [this=%p]", this));
1759 CacheFileAutoLock
lock(this);
1762 WriteMetadataIfNeededLocked();
1766 CacheFile::WriteMetadataIfNeededLocked(bool aFireAndForget
)
1768 // When aFireAndForget is set to true, we are called from dtor.
1769 // |this| must not be referenced after this method returns!
1771 LOG(("CacheFile::WriteMetadataIfNeededLocked() [this=%p]", this));
1776 MOZ_ASSERT(!mMemoryOnly
);
1779 MOZ_CRASH("Must have metadata here");
1783 if (NS_FAILED(mStatus
))
1786 if (!IsDirty() || mOutput
|| mInputs
.Length() || mChunks
.Count() ||
1787 mWritingMetadata
|| mOpeningFile
)
1790 if (!aFireAndForget
) {
1791 // if aFireAndForget is set, we are called from dtor. Write
1792 // scheduler hard-refers CacheFile otherwise, so we cannot be here.
1793 CacheFileIOManager::UnscheduleMetadataWrite(this);
1796 LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing metadata [this=%p]",
1799 rv
= mMetadata
->WriteMetadata(mDataSize
, aFireAndForget
? nullptr : this);
1800 if (NS_SUCCEEDED(rv
)) {
1801 mWritingMetadata
= true;
1802 mDataIsDirty
= false;
1804 LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing synchronously "
1805 "failed [this=%p]", this));
1806 // TODO: close streams with error
1812 CacheFile::PostWriteTimer()
1817 LOG(("CacheFile::PostWriteTimer() [this=%p]", this));
1819 CacheFileIOManager::ScheduleMetadataWrite(this);
1823 CacheFile::WriteAllCachedChunks(const uint32_t& aIdx
,
1824 nsRefPtr
<CacheFileChunk
>& aChunk
,
1827 CacheFile
*file
= static_cast<CacheFile
*>(aClosure
);
1829 LOG(("CacheFile::WriteAllCachedChunks() [this=%p, idx=%u, chunk=%p]",
1830 file
, aIdx
, aChunk
.get()));
1832 file
->mChunks
.Put(aIdx
, aChunk
);
1833 aChunk
->mFile
= file
;
1834 aChunk
->mActiveChunk
= true;
1836 MOZ_ASSERT(aChunk
->IsReady());
1839 file
->ReleaseOutsideLock(aChunk
);
1841 return PL_DHASH_REMOVE
;
1845 CacheFile::FailListenersIfNonExistentChunk(
1846 const uint32_t& aIdx
,
1847 nsAutoPtr
<ChunkListeners
>& aListeners
,
1850 CacheFile
*file
= static_cast<CacheFile
*>(aClosure
);
1852 LOG(("CacheFile::FailListenersIfNonExistentChunk() [this=%p, idx=%u]",
1855 nsRefPtr
<CacheFileChunk
> chunk
;
1856 file
->mChunks
.Get(aIdx
, getter_AddRefs(chunk
));
1858 MOZ_ASSERT(!chunk
->IsReady());
1859 return PL_DHASH_NEXT
;
1862 for (uint32_t i
= 0 ; i
< aListeners
->mItems
.Length() ; i
++) {
1863 ChunkListenerItem
*item
= aListeners
->mItems
[i
];
1864 file
->NotifyChunkListener(item
->mCallback
, item
->mTarget
,
1865 NS_ERROR_NOT_AVAILABLE
, aIdx
, nullptr);
1869 return PL_DHASH_REMOVE
;
1873 CacheFile::FailUpdateListeners(
1874 const uint32_t& aIdx
,
1875 nsRefPtr
<CacheFileChunk
>& aChunk
,
1879 CacheFile
*file
= static_cast<CacheFile
*>(aClosure
);
1882 LOG(("CacheFile::FailUpdateListeners() [this=%p, idx=%u]",
1885 if (aChunk
->IsReady()) {
1886 aChunk
->NotifyUpdateListeners();
1889 return PL_DHASH_NEXT
;
1893 CacheFile::CleanUpCachedChunks(const uint32_t& aIdx
,
1894 nsRefPtr
<CacheFileChunk
>& aChunk
,
1897 CacheFile
*file
= static_cast<CacheFile
*>(aClosure
);
1899 LOG(("CacheFile::CleanUpCachedChunks() [this=%p, idx=%u, chunk=%p]", file
,
1900 aIdx
, aChunk
.get()));
1902 if (file
->MustKeepCachedChunk(aIdx
)) {
1903 LOG(("CacheFile::CleanUpCachedChunks() - Keeping chunk"));
1904 return PL_DHASH_NEXT
;
1907 LOG(("CacheFile::CleanUpCachedChunks() - Removing chunk"));
1908 return PL_DHASH_REMOVE
;
1912 CacheFile::PadChunkWithZeroes(uint32_t aChunkIdx
)
1916 // This method is used to pad last incomplete chunk with zeroes or create
1917 // a new chunk full of zeroes
1918 MOZ_ASSERT(mDataSize
/ kChunkSize
== aChunkIdx
);
1921 nsRefPtr
<CacheFileChunk
> chunk
;
1922 rv
= GetChunkLocked(aChunkIdx
, WRITER
, nullptr, getter_AddRefs(chunk
));
1923 NS_ENSURE_SUCCESS(rv
, rv
);
1925 LOG(("CacheFile::PadChunkWithZeroes() - Zeroing hole in chunk %d, range %d-%d"
1926 " [this=%p]", aChunkIdx
, chunk
->DataSize(), kChunkSize
- 1, this));
1928 rv
= chunk
->EnsureBufSize(kChunkSize
);
1929 if (NS_FAILED(rv
)) {
1930 ReleaseOutsideLock(chunk
.forget().take());
1934 memset(chunk
->BufForWriting() + chunk
->DataSize(), 0, kChunkSize
- chunk
->DataSize());
1936 chunk
->UpdateDataSize(chunk
->DataSize(), kChunkSize
- chunk
->DataSize(),
1939 ReleaseOutsideLock(chunk
.forget().take());
1945 CacheFile::SetError(nsresult aStatus
)
1949 if (NS_SUCCEEDED(mStatus
)) {
1952 CacheFileIOManager::DoomFile(mHandle
, nullptr);
1958 CacheFile::InitIndexEntry()
1960 MOZ_ASSERT(mHandle
);
1962 if (mHandle
->IsDoomed())
1967 rv
= CacheFileIOManager::InitIndexEntry(mHandle
,
1969 mMetadata
->IsAnonymous(),
1970 mMetadata
->IsInBrowser());
1971 NS_ENSURE_SUCCESS(rv
, rv
);
1974 mMetadata
->GetExpirationTime(&expTime
);
1977 mMetadata
->GetFrecency(&frecency
);
1979 rv
= CacheFileIOManager::UpdateIndexEntry(mHandle
, &frecency
, &expTime
);
1980 NS_ENSURE_SUCCESS(rv
, rv
);
1990 CollectChunkSize(uint32_t const & aIdx
,
1991 nsRefPtr
<mozilla::net::CacheFileChunk
> const & aChunk
,
1992 mozilla::MallocSizeOf mallocSizeOf
, void* aClosure
)
1994 return aChunk
->SizeOfIncludingThis(mallocSizeOf
);
2000 CacheFile::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf
) const
2002 CacheFileAutoLock
lock(const_cast<CacheFile
*>(this));
2005 n
+= mKey
.SizeOfExcludingThisIfUnshared(mallocSizeOf
);
2006 n
+= mChunks
.SizeOfExcludingThis(CollectChunkSize
, mallocSizeOf
);
2007 n
+= mCachedChunks
.SizeOfExcludingThis(CollectChunkSize
, mallocSizeOf
);
2009 n
+= mMetadata
->SizeOfIncludingThis(mallocSizeOf
);
2012 // Input streams are not elsewhere reported.
2013 n
+= mInputs
.SizeOfExcludingThis(mallocSizeOf
);
2014 for (uint32_t i
= 0; i
< mInputs
.Length(); ++i
) {
2015 n
+= mInputs
[i
]->SizeOfIncludingThis(mallocSizeOf
);
2018 // Output streams are not elsewhere reported.
2020 n
+= mOutput
->SizeOfIncludingThis(mallocSizeOf
);
2023 // The listeners are usually classes reported just above.
2024 n
+= mChunkListeners
.SizeOfExcludingThis(nullptr, mallocSizeOf
);
2025 n
+= mObjsToRelease
.SizeOfExcludingThis(mallocSizeOf
);
2027 // mHandle reported directly from CacheFileIOManager.
2033 CacheFile::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf
) const
2035 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf
);