Bug 1494162 - Part 45: Lazy load Menu and MenuItem in TabBar. r=pbro
[gecko.git] / netwerk / cache2 / CacheEntry.cpp
blob752a9c0de820cba7daac182727aa51d272fdd6cb
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 "CacheEntry.h"
7 #include "CacheStorageService.h"
8 #include "CacheObserver.h"
9 #include "CacheFileUtils.h"
10 #include "CacheIndex.h"
12 #include "nsIInputStream.h"
13 #include "nsIOutputStream.h"
14 #include "nsISeekableStream.h"
15 #include "nsIURI.h"
16 #include "nsICacheEntryOpenCallback.h"
17 #include "nsICacheStorage.h"
18 #include "nsISerializable.h"
19 #include "nsIStreamTransportService.h"
20 #include "nsISizeOf.h"
22 #include "nsComponentManagerUtils.h"
23 #include "nsServiceManagerUtils.h"
24 #include "nsString.h"
25 #include "nsProxyRelease.h"
26 #include "nsSerializationHelper.h"
27 #include "nsThreadUtils.h"
28 #include "mozilla/Telemetry.h"
29 #include "mozilla/IntegerPrintfMacros.h"
30 #include <math.h>
31 #include <algorithm>
33 namespace mozilla {
34 namespace net {
36 static uint32_t const ENTRY_WANTED =
37 nsICacheEntryOpenCallback::ENTRY_WANTED;
38 static uint32_t const RECHECK_AFTER_WRITE_FINISHED =
39 nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
40 static uint32_t const ENTRY_NEEDS_REVALIDATION =
41 nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION;
42 static uint32_t const ENTRY_NOT_WANTED =
43 nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
45 NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry)
47 // CacheEntryHandle
49 CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry)
50 : mEntry(aEntry)
51 , mClosed(false)
53 #ifdef DEBUG
54 if (!mEntry->HandlesCount()) {
55 // CacheEntry.mHandlesCount must go from zero to one only under
56 // the service lock. Can access CacheStorageService::Self() w/o a check
57 // since CacheEntry hrefs it.
58 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
60 #endif
62 mEntry->AddHandleRef();
64 LOG(("New CacheEntryHandle %p for entry %p", this, aEntry));
67 NS_IMETHODIMP CacheEntryHandle::Dismiss()
69 LOG(("CacheEntryHandle::Dismiss %p", this));
71 if (mClosed.compareExchange(false, true)) {
72 mEntry->OnHandleClosed(this);
73 return NS_OK;
76 LOG((" already dropped"));
77 return NS_ERROR_UNEXPECTED;
80 CacheEntryHandle::~CacheEntryHandle()
82 mEntry->ReleaseHandleRef();
83 Dismiss();
85 LOG(("CacheEntryHandle::~CacheEntryHandle %p", this));
88 // CacheEntry::Callback
90 CacheEntry::Callback::Callback(CacheEntry* aEntry,
91 nsICacheEntryOpenCallback *aCallback,
92 bool aReadOnly, bool aCheckOnAnyThread,
93 bool aSecret)
94 : mEntry(aEntry)
95 , mCallback(aCallback)
96 , mTarget(GetCurrentThreadEventTarget())
97 , mReadOnly(aReadOnly)
98 , mRevalidating(false)
99 , mCheckOnAnyThread(aCheckOnAnyThread)
100 , mRecheckAfterWrite(false)
101 , mNotWanted(false)
102 , mSecret(aSecret)
103 , mDoomWhenFoundPinned(false)
104 , mDoomWhenFoundNonPinned(false)
106 MOZ_COUNT_CTOR(CacheEntry::Callback);
108 // The counter may go from zero to non-null only under the service lock
109 // but here we expect it to be already positive.
110 MOZ_ASSERT(mEntry->HandlesCount());
111 mEntry->AddHandleRef();
114 CacheEntry::Callback::Callback(CacheEntry* aEntry, bool aDoomWhenFoundInPinStatus)
115 : mEntry(aEntry)
116 , mReadOnly(false)
117 , mRevalidating(false)
118 , mCheckOnAnyThread(true)
119 , mRecheckAfterWrite(false)
120 , mNotWanted(false)
121 , mSecret(false)
122 , mDoomWhenFoundPinned(aDoomWhenFoundInPinStatus == true)
123 , mDoomWhenFoundNonPinned(aDoomWhenFoundInPinStatus == false)
125 MOZ_COUNT_CTOR(CacheEntry::Callback);
126 MOZ_ASSERT(mEntry->HandlesCount());
127 mEntry->AddHandleRef();
130 CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
131 : mEntry(aThat.mEntry)
132 , mCallback(aThat.mCallback)
133 , mTarget(aThat.mTarget)
134 , mReadOnly(aThat.mReadOnly)
135 , mRevalidating(aThat.mRevalidating)
136 , mCheckOnAnyThread(aThat.mCheckOnAnyThread)
137 , mRecheckAfterWrite(aThat.mRecheckAfterWrite)
138 , mNotWanted(aThat.mNotWanted)
139 , mSecret(aThat.mSecret)
140 , mDoomWhenFoundPinned(aThat.mDoomWhenFoundPinned)
141 , mDoomWhenFoundNonPinned(aThat.mDoomWhenFoundNonPinned)
143 MOZ_COUNT_CTOR(CacheEntry::Callback);
145 // The counter may go from zero to non-null only under the service lock
146 // but here we expect it to be already positive.
147 MOZ_ASSERT(mEntry->HandlesCount());
148 mEntry->AddHandleRef();
151 CacheEntry::Callback::~Callback()
153 ProxyRelease("CacheEntry::Callback::mCallback", mCallback, mTarget);
155 mEntry->ReleaseHandleRef();
156 MOZ_COUNT_DTOR(CacheEntry::Callback);
159 void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry)
161 if (mEntry == aEntry)
162 return;
164 // The counter may go from zero to non-null only under the service lock
165 // but here we expect it to be already positive.
166 MOZ_ASSERT(aEntry->HandlesCount());
167 aEntry->AddHandleRef();
168 mEntry->ReleaseHandleRef();
169 mEntry = aEntry;
172 bool CacheEntry::Callback::DeferDoom(bool *aDoom) const
174 MOZ_ASSERT(mEntry->mPinningKnown);
176 if (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) || MOZ_UNLIKELY(mDoomWhenFoundPinned)) {
177 *aDoom = (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && MOZ_LIKELY(!mEntry->mPinned)) ||
178 (MOZ_UNLIKELY(mDoomWhenFoundPinned) && MOZ_UNLIKELY(mEntry->mPinned));
180 return true;
183 return false;
186 nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const
188 if (!mCheckOnAnyThread) {
189 // Check we are on the target
190 return mTarget->IsOnCurrentThread(aOnCheckThread);
193 // We can invoke check anywhere
194 *aOnCheckThread = true;
195 return NS_OK;
198 nsresult CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread) const
200 return mTarget->IsOnCurrentThread(aOnAvailThread);
203 // CacheEntry
205 NS_IMPL_ISUPPORTS(CacheEntry,
206 nsIRunnable,
207 CacheFileListener)
209 /* static */
210 uint64_t CacheEntry::GetNextId()
212 static Atomic<uint64_t, Relaxed> id(0);
213 return ++id;
216 CacheEntry::CacheEntry(const nsACString& aStorageID,
217 const nsACString& aURI,
218 const nsACString& aEnhanceID,
219 bool aUseDisk,
220 bool aSkipSizeCheck,
221 bool aPin)
222 : mFrecency(0)
223 , mSortingExpirationTime(uint32_t(-1))
224 , mLock("CacheEntry")
225 , mFileStatus(NS_ERROR_NOT_INITIALIZED)
226 , mURI(aURI)
227 , mEnhanceID(aEnhanceID)
228 , mStorageID(aStorageID)
229 , mUseDisk(aUseDisk)
230 , mSkipSizeCheck(aSkipSizeCheck)
231 , mIsDoomed(false)
232 , mSecurityInfoLoaded(false)
233 , mPreventCallbacks(false)
234 , mHasData(false)
235 , mPinned(aPin)
236 , mPinningKnown(false)
237 , mState(NOTLOADED)
238 , mRegistration(NEVERREGISTERED)
239 , mWriter(nullptr)
240 , mUseCount(0)
241 , mCacheEntryId(GetNextId())
243 LOG(("CacheEntry::CacheEntry [this=%p]", this));
245 mService = CacheStorageService::Self();
247 CacheStorageService::Self()->RecordMemoryOnlyEntry(
248 this, !aUseDisk, true /* overwrite */);
251 CacheEntry::~CacheEntry()
253 LOG(("CacheEntry::~CacheEntry [this=%p]", this));
256 char const * CacheEntry::StateString(uint32_t aState)
258 switch (aState) {
259 case NOTLOADED: return "NOTLOADED";
260 case LOADING: return "LOADING";
261 case EMPTY: return "EMPTY";
262 case WRITING: return "WRITING";
263 case READY: return "READY";
264 case REVALIDATING: return "REVALIDATING";
267 return "?";
270 nsresult CacheEntry::HashingKeyWithStorage(nsACString &aResult) const
272 return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
275 nsresult CacheEntry::HashingKey(nsACString &aResult) const
277 return HashingKey(EmptyCString(), mEnhanceID, mURI, aResult);
280 // static
281 nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
282 const nsACString& aEnhanceID,
283 nsIURI* aURI,
284 nsACString &aResult)
286 nsAutoCString spec;
287 nsresult rv = aURI->GetAsciiSpec(spec);
288 NS_ENSURE_SUCCESS(rv, rv);
290 return HashingKey(aStorageID, aEnhanceID, spec, aResult);
293 // static
294 nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
295 const nsACString& aEnhanceID,
296 const nsACString& aURISpec,
297 nsACString &aResult)
300 * This key is used to salt hash that is a base for disk file name.
301 * Changing it will cause we will not be able to find files on disk.
304 aResult.Assign(aStorageID);
306 if (!aEnhanceID.IsEmpty()) {
307 CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID);
310 // Appending directly
311 aResult.Append(':');
312 aResult.Append(aURISpec);
314 return NS_OK;
317 void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags)
319 LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]",
320 this, StateString(mState), aFlags, aCallback));
322 bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
323 bool bypassIfBusy = aFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY;
324 bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
325 bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
326 bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
327 bool secret = aFlags & nsICacheStorage::OPEN_SECRETLY;
329 MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
330 MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry");
332 Callback callback(this, aCallback, readonly, multithread, secret);
334 if (!Open(callback, truncate, priority, bypassIfBusy)) {
335 // We get here when the callback wants to bypass cache when it's busy.
336 LOG((" writing or revalidating, callback wants to bypass cache"));
337 callback.mNotWanted = true;
338 InvokeAvailableCallback(callback);
342 bool CacheEntry::Open(Callback & aCallback, bool aTruncate,
343 bool aPriority, bool aBypassIfBusy)
345 mozilla::MutexAutoLock lock(mLock);
347 // Check state under the lock
348 if (aBypassIfBusy && (mState == WRITING || mState == REVALIDATING)) {
349 return false;
352 RememberCallback(aCallback);
354 // Load() opens the lock
355 if (Load(aTruncate, aPriority)) {
356 // Loading is in progress...
357 return true;
360 InvokeCallbacks();
362 return true;
365 bool CacheEntry::Load(bool aTruncate, bool aPriority)
367 LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
369 mLock.AssertCurrentThreadOwns();
371 if (mState > LOADING) {
372 LOG((" already loaded"));
373 return false;
376 if (mState == LOADING) {
377 LOG((" already loading"));
378 return true;
381 mState = LOADING;
383 MOZ_ASSERT(!mFile);
385 nsresult rv;
387 nsAutoCString fileKey;
388 rv = HashingKeyWithStorage(fileKey);
390 bool reportMiss = false;
392 // Check the index under two conditions for two states and take appropriate action:
393 // 1. When this is a disk entry and not told to truncate, check there is a disk file.
394 // If not, set the 'truncate' flag to true so that this entry will open instantly
395 // as a new one.
396 // 2. When this is a memory-only entry, check there is a disk file.
397 // If there is or could be, doom that file.
398 if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) {
399 // Check the index right now to know we have or have not the entry
400 // as soon as possible.
401 CacheIndex::EntryStatus status;
402 if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) {
403 switch (status) {
404 case CacheIndex::DOES_NOT_EXIST:
405 // Doesn't apply to memory-only entries, Load() is called only once for them
406 // and never again for their session lifetime.
407 if (!aTruncate && mUseDisk) {
408 LOG((" entry doesn't exist according information from the index, truncating"));
409 reportMiss = true;
410 aTruncate = true;
412 break;
413 case CacheIndex::EXISTS:
414 case CacheIndex::DO_NOT_KNOW:
415 if (!mUseDisk) {
416 LOG((" entry open as memory-only, but there is a file, status=%d, dooming it", status));
417 CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
419 break;
424 mFile = new CacheFile();
426 BackgroundOp(Ops::REGISTER);
428 bool directLoad = aTruncate || !mUseDisk;
429 if (directLoad) {
430 // mLoadStart will be used to calculate telemetry of life-time of this entry.
431 // Low resulution is then enough.
432 mLoadStart = TimeStamp::NowLoRes();
433 mPinningKnown = true;
434 } else {
435 mLoadStart = TimeStamp::Now();
439 mozilla::MutexAutoUnlock unlock(mLock);
441 if (reportMiss) {
442 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
443 CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
446 LOG((" performing load, file=%p", mFile.get()));
447 if (NS_SUCCEEDED(rv)) {
448 rv = mFile->Init(fileKey,
449 aTruncate,
450 !mUseDisk,
451 mSkipSizeCheck,
452 aPriority,
453 mPinned,
454 directLoad ? nullptr : this);
457 if (NS_FAILED(rv)) {
458 mFileStatus = rv;
459 AsyncDoom(nullptr);
460 return false;
464 if (directLoad) {
465 // Just fake the load has already been done as "new".
466 mFileStatus = NS_OK;
467 mState = EMPTY;
470 return mState == LOADING;
473 NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
475 LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08" PRIx32 ", new=%d]",
476 this, static_cast<uint32_t>(aResult), aIsNew));
478 MOZ_ASSERT(!mLoadStart.IsNull());
480 if (NS_SUCCEEDED(aResult)) {
481 if (aIsNew) {
482 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
483 CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
484 } else {
485 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
486 CacheFileUtils::DetailedCacheHitTelemetry::HIT, mLoadStart);
490 // OnFileReady, that is the only code that can transit from LOADING
491 // to any follow-on state and can only be invoked ones on an entry.
492 // Until this moment there is no consumer that could manipulate
493 // the entry state.
495 mozilla::MutexAutoLock lock(mLock);
497 MOZ_ASSERT(mState == LOADING);
499 mState = (aIsNew || NS_FAILED(aResult))
500 ? EMPTY
501 : READY;
503 mFileStatus = aResult;
505 mPinned = mFile->IsPinned();;
506 mPinningKnown = true;
507 LOG((" pinning=%d", mPinned));
509 if (mState == READY) {
510 mHasData = true;
512 uint32_t frecency;
513 mFile->GetFrecency(&frecency);
514 // mFrecency is held in a double to increase computance precision.
515 // It is ok to persist frecency only as a uint32 with some math involved.
516 mFrecency = INT2FRECENCY(frecency);
519 InvokeCallbacks();
521 return NS_OK;
524 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult)
526 if (mDoomCallback) {
527 RefPtr<DoomCallbackRunnable> event =
528 new DoomCallbackRunnable(this, aResult);
529 NS_DispatchToMainThread(event);
532 return NS_OK;
535 already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(bool aMemoryOnly,
536 nsICacheEntryOpenCallback* aCallback)
538 LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
540 mLock.AssertCurrentThreadOwns();
542 // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly
543 mPreventCallbacks = true;
545 RefPtr<CacheEntryHandle> handle;
546 RefPtr<CacheEntry> newEntry;
548 if (mPinned) {
549 MOZ_ASSERT(mUseDisk);
550 // We want to pin even no-store entries (the case we recreate a disk entry as
551 // a memory-only entry.)
552 aMemoryOnly = false;
555 mozilla::MutexAutoUnlock unlock(mLock);
557 // The following call dooms this entry (calls DoomAlreadyRemoved on us)
558 nsresult rv = CacheStorageService::Self()->AddStorageEntry(
559 GetStorageID(), GetURI(), GetEnhanceID(),
560 mUseDisk && !aMemoryOnly,
561 mSkipSizeCheck,
562 mPinned,
563 true, // truncate existing (this one)
564 getter_AddRefs(handle));
566 if (NS_SUCCEEDED(rv)) {
567 newEntry = handle->Entry();
568 LOG((" exchanged entry %p by entry %p, rv=0x%08" PRIx32,
569 this, newEntry.get(), static_cast<uint32_t>(rv)));
570 newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
571 } else {
572 LOG((" exchanged of entry %p failed, rv=0x%08" PRIx32,
573 this, static_cast<uint32_t>(rv)));
574 AsyncDoom(nullptr);
578 mPreventCallbacks = false;
580 if (!newEntry)
581 return nullptr;
583 newEntry->TransferCallbacks(*this);
584 mCallbacks.Clear();
586 // Must return a new write handle, since the consumer is expected to
587 // write to this newly recreated entry. The |handle| is only a common
588 // reference counter and doesn't revert entry state back when write
589 // fails and also doesn't update the entry frecency. Not updating
590 // frecency causes entries to not be purged from our memory pools.
591 RefPtr<CacheEntryHandle> writeHandle =
592 newEntry->NewWriteHandle();
593 return writeHandle.forget();
596 void CacheEntry::TransferCallbacks(CacheEntry & aFromEntry)
598 mozilla::MutexAutoLock lock(mLock);
600 LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]",
601 this, &aFromEntry));
603 if (!mCallbacks.Length())
604 mCallbacks.SwapElements(aFromEntry.mCallbacks);
605 else
606 mCallbacks.AppendElements(aFromEntry.mCallbacks);
608 uint32_t callbacksLength = mCallbacks.Length();
609 if (callbacksLength) {
610 // Carry the entry reference (unfortunately, needs to be done manually...)
611 for (uint32_t i = 0; i < callbacksLength; ++i)
612 mCallbacks[i].ExchangeEntry(this);
614 BackgroundOp(Ops::CALLBACKS, true);
618 void CacheEntry::RememberCallback(Callback & aCallback)
620 mLock.AssertCurrentThreadOwns();
622 LOG(("CacheEntry::RememberCallback [this=%p, cb=%p, state=%s]",
623 this, aCallback.mCallback.get(), StateString(mState)));
625 mCallbacks.AppendElement(aCallback);
628 void CacheEntry::InvokeCallbacksLock()
630 mozilla::MutexAutoLock lock(mLock);
631 InvokeCallbacks();
634 void CacheEntry::InvokeCallbacks()
636 mLock.AssertCurrentThreadOwns();
638 LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
640 // Invoke first all r/w callbacks, then all r/o callbacks.
641 if (InvokeCallbacks(false))
642 InvokeCallbacks(true);
644 LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
647 bool CacheEntry::InvokeCallbacks(bool aReadOnly)
649 mLock.AssertCurrentThreadOwns();
651 RefPtr<CacheEntryHandle> recreatedHandle;
653 uint32_t i = 0;
654 while (i < mCallbacks.Length()) {
655 if (mPreventCallbacks) {
656 LOG((" callbacks prevented!"));
657 return false;
660 if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
661 LOG((" entry is being written/revalidated"));
662 return false;
665 bool recreate;
666 if (mCallbacks[i].DeferDoom(&recreate)) {
667 mCallbacks.RemoveElementAt(i);
668 if (!recreate) {
669 continue;
672 LOG((" defer doom marker callback hit positive, recreating"));
673 recreatedHandle = ReopenTruncated(!mUseDisk, nullptr);
674 break;
677 if (mCallbacks[i].mReadOnly != aReadOnly) {
678 // Callback is not r/w or r/o, go to another one in line
679 ++i;
680 continue;
683 bool onCheckThread;
684 nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
686 if (NS_SUCCEEDED(rv) && !onCheckThread) {
687 // Redispatch to the target thread
688 rv = mCallbacks[i].mTarget->Dispatch(NewRunnableMethod("net::CacheEntry::InvokeCallbacksLock",
689 this,
690 &CacheEntry::InvokeCallbacksLock),
691 nsIEventTarget::DISPATCH_NORMAL);
692 if (NS_SUCCEEDED(rv)) {
693 LOG((" re-dispatching to target thread"));
694 return false;
698 Callback callback = mCallbacks[i];
699 mCallbacks.RemoveElementAt(i);
701 if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
702 // Callback didn't fire, put it back and go to another one in line.
703 // Only reason InvokeCallback returns false is that onCacheEntryCheck
704 // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other
705 // readers or potential writers would be unnecessarily kept from being
706 // invoked.
707 size_t pos = std::min(mCallbacks.Length(), static_cast<size_t>(i));
708 mCallbacks.InsertElementAt(pos, callback);
709 ++i;
713 if (recreatedHandle) {
714 // Must be released outside of the lock, enters InvokeCallback on the new entry
715 mozilla::MutexAutoUnlock unlock(mLock);
716 recreatedHandle = nullptr;
719 return true;
722 bool CacheEntry::InvokeCallback(Callback & aCallback)
724 LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]",
725 this, StateString(mState), aCallback.mCallback.get()));
727 mLock.AssertCurrentThreadOwns();
729 // When this entry is doomed we want to notify the callback any time
730 if (!mIsDoomed) {
731 // When we are here, the entry must be loaded from disk
732 MOZ_ASSERT(mState > LOADING);
734 if (mState == WRITING || mState == REVALIDATING) {
735 // Prevent invoking other callbacks since one of them is now writing
736 // or revalidating this entry. No consumers should get this entry
737 // until metadata are filled with values downloaded from the server
738 // or the entry revalidated and output stream has been opened.
739 LOG((" entry is being written/revalidated, callback bypassed"));
740 return false;
743 // mRecheckAfterWrite flag already set means the callback has already passed
744 // the onCacheEntryCheck call. Until the current write is not finished this
745 // callback will be bypassed.
746 if (!aCallback.mRecheckAfterWrite) {
748 if (!aCallback.mReadOnly) {
749 if (mState == EMPTY) {
750 // Advance to writing state, we expect to invoke the callback and let
751 // it fill content of this entry. Must set and check the state here
752 // to prevent more then one
753 mState = WRITING;
754 LOG((" advancing to WRITING state"));
757 if (!aCallback.mCallback) {
758 // We can be given no callback only in case of recreate, it is ok
759 // to advance to WRITING state since the caller of recreate is expected
760 // to write this entry now.
761 return true;
765 if (mState == READY) {
766 // Metadata present, validate the entry
767 uint32_t checkResult;
769 // mayhemer: TODO check and solve any potential races of concurent OnCacheEntryCheck
770 mozilla::MutexAutoUnlock unlock(mLock);
772 RefPtr<CacheEntryHandle> handle = NewHandle();
774 nsresult rv = aCallback.mCallback->OnCacheEntryCheck(
775 handle, nullptr, &checkResult);
776 LOG((" OnCacheEntryCheck: rv=0x%08" PRIx32 ", result=%" PRId32,
777 static_cast<uint32_t>(rv), static_cast<uint32_t>(checkResult)));
779 if (NS_FAILED(rv))
780 checkResult = ENTRY_NOT_WANTED;
783 aCallback.mRevalidating = checkResult == ENTRY_NEEDS_REVALIDATION;
785 switch (checkResult) {
786 case ENTRY_WANTED:
787 // Nothing more to do here, the consumer is responsible to handle
788 // the result of OnCacheEntryCheck it self.
789 // Proceed to callback...
790 break;
792 case RECHECK_AFTER_WRITE_FINISHED:
793 LOG((" consumer will check on the entry again after write is done"));
794 // The consumer wants the entry to complete first.
795 aCallback.mRecheckAfterWrite = true;
796 break;
798 case ENTRY_NEEDS_REVALIDATION:
799 LOG((" will be holding callbacks until entry is revalidated"));
800 // State is READY now and from that state entry cannot transit to any other
801 // state then REVALIDATING for which cocurrency is not an issue. Potentially
802 // no need to lock here.
803 mState = REVALIDATING;
804 break;
806 case ENTRY_NOT_WANTED:
807 LOG((" consumer not interested in the entry"));
808 // Do not give this entry to the consumer, it is not interested in us.
809 aCallback.mNotWanted = true;
810 break;
816 if (aCallback.mCallback) {
817 if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
818 // If we don't have data and the callback wants a complete entry,
819 // don't invoke now.
820 bool bypass = !mHasData;
821 if (!bypass && NS_SUCCEEDED(mFileStatus)) {
822 int64_t _unused;
823 bypass = !mFile->DataSize(&_unused);
826 if (bypass) {
827 LOG((" bypassing, entry data still being written"));
828 return false;
831 // Entry is complete now, do the check+avail call again
832 aCallback.mRecheckAfterWrite = false;
833 return InvokeCallback(aCallback);
836 mozilla::MutexAutoUnlock unlock(mLock);
837 InvokeAvailableCallback(aCallback);
840 return true;
843 void CacheEntry::InvokeAvailableCallback(Callback const & aCallback)
845 LOG(("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, n/w=%d]",
846 this, StateString(mState), aCallback.mCallback.get(), aCallback.mReadOnly, aCallback.mNotWanted));
848 nsresult rv;
850 uint32_t const state = mState;
852 // When we are here, the entry must be loaded from disk
853 MOZ_ASSERT(state > LOADING || mIsDoomed);
855 bool onAvailThread;
856 rv = aCallback.OnAvailThread(&onAvailThread);
857 if (NS_FAILED(rv)) {
858 LOG((" target thread dead?"));
859 return;
862 if (!onAvailThread) {
863 // Dispatch to the right thread
864 RefPtr<AvailableCallbackRunnable> event =
865 new AvailableCallbackRunnable(this, aCallback);
867 rv = aCallback.mTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
868 LOG((" redispatched, (rv = 0x%08" PRIx32 ")", static_cast<uint32_t>(rv)));
869 return;
872 if (mIsDoomed || aCallback.mNotWanted) {
873 LOG((" doomed or not wanted, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
874 aCallback.mCallback->OnCacheEntryAvailable(
875 nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
876 return;
879 if (state == READY) {
880 LOG((" ready/has-meta, notifying OCEA with entry and NS_OK"));
882 if (!aCallback.mSecret)
884 mozilla::MutexAutoLock lock(mLock);
885 BackgroundOp(Ops::FRECENCYUPDATE);
888 OnFetched(aCallback);
890 RefPtr<CacheEntryHandle> handle = NewHandle();
891 aCallback.mCallback->OnCacheEntryAvailable(
892 handle, false, nullptr, NS_OK);
893 return;
896 // R/O callbacks may do revalidation, let them fall through
897 if (aCallback.mReadOnly && !aCallback.mRevalidating) {
898 LOG((" r/o and not ready, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
899 aCallback.mCallback->OnCacheEntryAvailable(
900 nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
901 return;
904 // This is a new or potentially non-valid entry and needs to be fetched first.
905 // The CacheEntryHandle blocks other consumers until the channel
906 // either releases the entry or marks metadata as filled or whole entry valid,
907 // i.e. until MetaDataReady() or SetValid() on the entry is called respectively.
909 // Consumer will be responsible to fill or validate the entry metadata and data.
911 OnFetched(aCallback);
913 RefPtr<CacheEntryHandle> handle = NewWriteHandle();
914 rv = aCallback.mCallback->OnCacheEntryAvailable(
915 handle, state == WRITING, nullptr, NS_OK);
917 if (NS_FAILED(rv)) {
918 LOG((" writing/revalidating failed (0x%08" PRIx32 ")", static_cast<uint32_t>(rv)));
920 // Consumer given a new entry failed to take care of the entry.
921 OnHandleClosed(handle);
922 return;
925 LOG((" writing/revalidating"));
928 void CacheEntry::OnFetched(Callback const & aCallback)
930 if (NS_SUCCEEDED(mFileStatus) && !aCallback.mSecret) {
931 // Let the last-fetched and fetch-count properties be updated.
932 mFile->OnFetched();
936 CacheEntryHandle* CacheEntry::NewHandle()
938 return new CacheEntryHandle(this);
941 CacheEntryHandle* CacheEntry::NewWriteHandle()
943 mozilla::MutexAutoLock lock(mLock);
945 // Ignore the OPEN_SECRETLY flag on purpose here, which should actually be
946 // used only along with OPEN_READONLY, but there is no need to enforce that.
947 BackgroundOp(Ops::FRECENCYUPDATE);
949 return (mWriter = NewHandle());
952 void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle)
954 LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this, StateString(mState), aHandle));
956 mozilla::MutexAutoLock lock(mLock);
958 if (IsDoomed() && NS_SUCCEEDED(mFileStatus) &&
959 // Note: mHandlesCount is dropped before this method is called
960 (mHandlesCount == 0 ||
961 (mHandlesCount == 1 && mWriter && mWriter != aHandle))
963 // This entry is no longer referenced from outside and is doomed.
964 // We can do this also when there is just reference from the writer,
965 // no one else could ever reach the written data.
966 // Tell the file to kill the handle, i.e. bypass any I/O operations
967 // on it except removing the file.
968 mFile->Kill();
971 if (mWriter != aHandle) {
972 LOG((" not the writer"));
973 return;
976 if (mOutputStream) {
977 LOG((" abandoning phantom output stream"));
978 // No one took our internal output stream, so there are no data
979 // and output stream has to be open symultaneously with input stream
980 // on this entry again.
981 mHasData = false;
982 // This asynchronously ends up invoking callbacks on this entry
983 // through OnOutputClosed() call.
984 mOutputStream->Close();
985 mOutputStream = nullptr;
986 } else {
987 // We must always redispatch, otherwise there is a risk of stack
988 // overflow. This code can recurse deeply. It won't execute sooner
989 // than we release mLock.
990 BackgroundOp(Ops::CALLBACKS, true);
993 mWriter = nullptr;
995 if (mState == WRITING) {
996 LOG((" reverting to state EMPTY - write failed"));
997 mState = EMPTY;
999 else if (mState == REVALIDATING) {
1000 LOG((" reverting to state READY - reval failed"));
1001 mState = READY;
1004 if (mState == READY && !mHasData) {
1005 // We may get to this state when following steps happen:
1006 // 1. a new entry is given to a consumer
1007 // 2. the consumer calls MetaDataReady(), we transit to READY
1008 // 3. abandons the entry w/o opening the output stream, mHasData left false
1010 // In this case any following consumer will get a ready entry (with metadata)
1011 // but in state like the entry data write was still happening (was in progress)
1012 // and will indefinitely wait for the entry data or even the entry itself when
1013 // RECHECK_AFTER_WRITE is returned from onCacheEntryCheck.
1014 LOG((" we are in READY state, pretend we have data regardless it"
1015 " has actully been never touched"));
1016 mHasData = true;
1020 void CacheEntry::OnOutputClosed()
1022 // Called when the file's output stream is closed. Invoke any callbacks
1023 // waiting for complete entry.
1025 mozilla::MutexAutoLock lock(mLock);
1026 InvokeCallbacks();
1029 bool CacheEntry::IsReferenced() const
1031 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
1033 // Increasing this counter from 0 to non-null and this check both happen only
1034 // under the service lock.
1035 return mHandlesCount > 0;
1038 bool CacheEntry::IsFileDoomed()
1040 if (NS_SUCCEEDED(mFileStatus)) {
1041 return mFile->IsDoomed();
1044 return false;
1047 uint32_t CacheEntry::GetMetadataMemoryConsumption()
1049 NS_ENSURE_SUCCESS(mFileStatus, 0);
1051 uint32_t size;
1052 if (NS_FAILED(mFile->ElementsSize(&size)))
1053 return 0;
1055 return size;
1058 // nsICacheEntry
1060 nsresult CacheEntry::GetPersistent(bool *aPersistToDisk)
1062 // No need to sync when only reading.
1063 // When consumer needs to be consistent with state of the memory storage entries
1064 // table, then let it use GetUseDisk getter that must be called under the service lock.
1065 *aPersistToDisk = mUseDisk;
1066 return NS_OK;
1069 nsresult CacheEntry::GetKey(nsACString & aKey)
1071 aKey.Assign(mURI);
1072 return NS_OK;
1075 nsresult CacheEntry::GetCacheEntryId(uint64_t *aCacheEntryId)
1077 *aCacheEntryId = mCacheEntryId;
1078 return NS_OK;
1081 nsresult CacheEntry::GetFetchCount(int32_t *aFetchCount)
1083 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1085 return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount));
1088 nsresult CacheEntry::GetLastFetched(uint32_t *aLastFetched)
1090 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1092 return mFile->GetLastFetched(aLastFetched);
1095 nsresult CacheEntry::GetLastModified(uint32_t *aLastModified)
1097 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1099 return mFile->GetLastModified(aLastModified);
1102 nsresult CacheEntry::GetExpirationTime(uint32_t *aExpirationTime)
1104 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1106 return mFile->GetExpirationTime(aExpirationTime);
1109 nsresult CacheEntry::GetOnStartTime(uint64_t *aTime)
1111 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1112 return mFile->GetOnStartTime(aTime);
1115 nsresult CacheEntry::GetOnStopTime(uint64_t *aTime)
1117 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1118 return mFile->GetOnStopTime(aTime);
1121 nsresult CacheEntry::SetNetworkTimes(uint64_t aOnStartTime, uint64_t aOnStopTime)
1123 if (NS_SUCCEEDED(mFileStatus)) {
1124 return mFile->SetNetworkTimes(aOnStartTime, aOnStopTime);
1126 return NS_ERROR_NOT_AVAILABLE;
1129 nsresult CacheEntry::GetIsForcedValid(bool *aIsForcedValid)
1131 NS_ENSURE_ARG(aIsForcedValid);
1133 MOZ_ASSERT(mState > LOADING);
1135 if (mPinned) {
1136 *aIsForcedValid = true;
1137 return NS_OK;
1140 nsAutoCString key;
1141 nsresult rv = HashingKey(key);
1142 if (NS_FAILED(rv)) {
1143 return rv;
1146 *aIsForcedValid = CacheStorageService::Self()->IsForcedValidEntry(mStorageID, key);
1147 LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this, *aIsForcedValid));
1149 return NS_OK;
1152 nsresult CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture)
1154 LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this, aSecondsToTheFuture));
1156 nsAutoCString key;
1157 nsresult rv = HashingKey(key);
1158 if (NS_FAILED(rv)) {
1159 return rv;
1162 CacheStorageService::Self()->ForceEntryValidFor(mStorageID, key, aSecondsToTheFuture);
1164 return NS_OK;
1167 nsresult CacheEntry::SetExpirationTime(uint32_t aExpirationTime)
1169 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1171 nsresult rv = mFile->SetExpirationTime(aExpirationTime);
1172 NS_ENSURE_SUCCESS(rv, rv);
1174 // Aligned assignment, thus atomic.
1175 mSortingExpirationTime = aExpirationTime;
1176 return NS_OK;
1179 nsresult CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval)
1181 LOG(("CacheEntry::OpenInputStream [this=%p]", this));
1182 return OpenInputStreamInternal(offset, nullptr, _retval);
1185 nsresult CacheEntry::OpenAlternativeInputStream(const nsACString & type, nsIInputStream * *_retval)
1187 LOG(("CacheEntry::OpenAlternativeInputStream [this=%p, type=%s]", this,
1188 PromiseFlatCString(type).get()));
1189 return OpenInputStreamInternal(0, PromiseFlatCString(type).get(), _retval);
1192 nsresult CacheEntry::OpenInputStreamInternal(int64_t offset, const char *aAltDataType, nsIInputStream * *_retval)
1194 LOG(("CacheEntry::OpenInputStreamInternal [this=%p]", this));
1196 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1198 nsresult rv;
1200 RefPtr<CacheEntryHandle> selfHandle = NewHandle();
1202 nsCOMPtr<nsIInputStream> stream;
1203 if (aAltDataType) {
1204 rv = mFile->OpenAlternativeInputStream(selfHandle, aAltDataType,
1205 getter_AddRefs(stream));
1206 if (NS_FAILED(rv)) {
1207 // Failure of this method may be legal when the alternative data requested
1208 // is not avaialble or of a different type. Console error logs are ensured
1209 // by CacheFile::OpenAlternativeInputStream.
1210 return rv;
1212 } else {
1213 rv = mFile->OpenInputStream(selfHandle, getter_AddRefs(stream));
1214 NS_ENSURE_SUCCESS(rv, rv);
1217 nsCOMPtr<nsISeekableStream> seekable =
1218 do_QueryInterface(stream, &rv);
1219 NS_ENSURE_SUCCESS(rv, rv);
1221 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1222 NS_ENSURE_SUCCESS(rv, rv);
1224 mozilla::MutexAutoLock lock(mLock);
1226 if (!mHasData) {
1227 // So far output stream on this new entry not opened, do it now.
1228 LOG((" creating phantom output stream"));
1229 rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
1230 NS_ENSURE_SUCCESS(rv, rv);
1233 stream.forget(_retval);
1234 return NS_OK;
1237 nsresult CacheEntry::OpenOutputStream(int64_t offset, int64_t predictedSize, nsIOutputStream * *_retval)
1239 LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
1241 nsresult rv;
1243 mozilla::MutexAutoLock lock(mLock);
1245 MOZ_ASSERT(mState > EMPTY);
1247 if (mFile->EntryWouldExceedLimit(0, predictedSize, false)) {
1248 LOG((" entry would exceed size limit"));
1249 return NS_ERROR_FILE_TOO_BIG;
1252 if (mOutputStream && !mIsDoomed) {
1253 LOG((" giving phantom output stream"));
1254 mOutputStream.forget(_retval);
1256 else {
1257 rv = OpenOutputStreamInternal(offset, _retval);
1258 if (NS_FAILED(rv)) return rv;
1261 // Entry considered ready when writer opens output stream.
1262 if (mState < READY)
1263 mState = READY;
1265 // Invoke any pending readers now.
1266 InvokeCallbacks();
1268 return NS_OK;
1271 nsresult CacheEntry::OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval)
1273 LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
1274 PromiseFlatCString(type).get()));
1276 nsresult rv;
1278 mozilla::MutexAutoLock lock(mLock);
1280 if (!mHasData || mState < READY || mOutputStream || mIsDoomed) {
1281 LOG((" entry not in state to write alt-data"));
1282 return NS_ERROR_NOT_AVAILABLE;
1285 if (mFile->EntryWouldExceedLimit(0, predictedSize, true)) {
1286 LOG((" entry would exceed size limit"));
1287 return NS_ERROR_FILE_TOO_BIG;
1290 nsCOMPtr<nsIOutputStream> stream;
1291 rv = mFile->OpenAlternativeOutputStream(nullptr,
1292 PromiseFlatCString(type).get(),
1293 getter_AddRefs(stream));
1294 NS_ENSURE_SUCCESS(rv, rv);
1296 stream.swap(*_retval);
1297 return NS_OK;
1300 nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval)
1302 LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
1304 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1306 mLock.AssertCurrentThreadOwns();
1308 if (mIsDoomed) {
1309 LOG((" doomed..."));
1310 return NS_ERROR_NOT_AVAILABLE;
1313 MOZ_ASSERT(mState > LOADING);
1315 nsresult rv;
1317 // No need to sync on mUseDisk here, we don't need to be consistent
1318 // with content of the memory storage entries hash table.
1319 if (!mUseDisk) {
1320 rv = mFile->SetMemoryOnly();
1321 NS_ENSURE_SUCCESS(rv, rv);
1324 RefPtr<CacheOutputCloseListener> listener =
1325 new CacheOutputCloseListener(this);
1327 nsCOMPtr<nsIOutputStream> stream;
1328 rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
1329 NS_ENSURE_SUCCESS(rv, rv);
1331 nsCOMPtr<nsISeekableStream> seekable =
1332 do_QueryInterface(stream, &rv);
1333 NS_ENSURE_SUCCESS(rv, rv);
1335 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1336 NS_ENSURE_SUCCESS(rv, rv);
1338 // Prevent opening output stream again.
1339 mHasData = true;
1341 stream.swap(*_retval);
1342 return NS_OK;
1345 nsresult CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo)
1348 mozilla::MutexAutoLock lock(mLock);
1349 if (mSecurityInfoLoaded) {
1350 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1351 return NS_OK;
1355 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1357 nsCString info;
1358 nsCOMPtr<nsISupports> secInfo;
1359 nsresult rv;
1361 rv = mFile->GetElement("security-info", getter_Copies(info));
1362 NS_ENSURE_SUCCESS(rv, rv);
1364 if (!info.IsVoid()) {
1365 rv = NS_DeserializeObject(info, getter_AddRefs(secInfo));
1366 NS_ENSURE_SUCCESS(rv, rv);
1370 mozilla::MutexAutoLock lock(mLock);
1372 mSecurityInfo.swap(secInfo);
1373 mSecurityInfoLoaded = true;
1375 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1378 return NS_OK;
1380 nsresult CacheEntry::SetSecurityInfo(nsISupports *aSecurityInfo)
1382 nsresult rv;
1384 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1387 mozilla::MutexAutoLock lock(mLock);
1389 mSecurityInfo = aSecurityInfo;
1390 mSecurityInfoLoaded = true;
1393 nsCOMPtr<nsISerializable> serializable =
1394 do_QueryInterface(aSecurityInfo);
1395 if (aSecurityInfo && !serializable)
1396 return NS_ERROR_UNEXPECTED;
1398 nsCString info;
1399 if (serializable) {
1400 rv = NS_SerializeToString(serializable, info);
1401 NS_ENSURE_SUCCESS(rv, rv);
1404 rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
1405 NS_ENSURE_SUCCESS(rv, rv);
1407 return NS_OK;
1410 nsresult CacheEntry::GetStorageDataSize(uint32_t *aStorageDataSize)
1412 NS_ENSURE_ARG(aStorageDataSize);
1414 int64_t dataSize;
1415 nsresult rv = GetDataSize(&dataSize);
1416 if (NS_FAILED(rv))
1417 return rv;
1419 *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
1421 return NS_OK;
1424 nsresult CacheEntry::AsyncDoom(nsICacheEntryDoomCallback *aCallback)
1426 LOG(("CacheEntry::AsyncDoom [this=%p]", this));
1429 mozilla::MutexAutoLock lock(mLock);
1431 if (mIsDoomed || mDoomCallback)
1432 return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
1434 RemoveForcedValidity();
1436 mIsDoomed = true;
1437 mDoomCallback = aCallback;
1440 // This immediately removes the entry from the master hashtable and also
1441 // immediately dooms the file. This way we make sure that any consumer
1442 // after this point asking for the same entry won't get
1443 // a) this entry
1444 // b) a new entry with the same file
1445 PurgeAndDoom();
1447 return NS_OK;
1450 nsresult CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval)
1452 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1454 return mFile->GetElement(aKey, aRetval);
1457 nsresult CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue)
1459 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1461 return mFile->SetElement(aKey, aValue);
1464 nsresult CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor)
1466 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1468 return mFile->VisitMetaData(aVisitor);
1471 nsresult CacheEntry::MetaDataReady()
1473 mozilla::MutexAutoLock lock(mLock);
1475 LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, StateString(mState)));
1477 MOZ_ASSERT(mState > EMPTY);
1479 if (mState == WRITING)
1480 mState = READY;
1482 InvokeCallbacks();
1484 return NS_OK;
1487 nsresult CacheEntry::SetValid()
1489 LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState)));
1491 nsCOMPtr<nsIOutputStream> outputStream;
1494 mozilla::MutexAutoLock lock(mLock);
1496 MOZ_ASSERT(mState > EMPTY);
1498 mState = READY;
1499 mHasData = true;
1501 InvokeCallbacks();
1503 outputStream.swap(mOutputStream);
1506 if (outputStream) {
1507 LOG((" abandoning phantom output stream"));
1508 outputStream->Close();
1511 return NS_OK;
1514 nsresult CacheEntry::Recreate(bool aMemoryOnly,
1515 nsICacheEntry **_retval)
1517 LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
1519 mozilla::MutexAutoLock lock(mLock);
1521 RefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
1522 if (handle) {
1523 handle.forget(_retval);
1524 return NS_OK;
1527 BackgroundOp(Ops::CALLBACKS, true);
1528 return NS_ERROR_NOT_AVAILABLE;
1531 nsresult CacheEntry::GetDataSize(int64_t *aDataSize)
1533 LOG(("CacheEntry::GetDataSize [this=%p]", this));
1534 *aDataSize = 0;
1537 mozilla::MutexAutoLock lock(mLock);
1539 if (!mHasData) {
1540 LOG((" write in progress (no data)"));
1541 return NS_ERROR_IN_PROGRESS;
1545 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1547 // mayhemer: TODO Problem with compression?
1548 if (!mFile->DataSize(aDataSize)) {
1549 LOG((" write in progress (stream active)"));
1550 return NS_ERROR_IN_PROGRESS;
1553 LOG((" size=%" PRId64, *aDataSize));
1554 return NS_OK;
1558 nsresult CacheEntry::GetAltDataSize(int64_t *aDataSize)
1560 LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
1561 if (NS_FAILED(mFileStatus)) {
1562 return mFileStatus;
1564 return mFile->GetAltDataSize(aDataSize);
1568 nsresult CacheEntry::MarkValid()
1570 // NOT IMPLEMENTED ACTUALLY
1571 return NS_OK;
1574 nsresult CacheEntry::MaybeMarkValid()
1576 // NOT IMPLEMENTED ACTUALLY
1577 return NS_OK;
1580 nsresult CacheEntry::HasWriteAccess(bool aWriteAllowed, bool *aWriteAccess)
1582 *aWriteAccess = aWriteAllowed;
1583 return NS_OK;
1586 nsresult CacheEntry::Close()
1588 // NOT IMPLEMENTED ACTUALLY
1589 return NS_OK;
1592 nsresult CacheEntry::GetDiskStorageSizeInKB(uint32_t *aDiskStorageSize)
1594 if (NS_FAILED(mFileStatus)) {
1595 return NS_ERROR_NOT_AVAILABLE;
1598 return mFile->GetDiskStorageSizeInKB(aDiskStorageSize);
1601 nsresult CacheEntry::GetLoadContextInfo(nsILoadContextInfo** aInfo)
1603 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(mStorageID);
1604 if (!info) {
1605 return NS_ERROR_FAILURE;
1608 info.forget(aInfo);
1610 return NS_OK;
1613 // nsIRunnable
1615 NS_IMETHODIMP CacheEntry::Run()
1617 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1619 mozilla::MutexAutoLock lock(mLock);
1621 BackgroundOp(mBackgroundOperations.Grab());
1622 return NS_OK;
1625 // Management methods
1627 double CacheEntry::GetFrecency() const
1629 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1630 return mFrecency;
1633 uint32_t CacheEntry::GetExpirationTime() const
1635 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1636 return mSortingExpirationTime;
1639 bool CacheEntry::IsRegistered() const
1641 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1642 return mRegistration == REGISTERED;
1645 bool CacheEntry::CanRegister() const
1647 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1648 return mRegistration == NEVERREGISTERED;
1651 void CacheEntry::SetRegistered(bool aRegistered)
1653 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1655 if (aRegistered) {
1656 MOZ_ASSERT(mRegistration == NEVERREGISTERED);
1657 mRegistration = REGISTERED;
1659 else {
1660 MOZ_ASSERT(mRegistration == REGISTERED);
1661 mRegistration = DEREGISTERED;
1665 bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned)
1667 LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this));
1669 mozilla::MutexAutoLock lock(mLock);
1671 if (mPinningKnown) {
1672 LOG((" pinned=%d, caller=%d", mPinned, aPinned));
1673 // Bypass when the pin status of this entry doesn't match the pin status
1674 // caller wants to remove
1675 return mPinned != aPinned;
1678 LOG((" pinning unknown, caller=%d", aPinned));
1679 // Oterwise, remember to doom after the status is determined for any
1680 // callback opening the entry after this point...
1681 Callback c(this, aPinned);
1682 RememberCallback(c);
1683 // ...and always bypass
1684 return true;
1687 bool CacheEntry::Purge(uint32_t aWhat)
1689 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
1691 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1693 switch (aWhat) {
1694 case PURGE_DATA_ONLY_DISK_BACKED:
1695 case PURGE_WHOLE_ONLY_DISK_BACKED:
1696 // This is an in-memory only entry, don't purge it
1697 if (!mUseDisk) {
1698 LOG((" not using disk"));
1699 return false;
1703 if (mState == WRITING || mState == LOADING || mFrecency == 0) {
1704 // In-progress (write or load) entries should (at least for consistency and from
1705 // the logical point of view) stay in memory.
1706 // Zero-frecency entries are those which have never been given to any consumer, those
1707 // are actually very fresh and should not go just because frecency had not been set
1708 // so far.
1709 LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency));
1710 return false;
1713 if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) {
1714 // The file is used when there are open streams or chunks/metadata still waiting for
1715 // write. In this case, this entry cannot be purged, otherwise reopenned entry
1716 // would may not even find the data on disk - CacheFile is not shared and cannot be
1717 // left orphan when its job is not done, hence keep the whole entry.
1718 LOG((" file still under use"));
1719 return false;
1722 switch (aWhat) {
1723 case PURGE_WHOLE_ONLY_DISK_BACKED:
1724 case PURGE_WHOLE:
1726 if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1727 LOG((" not purging, still referenced"));
1728 return false;
1731 CacheStorageService::Self()->UnregisterEntry(this);
1733 // Entry removed it self from control arrays, return true
1734 return true;
1737 case PURGE_DATA_ONLY_DISK_BACKED:
1739 NS_ENSURE_SUCCESS(mFileStatus, false);
1741 mFile->ThrowMemoryCachedData();
1743 // Entry has been left in control arrays, return false (not purged)
1744 return false;
1748 LOG((" ?"));
1749 return false;
1752 void CacheEntry::PurgeAndDoom()
1754 LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
1756 CacheStorageService::Self()->RemoveEntry(this);
1757 DoomAlreadyRemoved();
1760 void CacheEntry::DoomAlreadyRemoved()
1762 LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
1764 mozilla::MutexAutoLock lock(mLock);
1766 RemoveForcedValidity();
1768 mIsDoomed = true;
1770 // Pretend pinning is know. This entry is now doomed for good, so don't
1771 // bother with defering doom because of unknown pinning state any more.
1772 mPinningKnown = true;
1774 // This schedules dooming of the file, dooming is ensured to happen
1775 // sooner than demand to open the same file made after this point
1776 // so that we don't get this file for any newer opened entry(s).
1777 DoomFile();
1779 // Must force post here since may be indirectly called from
1780 // InvokeCallbacks of this entry and we don't want reentrancy here.
1781 BackgroundOp(Ops::CALLBACKS, true);
1782 // Process immediately when on the management thread.
1783 BackgroundOp(Ops::UNREGISTER);
1786 void CacheEntry::DoomFile()
1788 nsresult rv = NS_ERROR_NOT_AVAILABLE;
1790 if (NS_SUCCEEDED(mFileStatus)) {
1791 if (mHandlesCount == 0 ||
1792 (mHandlesCount == 1 && mWriter)) {
1793 // We kill the file also when there is just reference from the writer,
1794 // no one else could ever reach the written data. Obvisouly also
1795 // when there is no reference at all (should we ever end up here
1796 // in that case.)
1797 // Tell the file to kill the handle, i.e. bypass any I/O operations
1798 // on it except removing the file.
1799 mFile->Kill();
1802 // Always calls the callback asynchronously.
1803 rv = mFile->Doom(mDoomCallback ? this : nullptr);
1804 if (NS_SUCCEEDED(rv)) {
1805 LOG((" file doomed"));
1806 return;
1809 if (NS_ERROR_FILE_NOT_FOUND == rv) {
1810 // File is set to be just memory-only, notify the callbacks
1811 // and pretend dooming has succeeded. From point of view of
1812 // the entry it actually did - the data is gone and cannot be
1813 // reused.
1814 rv = NS_OK;
1818 // Always posts to the main thread.
1819 OnFileDoomed(rv);
1822 void CacheEntry::RemoveForcedValidity()
1824 mLock.AssertCurrentThreadOwns();
1826 nsresult rv;
1828 if (mIsDoomed) {
1829 return;
1832 nsAutoCString entryKey;
1833 rv = HashingKey(entryKey);
1834 if (NS_WARN_IF(NS_FAILED(rv))) {
1835 return;
1838 CacheStorageService::Self()->RemoveEntryForceValid(mStorageID, entryKey);
1841 void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync)
1843 mLock.AssertCurrentThreadOwns();
1845 if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
1846 if (mBackgroundOperations.Set(aOperations))
1847 CacheStorageService::Self()->Dispatch(this);
1849 LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
1850 return;
1854 mozilla::MutexAutoUnlock unlock(mLock);
1856 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1858 if (aOperations & Ops::FRECENCYUPDATE) {
1859 ++mUseCount;
1861 #ifndef M_LN2
1862 #define M_LN2 0.69314718055994530942
1863 #endif
1865 // Half-life is dynamic, in seconds.
1866 static double half_life = CacheObserver::HalfLifeSeconds();
1867 // Must convert from seconds to milliseconds since PR_Now() gives usecs.
1868 static double const decay = (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
1870 double now_decay = static_cast<double>(PR_Now()) * decay;
1872 if (mFrecency == 0) {
1873 mFrecency = now_decay;
1875 else {
1876 // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1) but
1877 // more precise.
1878 mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
1880 LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this, mFrecency));
1882 // Because CacheFile::Set*() are not thread-safe to use (uses WeakReference that
1883 // is not thread-safe) we must post to the main thread...
1884 NS_DispatchToMainThread(
1885 NewRunnableMethod<double>("net::CacheEntry::StoreFrecency",
1886 this,
1887 &CacheEntry::StoreFrecency,
1888 mFrecency));
1891 if (aOperations & Ops::REGISTER) {
1892 LOG(("CacheEntry REGISTER [this=%p]", this));
1894 CacheStorageService::Self()->RegisterEntry(this);
1897 if (aOperations & Ops::UNREGISTER) {
1898 LOG(("CacheEntry UNREGISTER [this=%p]", this));
1900 CacheStorageService::Self()->UnregisterEntry(this);
1902 } // unlock
1904 if (aOperations & Ops::CALLBACKS) {
1905 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
1907 InvokeCallbacks();
1911 void CacheEntry::StoreFrecency(double aFrecency)
1913 MOZ_ASSERT(NS_IsMainThread());
1915 if (NS_SUCCEEDED(mFileStatus)) {
1916 mFile->SetFrecency(FRECENCY2INT(aFrecency));
1920 // CacheOutputCloseListener
1922 CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
1923 : Runnable("net::CacheOutputCloseListener")
1924 , mEntry(aEntry)
1928 void CacheOutputCloseListener::OnOutputClosed()
1930 // We need this class and to redispatch since this callback is invoked
1931 // under the file's lock and to do the job we need to enter the entry's
1932 // lock too. That would lead to potential deadlocks.
1933 NS_DispatchToCurrentThread(this);
1936 NS_IMETHODIMP CacheOutputCloseListener::Run()
1938 mEntry->OnOutputClosed();
1939 return NS_OK;
1942 // Memory reporting
1944 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1946 size_t n = 0;
1948 n += mCallbacks.ShallowSizeOfExcludingThis(mallocSizeOf);
1949 if (mFile) {
1950 n += mFile->SizeOfIncludingThis(mallocSizeOf);
1953 n += mURI.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1954 n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1955 n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1957 // mDoomCallback is an arbitrary class that is probably reported elsewhere.
1958 // mOutputStream is reported in mFile.
1959 // mWriter is one of many handles we create, but (intentionally) not keep
1960 // any reference to, so those unfortunately cannot be reported. Handles are
1961 // small, though.
1962 // mSecurityInfo doesn't impl nsISizeOf.
1964 return n;
1967 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1969 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1972 } // namespace net
1973 } // namespace mozilla