Bumping manifests a=b2g-bump
[gecko.git] / netwerk / cache2 / CacheFile.cpp
blob55d4ec92706f76bcb6568b7d9e80d1644900d5b2
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/. */
5 #include "CacheLog.h"
6 #include "CacheFile.h"
8 #include "CacheFileChunk.h"
9 #include "CacheFileInputStream.h"
10 #include "CacheFileOutputStream.h"
11 #include "nsThreadUtils.h"
12 #include "mozilla/DebugOnly.h"
13 #include <algorithm>
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
27 namespace mozilla {
28 namespace net {
30 class NotifyCacheFileListenerEvent : public nsRunnable {
31 public:
32 NotifyCacheFileListenerEvent(CacheFileListener *aCallback,
33 nsresult aResult,
34 bool aIsNew)
35 : mCallback(aCallback)
36 , mRV(aResult)
37 , mIsNew(aIsNew)
39 LOG(("NotifyCacheFileListenerEvent::NotifyCacheFileListenerEvent() "
40 "[this=%p]", this));
41 MOZ_COUNT_CTOR(NotifyCacheFileListenerEvent);
44 protected:
45 ~NotifyCacheFileListenerEvent()
47 LOG(("NotifyCacheFileListenerEvent::~NotifyCacheFileListenerEvent() "
48 "[this=%p]", this));
49 MOZ_COUNT_DTOR(NotifyCacheFileListenerEvent);
52 public:
53 NS_IMETHOD Run()
55 LOG(("NotifyCacheFileListenerEvent::Run() [this=%p]", this));
57 mCallback->OnFileReady(mRV, mIsNew);
58 return NS_OK;
61 protected:
62 nsCOMPtr<CacheFileListener> mCallback;
63 nsresult mRV;
64 bool mIsNew;
67 class NotifyChunkListenerEvent : public nsRunnable {
68 public:
69 NotifyChunkListenerEvent(CacheFileChunkListener *aCallback,
70 nsresult aResult,
71 uint32_t aChunkIdx,
72 CacheFileChunk *aChunk)
73 : mCallback(aCallback)
74 , mRV(aResult)
75 , mChunkIdx(aChunkIdx)
76 , mChunk(aChunk)
78 LOG(("NotifyChunkListenerEvent::NotifyChunkListenerEvent() [this=%p]",
79 this));
80 MOZ_COUNT_CTOR(NotifyChunkListenerEvent);
83 protected:
84 ~NotifyChunkListenerEvent()
86 LOG(("NotifyChunkListenerEvent::~NotifyChunkListenerEvent() [this=%p]",
87 this));
88 MOZ_COUNT_DTOR(NotifyChunkListenerEvent);
91 public:
92 NS_IMETHOD Run()
94 LOG(("NotifyChunkListenerEvent::Run() [this=%p]", this));
96 mCallback->OnChunkAvailable(mRV, mChunkIdx, mChunk);
97 return NS_OK;
100 protected:
101 nsCOMPtr<CacheFileChunkListener> mCallback;
102 nsresult mRV;
103 uint32_t mChunkIdx;
104 nsRefPtr<CacheFileChunk> mChunk;
108 class DoomFileHelper : public CacheFileIOListener
110 public:
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
141 if (mListener)
142 mListener->OnFileDoomed(aResult);
143 return NS_OK;
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;
158 private:
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)
183 , mReady(false)
184 , mMemoryOnly(false)
185 , mOpenAsMemoryOnly(false)
186 , mPriority(false)
187 , mDataAccessed(false)
188 , mDataIsDirty(false)
189 , mWritingMetadata(false)
190 , mPreloadWithoutInputStreams(true)
191 , mPreloadChunkCount(0)
192 , mStatus(NS_OK)
193 , mDataSize(-1)
194 , mOutput(nullptr)
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);
210 nsresult
211 CacheFile::Init(const nsACString &aKey,
212 bool aCreateNew,
213 bool aMemoryOnly,
214 bool aPriority,
215 CacheFileListener *aCallback)
217 MOZ_ASSERT(!mListener);
218 MOZ_ASSERT(!mHandle);
220 nsresult rv;
222 mKey = aKey;
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));
240 if (mMemoryOnly) {
241 MOZ_ASSERT(!aCallback);
243 mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
244 mReady = true;
245 mDataSize = mMetadata->Offset();
246 return NS_OK;
248 else {
249 uint32_t flags;
250 if (aCreateNew) {
251 MOZ_ASSERT(!aCallback);
252 flags = CacheFileIOManager::CREATE_NEW;
254 // make sure we can use this entry immediately
255 mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
256 mReady = true;
257 mDataSize = mMetadata->Offset();
258 } else {
259 flags = CacheFileIOManager::CREATE;
262 if (mPriority) {
263 flags |= CacheFileIOManager::PRIORITY;
266 mOpeningFile = true;
267 mListener = aCallback;
268 rv = CacheFileIOManager::OpenFile(mKey, flags, this);
269 if (NS_FAILED(rv)) {
270 mListener = nullptr;
271 mOpeningFile = false;
273 if (aCreateNew) {
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));
279 mMemoryOnly = true;
281 else if (rv == NS_ERROR_NOT_INITIALIZED) {
282 NS_WARNING("Forcing memory-only entry since CacheIOManager isn't "
283 "initialized.");
284 LOG(("CacheFile::Init() - CacheFileIOManager isn't initialized, "
285 "initializing entry as memory-only. [this=%p]", this));
287 mMemoryOnly = true;
288 mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
289 mReady = true;
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);
297 else {
298 NS_ENSURE_SUCCESS(rv, rv);
303 return NS_OK;
306 nsresult
307 CacheFile::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk)
309 CacheFileAutoLock lock(this);
311 nsresult rv;
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)) {
319 SetError(aResult);
322 if (HaveChunkListeners(index)) {
323 rv = NotifyChunkListeners(index, aResult, aChunk);
324 NS_ENSURE_SUCCESS(rv, rv);
327 return NS_OK;
330 nsresult
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);
342 nsresult rv;
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);
349 MOZ_ASSERT(mHandle);
351 if (NS_FAILED(aResult)) {
352 SetError(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);
366 return NS_OK;
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()));
374 return NS_OK;
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;
382 return NS_OK;
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));
390 } else {
391 LOG(("CacheFile::OnChunkWritten() - Releasing failed chunk [this=%p, "
392 "chunk=%p]", this, aChunk));
395 RemoveChunkInternal(aChunk, keepChunk);
397 WriteMetadataIfNeededLocked();
399 return NS_OK;
402 nsresult
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;
410 nsresult
411 CacheFile::OnChunkUpdated(CacheFileChunk *aChunk)
413 MOZ_CRASH("CacheFile::OnChunkUpdated should not be called!");
414 return NS_ERROR_UNEXPECTED;
417 nsresult
418 CacheFile::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
420 nsresult rv;
422 // Using an 'auto' class to perform doom or fail the listener
423 // outside the CacheFile's lock.
424 class AutoFailDoomListener
426 public:
427 explicit AutoFailDoomListener(CacheFileHandle *aHandle)
428 : mHandle(aHandle)
429 , mAlreadyDoomed(false)
431 ~AutoFailDoomListener()
433 if (!mListener)
434 return;
436 if (mHandle) {
437 if (mAlreadyDoomed) {
438 mListener->OnFileDoomed(mHandle, NS_OK);
439 } else {
440 CacheFileIOManager::DoomFile(mHandle, mListener);
442 } else {
443 mListener->OnFileDoomed(nullptr, NS_ERROR_NOT_AVAILABLE);
447 CacheFileHandle* mHandle;
448 nsCOMPtr<CacheFileIOListener> mListener;
449 bool mAlreadyDoomed;
450 } autoDoom(aHandle);
452 nsCOMPtr<CacheFileListener> listener;
453 bool isNew = false;
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);
473 if (mMemoryOnly) {
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;
479 return NS_OK;
481 else if (NS_FAILED(aResult)) {
482 if (mMetadata) {
483 // This entry was initialized as createNew, just switch to memory-only
484 // mode.
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));
490 mMemoryOnly = true;
491 return NS_OK;
493 else if (aResult == NS_ERROR_FILE_INVALID_PATH) {
494 // CacheFileIOManager doesn't have mCacheDirectory, switch to
495 // memory-only mode.
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]",
500 this));
502 mMemoryOnly = true;
503 mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
504 mReady = true;
505 mDataSize = mMetadata->Offset();
507 isNew = true;
508 retval = NS_OK;
510 else {
511 // CacheFileIOManager::OpenFile() failed for another reason.
512 isNew = false;
513 retval = aResult;
516 mListener.swap(listener);
518 else {
519 mHandle = aHandle;
520 if (NS_FAILED(mStatus)) {
521 CacheFileIOManager::DoomFile(mHandle, nullptr);
524 if (mMetadata) {
525 InitIndexEntry();
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);
533 return NS_OK;
538 if (listener) {
539 listener->OnFileReady(retval, isNew);
540 return NS_OK;
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);
550 if (NS_FAILED(rv)) {
551 mListener.swap(listener);
552 listener->OnFileReady(rv, false);
555 return NS_OK;
558 nsresult
559 CacheFile::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
560 nsresult aResult)
562 MOZ_CRASH("CacheFile::OnDataWritten should not be called!");
563 return NS_ERROR_UNEXPECTED;
566 nsresult
567 CacheFile::OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult)
569 MOZ_CRASH("CacheFile::OnDataRead should not be called!");
570 return NS_ERROR_UNEXPECTED;
573 nsresult
574 CacheFile::OnMetadataRead(nsresult aResult)
576 MOZ_ASSERT(mListener);
578 LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08x]", this, aResult));
580 bool isNew = false;
581 if (NS_SUCCEEDED(aResult)) {
582 mReady = true;
583 mDataSize = mMetadata->Offset();
584 if (mDataSize == 0 && mMetadata->ElementsSize() == 0) {
585 isNew = true;
586 mMetadata->MarkDirty();
587 } else {
588 CacheFileAutoLock lock(this);
589 PreloadChunks(0);
592 InitIndexEntry();
595 nsCOMPtr<CacheFileListener> listener;
596 mListener.swap(listener);
597 listener->OnFileReady(aResult, isNew);
598 return NS_OK;
601 nsresult
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())
619 return NS_OK;
621 if (IsDirty())
622 WriteMetadataIfNeededLocked();
624 if (!mWritingMetadata) {
625 LOG(("CacheFile::OnMetadataWritten() - Releasing file handle [this=%p]",
626 this));
627 CacheFileIOManager::ReleaseNSPRHandle(mHandle);
630 return NS_OK;
633 nsresult
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);
650 return NS_OK;
653 nsresult
654 CacheFile::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
656 MOZ_CRASH("CacheFile::OnEOFSet should not be called!");
657 return NS_ERROR_UNEXPECTED;
660 nsresult
661 CacheFile::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult)
663 MOZ_CRASH("CacheFile::OnFileRenamed should not be called!");
664 return NS_ERROR_UNEXPECTED;
667 nsresult
668 CacheFile::OpenInputStream(nsIInputStream **_retval)
670 CacheFileAutoLock lock(this);
672 MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
674 if (!mReady) {
675 LOG(("CacheFile::OpenInputStream() - CacheFile is not ready [this=%p]",
676 this));
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.
691 return mStatus;
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]",
702 input, this));
704 mInputs.AppendElement(input);
705 NS_ADDREF(input);
707 mDataAccessed = true;
708 NS_ADDREF(*_retval = input);
709 return NS_OK;
712 nsresult
713 CacheFile::OpenOutputStream(CacheOutputCloseListener *aCloseListener, nsIOutputStream **_retval)
715 CacheFileAutoLock lock(this);
717 MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
719 if (!mReady) {
720 LOG(("CacheFile::OpenOutputStream() - CacheFile is not ready [this=%p]",
721 this));
723 return NS_ERROR_NOT_AVAILABLE;
726 if (mOutput) {
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);
746 return NS_OK;
749 nsresult
750 CacheFile::SetMemoryOnly()
752 LOG(("CacheFile::SetMemoryOnly() mMemoryOnly=%d [this=%p]",
753 mMemoryOnly, this));
755 if (mMemoryOnly)
756 return NS_OK;
758 MOZ_ASSERT(mReady);
760 if (!mReady) {
761 LOG(("CacheFile::SetMemoryOnly() - CacheFile is not ready [this=%p]",
762 this));
764 return NS_ERROR_NOT_AVAILABLE;
767 if (mDataAccessed) {
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???
773 mMemoryOnly = true;
774 return NS_OK;
777 nsresult
778 CacheFile::Doom(CacheFileListener *aCallback)
780 LOG(("CacheFile::Doom() [this=%p, listener=%p]", this, aCallback));
782 CacheFileAutoLock lock(this);
784 return DoomLocked(aCallback);
787 nsresult
788 CacheFile::DoomLocked(CacheFileListener *aCallback)
790 MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
792 LOG(("CacheFile::DoomLocked() [this=%p, listener=%p]", this, aCallback));
794 nsresult rv = NS_OK;
796 if (mMemoryOnly) {
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);
808 if (mHandle) {
809 rv = CacheFileIOManager::DoomFile(mHandle, listener);
810 } else if (mOpeningFile) {
811 mDoomAfterOpenListener = listener;
814 return rv;
817 nsresult
818 CacheFile::ThrowMemoryCachedData()
820 CacheFileAutoLock lock(this);
822 LOG(("CacheFile::ThrowMemoryCachedData() [this=%p]", this));
824 if (mMemoryOnly) {
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;
834 if (mOpeningFile) {
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);
848 return NS_OK;
851 nsresult
852 CacheFile::GetElement(const char *aKey, char **_retval)
854 CacheFileAutoLock lock(this);
855 MOZ_ASSERT(mMetadata);
856 NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
858 const char *value;
859 value = mMetadata->GetElement(aKey);
860 if (!value)
861 return NS_ERROR_NOT_AVAILABLE;
863 *_retval = NS_strdup(value);
864 return NS_OK;
867 nsresult
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);
874 PostWriteTimer();
875 return mMetadata->SetElement(aKey, aValue);
878 nsresult
879 CacheFile::VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor)
881 CacheFileAutoLock lock(this);
882 MOZ_ASSERT(mMetadata);
883 MOZ_ASSERT(mReady);
884 NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
886 return mMetadata->Visit(aVisitor);
889 nsresult
890 CacheFile::ElementsSize(uint32_t *_retval)
892 CacheFileAutoLock lock(this);
894 if (!mMetadata)
895 return NS_ERROR_NOT_AVAILABLE;
897 *_retval = mMetadata->ElementsSize();
898 return NS_OK;
901 nsresult
902 CacheFile::SetExpirationTime(uint32_t aExpirationTime)
904 CacheFileAutoLock lock(this);
905 MOZ_ASSERT(mMetadata);
906 NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
908 PostWriteTimer();
910 if (mHandle && !mHandle->IsDoomed())
911 CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, &aExpirationTime);
913 return mMetadata->SetExpirationTime(aExpirationTime);
916 nsresult
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);
926 nsresult
927 CacheFile::SetFrecency(uint32_t aFrecency)
929 CacheFileAutoLock lock(this);
930 MOZ_ASSERT(mMetadata);
931 NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
933 PostWriteTimer();
935 if (mHandle && !mHandle->IsDoomed())
936 CacheFileIOManager::UpdateIndexEntry(mHandle, &aFrecency, nullptr);
938 return mMetadata->SetFrecency(aFrecency);
941 nsresult
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);
951 nsresult
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);
961 nsresult
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);
971 nsresult
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);
981 nsresult
982 CacheFile::OnFetched()
984 CacheFileAutoLock lock(this);
985 MOZ_ASSERT(mMetadata);
986 NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
988 PostWriteTimer();
990 return mMetadata->OnFetched();
993 void
994 CacheFile::Lock()
996 mLock.Lock();
999 void
1000 CacheFile::Unlock()
1002 nsTArray<nsISupports*> objs;
1003 objs.SwapElements(mObjsToRelease);
1005 mLock.Unlock();
1007 for (uint32_t i = 0; i < objs.Length(); i++)
1008 objs[i]->Release();
1011 void
1012 CacheFile::AssertOwnsLock() const
1014 mLock.AssertCurrentThreadOwns();
1017 void
1018 CacheFile::ReleaseOutsideLock(nsISupports *aObject)
1020 AssertOwnsLock();
1022 mObjsToRelease.AppendElement(aObject);
1025 nsresult
1026 CacheFile::GetChunk(uint32_t aIndex, ECallerType aCaller,
1027 CacheFileChunkListener *aCallback, CacheFileChunk **_retval)
1029 CacheFileAutoLock lock(this);
1030 return GetChunkLocked(aIndex, aCaller, aCallback, _retval);
1033 nsresult
1034 CacheFile::GetChunkLocked(uint32_t aIndex, ECallerType aCaller,
1035 CacheFileChunkListener *aCallback,
1036 CacheFileChunk **_retval)
1038 AssertOwnsLock();
1040 LOG(("CacheFile::GetChunkLocked() [this=%p, idx=%u, caller=%d, listener=%p]",
1041 this, aIndex, aCaller, aCallback));
1043 MOZ_ASSERT(mReady);
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
1050 // is reader.
1051 bool preload = !mMemoryOnly && (aCaller == READER);
1053 nsresult rv;
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)) {
1067 SetError(rv);
1068 LOG(("CacheFile::GetChunkLocked() - Found failed chunk in mChunks "
1069 "[this=%p]", this));
1070 return rv;
1073 if (chunk->IsReady() || aCaller == WRITER) {
1074 chunk.swap(*_retval);
1075 } else {
1076 rv = QueueChunkListener(aIndex, aCallback);
1077 NS_ENSURE_SUCCESS(rv, rv);
1080 if (preload) {
1081 PreloadChunks(aIndex + 1);
1084 return NS_OK;
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);
1103 if (preload) {
1104 PreloadChunks(aIndex + 1);
1107 return NS_OK;
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);
1115 if (mMemoryOnly) {
1116 // If this ever really happen it is better to fail rather than crashing on
1117 // a null handle.
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);
1138 return rv;
1141 if (aCaller == WRITER) {
1142 chunk.swap(*_retval);
1143 } else if (aCaller != PRELOADER) {
1144 rv = QueueChunkListener(aIndex, aCallback);
1145 NS_ENSURE_SUCCESS(rv, rv);
1148 if (preload) {
1149 PreloadChunks(aIndex + 1);
1152 return NS_OK;
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));
1163 chunk->InitNew();
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);
1172 return NS_OK;
1174 } else {
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;
1188 if (mMemoryOnly) {
1189 // We need to create all missing CacheFileChunks if this is memory-only
1190 // entry
1191 for (uint32_t i = startChunk ; i < aIndex ; i++) {
1192 rv = PadChunkWithZeroes(i);
1193 NS_ENSURE_SUCCESS(rv, rv);
1195 } else {
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,
1204 nullptr);
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);
1212 } else {
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);
1224 return NS_OK;
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!");
1233 if (mOutput) {
1234 // the chunk doesn't exist but mOutput may create it
1235 rv = QueueChunkListener(aIndex, aCallback);
1236 NS_ENSURE_SUCCESS(rv, rv);
1237 } else {
1238 return NS_ERROR_NOT_AVAILABLE;
1241 return NS_OK;
1244 void
1245 CacheFile::PreloadChunks(uint32_t aIndex)
1247 AssertOwnsLock();
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.
1256 return;
1259 if (mChunks.GetWeak(i) || mCachedChunks.GetWeak(i)) {
1260 // This chunk is already in memory or is being read right now.
1261 continue;
1264 LOG(("CacheFile::PreloadChunks() - Preloading chunk [this=%p, idx=%u]",
1265 this, i));
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
1270 // returned.
1271 MOZ_ASSERT(!chunk);
1275 bool
1276 CacheFile::ShouldCacheChunk(uint32_t aIndex)
1278 AssertOwnsLock();
1280 #ifdef CACHE_CHUNKS
1281 // We cache all chunks.
1282 return true;
1283 #else
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.
1293 return true;
1296 // Cache only chunks that we really need to keep.
1297 return MustKeepCachedChunk(aIndex);
1298 #endif
1301 bool
1302 CacheFile::MustKeepCachedChunk(uint32_t aIndex)
1304 AssertOwnsLock();
1306 // We must keep the chunk when this is memory only entry or we don't have
1307 // a handle yet.
1308 if (mMemoryOnly || mOpeningFile) {
1309 return true;
1312 if (mPreloadChunkCount == 0) {
1313 // Preloading of chunks is disabled
1314 return false;
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
1325 int64_t minPos;
1326 if (mPreloadChunkCount >= aIndex) {
1327 minPos = 0;
1328 } else {
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) {
1335 return true;
1339 return false;
1342 nsresult
1343 CacheFile::DeactivateChunk(CacheFileChunk *aChunk)
1345 nsresult rv;
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()));
1356 MOZ_ASSERT(mReady);
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
1366 return NS_OK;
1369 #ifdef DEBUG
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);
1381 #endif
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);
1393 return mStatus;
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);
1410 SetError(rv);
1411 return rv;
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()
1418 chunk = nullptr;
1419 return NS_OK;
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);
1428 if (!mMemoryOnly)
1429 WriteMetadataIfNeededLocked();
1432 return NS_OK;
1435 void
1436 CacheFile::RemoveChunkInternal(CacheFileChunk *aChunk, bool aCacheChunk)
1438 AssertOwnsLock();
1440 aChunk->mActiveChunk = false;
1441 ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
1442 aChunk->mFile.forget().take()));
1444 if (aCacheChunk) {
1445 mCachedChunks.Put(aChunk->Index(), aChunk);
1448 mChunks.Remove(aChunk->Index());
1451 int64_t
1452 CacheFile::BytesFromChunk(uint32_t aIndex)
1454 AssertOwnsLock();
1456 if (!mDataSize)
1457 return 0;
1459 // Index of the last existing chunk.
1460 uint32_t lastChunk = (mDataSize - 1) / kChunkSize;
1461 if (aIndex > lastChunk)
1462 return 0;
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;
1468 if (mMemoryOnly) {
1469 maxPreloadedChunk = lastChunk;
1470 } else {
1471 maxPreloadedChunk = std::min(aIndex + mPreloadChunkCount, lastChunk);
1474 uint32_t i;
1475 for (i = aIndex; i <= maxPreloadedChunk; ++i) {
1476 CacheFileChunk * chunk;
1478 chunk = mChunks.GetWeak(i);
1479 if (chunk) {
1480 MOZ_ASSERT(i == lastChunk || chunk->mDataSize == kChunkSize);
1481 if (chunk->IsReady()) {
1482 continue;
1485 // don't search this chunk in cached
1486 break;
1489 chunk = mCachedChunks.GetWeak(i);
1490 if (chunk) {
1491 MOZ_ASSERT(i == lastChunk || chunk->mDataSize == kChunkSize);
1492 continue;
1495 break;
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);
1506 static uint32_t
1507 StatusToTelemetryEnum(nsresult aStatus)
1509 if (NS_SUCCEEDED(aStatus)) {
1510 return 0;
1513 switch (aStatus) {
1514 case NS_BASE_STREAM_CLOSED:
1515 return 0; // Log this as a success
1516 case NS_ERROR_OUT_OF_MEMORY:
1517 return 2;
1518 case NS_ERROR_FILE_DISK_FULL:
1519 return 3;
1520 case NS_ERROR_FILE_CORRUPTED:
1521 return 4;
1522 case NS_ERROR_FILE_NOT_FOUND:
1523 return 5;
1524 case NS_BINDING_ABORTED:
1525 return 6;
1526 default:
1527 return 1; // other error
1530 NS_NOTREACHED("We should never get here");
1533 nsresult
1534 CacheFile::RemoveInput(CacheFileInputStream *aInput, nsresult aStatus)
1536 CacheFileAutoLock lock(this);
1538 LOG(("CacheFile::RemoveInput() [this=%p, input=%p, status=0x%08x]", this,
1539 aInput, aStatus));
1541 DebugOnly<bool> found;
1542 found = mInputs.RemoveElement(aInput);
1543 MOZ_ASSERT(found);
1545 ReleaseOutsideLock(static_cast<nsIInputStream*>(aInput));
1547 if (!mMemoryOnly)
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));
1557 return NS_OK;
1560 nsresult
1561 CacheFile::RemoveOutput(CacheFileOutputStream *aOutput, nsresult aStatus)
1563 AssertOwnsLock();
1565 LOG(("CacheFile::RemoveOutput() [this=%p, output=%p, status=0x%08x]", this,
1566 aOutput, aStatus));
1568 if (mOutput != aOutput) {
1569 LOG(("CacheFile::RemoveOutput() - This output was already removed, ignoring"
1570 " call [this=%p]", this));
1571 return NS_OK;
1574 mOutput = nullptr;
1576 // Cancel all queued chunk and update listeners that cannot be satisfied
1577 NotifyListenersAboutOutputRemoval();
1579 if (!mMemoryOnly)
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) {
1586 mStatus = aStatus;
1589 // Notify close listener as the last action
1590 aOutput->NotifyCloseListener();
1592 Telemetry::Accumulate(Telemetry::NETWORK_CACHE_V2_OUTPUT_STREAM_STATUS,
1593 StatusToTelemetryEnum(aStatus));
1595 return NS_OK;
1598 nsresult
1599 CacheFile::NotifyChunkListener(CacheFileChunkListener *aCallback,
1600 nsIEventTarget *aTarget,
1601 nsresult aResult,
1602 uint32_t aChunkIdx,
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));
1609 nsresult rv;
1610 nsRefPtr<NotifyChunkListenerEvent> ev;
1611 ev = new NotifyChunkListenerEvent(aCallback, aResult, aChunkIdx, aChunk);
1612 if (aTarget)
1613 rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
1614 else
1615 rv = NS_DispatchToCurrentThread(ev);
1616 NS_ENSURE_SUCCESS(rv, rv);
1618 return NS_OK;
1621 nsresult
1622 CacheFile::QueueChunkListener(uint32_t aIndex,
1623 CacheFileChunkListener *aCallback)
1625 LOG(("CacheFile::QueueChunkListener() [this=%p, idx=%u, listener=%p]",
1626 this, aIndex, aCallback));
1628 AssertOwnsLock();
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);
1648 return NS_OK;
1651 nsresult
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));
1658 AssertOwnsLock();
1660 nsresult rv, rv2;
1662 ChunkListeners *listeners;
1663 mChunkListeners.Get(aIndex, &listeners);
1664 MOZ_ASSERT(listeners);
1666 rv = NS_OK;
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,
1670 aChunk);
1671 if (NS_FAILED(rv2) && NS_SUCCEEDED(rv))
1672 rv = rv2;
1673 delete item;
1676 mChunkListeners.Remove(aIndex);
1678 return rv;
1681 bool
1682 CacheFile::HaveChunkListeners(uint32_t aIndex)
1684 ChunkListeners *listeners;
1685 mChunkListeners.Get(aIndex, &listeners);
1686 return !!listeners;
1689 void
1690 CacheFile::NotifyListenersAboutOutputRemoval()
1692 LOG(("CacheFile::NotifyListenersAboutOutputRemoval() [this=%p]", this));
1694 AssertOwnsLock();
1696 // First fail all chunk listeners that wait for non-existent chunk
1697 mChunkListeners.Enumerate(&CacheFile::FailListenersIfNonExistentChunk,
1698 this);
1700 // Fail all update listeners
1701 mChunks.Enumerate(&CacheFile::FailUpdateListeners, this);
1704 bool
1705 CacheFile::DataSize(int64_t* aSize)
1707 CacheFileAutoLock lock(this);
1709 if (mOutput)
1710 return false;
1712 *aSize = mDataSize;
1713 return true;
1716 bool
1717 CacheFile::IsDoomed()
1719 if (!mHandle)
1720 return false;
1722 return mHandle->IsDoomed();
1725 bool
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;
1734 if (!mMemoryOnly) {
1735 result = mDataIsDirty ||
1736 (mMetadata && mMetadata->IsDirty()) ||
1737 mWritingMetadata;
1740 result = result ||
1741 mOpeningFile ||
1742 mOutput ||
1743 mChunks.Count();
1745 return result;
1748 bool
1749 CacheFile::IsDirty()
1751 return mDataIsDirty || mMetadata->IsDirty();
1754 void
1755 CacheFile::WriteMetadataIfNeeded()
1757 LOG(("CacheFile::WriteMetadataIfNeeded() [this=%p]", this));
1759 CacheFileAutoLock lock(this);
1761 if (!mMemoryOnly)
1762 WriteMetadataIfNeededLocked();
1765 void
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));
1773 nsresult rv;
1775 AssertOwnsLock();
1776 MOZ_ASSERT(!mMemoryOnly);
1778 if (!mMetadata) {
1779 MOZ_CRASH("Must have metadata here");
1780 return;
1783 if (NS_FAILED(mStatus))
1784 return;
1786 if (!IsDirty() || mOutput || mInputs.Length() || mChunks.Count() ||
1787 mWritingMetadata || mOpeningFile)
1788 return;
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]",
1797 this));
1799 rv = mMetadata->WriteMetadata(mDataSize, aFireAndForget ? nullptr : this);
1800 if (NS_SUCCEEDED(rv)) {
1801 mWritingMetadata = true;
1802 mDataIsDirty = false;
1803 } else {
1804 LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing synchronously "
1805 "failed [this=%p]", this));
1806 // TODO: close streams with error
1807 SetError(rv);
1811 void
1812 CacheFile::PostWriteTimer()
1814 if (mMemoryOnly)
1815 return;
1817 LOG(("CacheFile::PostWriteTimer() [this=%p]", this));
1819 CacheFileIOManager::ScheduleMetadataWrite(this);
1822 PLDHashOperator
1823 CacheFile::WriteAllCachedChunks(const uint32_t& aIdx,
1824 nsRefPtr<CacheFileChunk>& aChunk,
1825 void* aClosure)
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());
1838 NS_ADDREF(aChunk);
1839 file->ReleaseOutsideLock(aChunk);
1841 return PL_DHASH_REMOVE;
1844 PLDHashOperator
1845 CacheFile::FailListenersIfNonExistentChunk(
1846 const uint32_t& aIdx,
1847 nsAutoPtr<ChunkListeners>& aListeners,
1848 void* aClosure)
1850 CacheFile *file = static_cast<CacheFile*>(aClosure);
1852 LOG(("CacheFile::FailListenersIfNonExistentChunk() [this=%p, idx=%u]",
1853 file, aIdx));
1855 nsRefPtr<CacheFileChunk> chunk;
1856 file->mChunks.Get(aIdx, getter_AddRefs(chunk));
1857 if (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);
1866 delete item;
1869 return PL_DHASH_REMOVE;
1872 PLDHashOperator
1873 CacheFile::FailUpdateListeners(
1874 const uint32_t& aIdx,
1875 nsRefPtr<CacheFileChunk>& aChunk,
1876 void* aClosure)
1878 #ifdef PR_LOGGING
1879 CacheFile *file = static_cast<CacheFile*>(aClosure);
1880 #endif
1882 LOG(("CacheFile::FailUpdateListeners() [this=%p, idx=%u]",
1883 file, aIdx));
1885 if (aChunk->IsReady()) {
1886 aChunk->NotifyUpdateListeners();
1889 return PL_DHASH_NEXT;
1892 PLDHashOperator
1893 CacheFile::CleanUpCachedChunks(const uint32_t& aIdx,
1894 nsRefPtr<CacheFileChunk>& aChunk,
1895 void* aClosure)
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;
1911 nsresult
1912 CacheFile::PadChunkWithZeroes(uint32_t aChunkIdx)
1914 AssertOwnsLock();
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);
1920 nsresult rv;
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());
1931 SetError(rv);
1932 return rv;
1934 memset(chunk->BufForWriting() + chunk->DataSize(), 0, kChunkSize - chunk->DataSize());
1936 chunk->UpdateDataSize(chunk->DataSize(), kChunkSize - chunk->DataSize(),
1937 false);
1939 ReleaseOutsideLock(chunk.forget().take());
1941 return NS_OK;
1944 void
1945 CacheFile::SetError(nsresult aStatus)
1947 AssertOwnsLock();
1949 if (NS_SUCCEEDED(mStatus)) {
1950 mStatus = aStatus;
1951 if (mHandle) {
1952 CacheFileIOManager::DoomFile(mHandle, nullptr);
1957 nsresult
1958 CacheFile::InitIndexEntry()
1960 MOZ_ASSERT(mHandle);
1962 if (mHandle->IsDoomed())
1963 return NS_OK;
1965 nsresult rv;
1967 rv = CacheFileIOManager::InitIndexEntry(mHandle,
1968 mMetadata->AppId(),
1969 mMetadata->IsAnonymous(),
1970 mMetadata->IsInBrowser());
1971 NS_ENSURE_SUCCESS(rv, rv);
1973 uint32_t expTime;
1974 mMetadata->GetExpirationTime(&expTime);
1976 uint32_t frecency;
1977 mMetadata->GetFrecency(&frecency);
1979 rv = CacheFileIOManager::UpdateIndexEntry(mHandle, &frecency, &expTime);
1980 NS_ENSURE_SUCCESS(rv, rv);
1982 return NS_OK;
1985 // Memory reporting
1987 namespace { // anon
1989 size_t
1990 CollectChunkSize(uint32_t const & aIdx,
1991 nsRefPtr<mozilla::net::CacheFileChunk> const & aChunk,
1992 mozilla::MallocSizeOf mallocSizeOf, void* aClosure)
1994 return aChunk->SizeOfIncludingThis(mallocSizeOf);
1997 } // anon
1999 size_t
2000 CacheFile::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
2002 CacheFileAutoLock lock(const_cast<CacheFile*>(this));
2004 size_t n = 0;
2005 n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
2006 n += mChunks.SizeOfExcludingThis(CollectChunkSize, mallocSizeOf);
2007 n += mCachedChunks.SizeOfExcludingThis(CollectChunkSize, mallocSizeOf);
2008 if (mMetadata) {
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.
2019 if (mOutput) {
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.
2029 return n;
2032 size_t
2033 CacheFile::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
2035 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
2038 } // net
2039 } // mozilla