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/. */
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"
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"
25 #include "nsProxyRelease.h"
26 #include "nsSerializationHelper.h"
27 #include "nsThreadUtils.h"
28 #include "mozilla/Telemetry.h"
29 #include "mozilla/IntegerPrintfMacros.h"
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
)
49 CacheEntryHandle::CacheEntryHandle(CacheEntry
* aEntry
)
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();
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);
76 LOG((" already dropped"));
77 return NS_ERROR_UNEXPECTED
;
80 CacheEntryHandle::~CacheEntryHandle()
82 mEntry
->ReleaseHandleRef();
85 LOG(("CacheEntryHandle::~CacheEntryHandle %p", this));
88 // CacheEntry::Callback
90 CacheEntry::Callback::Callback(CacheEntry
* aEntry
,
91 nsICacheEntryOpenCallback
*aCallback
,
92 bool aReadOnly
, bool aCheckOnAnyThread
,
95 , mCallback(aCallback
)
96 , mTarget(GetCurrentThreadEventTarget())
97 , mReadOnly(aReadOnly
)
98 , mRevalidating(false)
99 , mCheckOnAnyThread(aCheckOnAnyThread
)
100 , mRecheckAfterWrite(false)
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
)
117 , mRevalidating(false)
118 , mCheckOnAnyThread(true)
119 , mRecheckAfterWrite(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
)
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();
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
));
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;
198 nsresult
CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread
) const
200 return mTarget
->IsOnCurrentThread(aOnAvailThread
);
205 NS_IMPL_ISUPPORTS(CacheEntry
,
210 uint64_t CacheEntry::GetNextId()
212 static Atomic
<uint64_t, Relaxed
> id(0);
216 CacheEntry::CacheEntry(const nsACString
& aStorageID
,
217 const nsACString
& aURI
,
218 const nsACString
& aEnhanceID
,
223 , mSortingExpirationTime(uint32_t(-1))
224 , mLock("CacheEntry")
225 , mFileStatus(NS_ERROR_NOT_INITIALIZED
)
227 , mEnhanceID(aEnhanceID
)
228 , mStorageID(aStorageID
)
230 , mSkipSizeCheck(aSkipSizeCheck
)
232 , mSecurityInfoLoaded(false)
233 , mPreventCallbacks(false)
236 , mPinningKnown(false)
238 , mRegistration(NEVERREGISTERED
)
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
)
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";
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
);
281 nsresult
CacheEntry::HashingKey(const nsACString
& aStorageID
,
282 const nsACString
& aEnhanceID
,
287 nsresult rv
= aURI
->GetAsciiSpec(spec
);
288 NS_ENSURE_SUCCESS(rv
, rv
);
290 return HashingKey(aStorageID
, aEnhanceID
, spec
, aResult
);
294 nsresult
CacheEntry::HashingKey(const nsACString
& aStorageID
,
295 const nsACString
& aEnhanceID
,
296 const nsACString
& aURISpec
,
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
312 aResult
.Append(aURISpec
);
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
)) {
352 RememberCallback(aCallback
);
354 // Load() opens the lock
355 if (Load(aTruncate
, aPriority
)) {
356 // Loading is in progress...
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"));
376 if (mState
== LOADING
) {
377 LOG((" already loading"));
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
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
))) {
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"));
413 case CacheIndex::EXISTS
:
414 case CacheIndex::DO_NOT_KNOW
:
416 LOG((" entry open as memory-only, but there is a file, status=%d, dooming it", status
));
417 CacheFileIOManager::DoomFileByKey(fileKey
, nullptr);
424 mFile
= new CacheFile();
426 BackgroundOp(Ops::REGISTER
);
428 bool directLoad
= aTruncate
|| !mUseDisk
;
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;
435 mLoadStart
= TimeStamp::Now();
439 mozilla::MutexAutoUnlock
unlock(mLock
);
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
,
454 directLoad
? nullptr : this);
465 // Just fake the load has already been done as "new".
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
)) {
482 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
483 CacheFileUtils::DetailedCacheHitTelemetry::MISS
, mLoadStart
);
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
495 mozilla::MutexAutoLock
lock(mLock
);
497 MOZ_ASSERT(mState
== LOADING
);
499 mState
= (aIsNew
|| NS_FAILED(aResult
))
503 mFileStatus
= aResult
;
505 mPinned
= mFile
->IsPinned();;
506 mPinningKnown
= true;
507 LOG((" pinning=%d", mPinned
));
509 if (mState
== READY
) {
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
);
524 NS_IMETHODIMP
CacheEntry::OnFileDoomed(nsresult aResult
)
527 RefPtr
<DoomCallbackRunnable
> event
=
528 new DoomCallbackRunnable(this, aResult
);
529 NS_DispatchToMainThread(event
);
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
;
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.)
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
,
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
);
572 LOG((" exchanged of entry %p failed, rv=0x%08" PRIx32
,
573 this, static_cast<uint32_t>(rv
)));
578 mPreventCallbacks
= false;
583 newEntry
->TransferCallbacks(*this);
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]",
603 if (!mCallbacks
.Length())
604 mCallbacks
.SwapElements(aFromEntry
.mCallbacks
);
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
);
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
;
654 while (i
< mCallbacks
.Length()) {
655 if (mPreventCallbacks
) {
656 LOG((" callbacks prevented!"));
660 if (!mIsDoomed
&& (mState
== WRITING
|| mState
== REVALIDATING
)) {
661 LOG((" entry is being written/revalidated"));
666 if (mCallbacks
[i
].DeferDoom(&recreate
)) {
667 mCallbacks
.RemoveElementAt(i
);
672 LOG((" defer doom marker callback hit positive, recreating"));
673 recreatedHandle
= ReopenTruncated(!mUseDisk
, nullptr);
677 if (mCallbacks
[i
].mReadOnly
!= aReadOnly
) {
678 // Callback is not r/w or r/o, go to another one in line
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",
690 &CacheEntry::InvokeCallbacksLock
),
691 nsIEventTarget::DISPATCH_NORMAL
);
692 if (NS_SUCCEEDED(rv
)) {
693 LOG((" re-dispatching to target thread"));
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
707 size_t pos
= std::min(mCallbacks
.Length(), static_cast<size_t>(i
));
708 mCallbacks
.InsertElementAt(pos
, callback
);
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;
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
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"));
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
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.
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
)));
780 checkResult
= ENTRY_NOT_WANTED
;
783 aCallback
.mRevalidating
= checkResult
== ENTRY_NEEDS_REVALIDATION
;
785 switch (checkResult
) {
787 // Nothing more to do here, the consumer is responsible to handle
788 // the result of OnCacheEntryCheck it self.
789 // Proceed to callback...
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;
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
;
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;
816 if (aCallback
.mCallback
) {
817 if (!mIsDoomed
&& aCallback
.mRecheckAfterWrite
) {
818 // If we don't have data and the callback wants a complete entry,
820 bool bypass
= !mHasData
;
821 if (!bypass
&& NS_SUCCEEDED(mFileStatus
)) {
823 bypass
= !mFile
->DataSize(&_unused
);
827 LOG((" bypassing, entry data still being written"));
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
);
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
));
850 uint32_t const state
= mState
;
852 // When we are here, the entry must be loaded from disk
853 MOZ_ASSERT(state
> LOADING
|| mIsDoomed
);
856 rv
= aCallback
.OnAvailThread(&onAvailThread
);
858 LOG((" target thread dead?"));
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
)));
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
);
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
);
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
);
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
);
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
);
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.
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.
971 if (mWriter
!= aHandle
) {
972 LOG((" not the writer"));
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.
982 // This asynchronously ends up invoking callbacks on this entry
983 // through OnOutputClosed() call.
984 mOutputStream
->Close();
985 mOutputStream
= nullptr;
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);
995 if (mState
== WRITING
) {
996 LOG((" reverting to state EMPTY - write failed"));
999 else if (mState
== REVALIDATING
) {
1000 LOG((" reverting to state READY - reval failed"));
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"));
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
);
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();
1047 uint32_t CacheEntry::GetMetadataMemoryConsumption()
1049 NS_ENSURE_SUCCESS(mFileStatus
, 0);
1052 if (NS_FAILED(mFile
->ElementsSize(&size
)))
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
;
1069 nsresult
CacheEntry::GetKey(nsACString
& aKey
)
1075 nsresult
CacheEntry::GetCacheEntryId(uint64_t *aCacheEntryId
)
1077 *aCacheEntryId
= mCacheEntryId
;
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
);
1136 *aIsForcedValid
= true;
1141 nsresult rv
= HashingKey(key
);
1142 if (NS_FAILED(rv
)) {
1146 *aIsForcedValid
= CacheStorageService::Self()->IsForcedValidEntry(mStorageID
, key
);
1147 LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this, *aIsForcedValid
));
1152 nsresult
CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture
)
1154 LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this, aSecondsToTheFuture
));
1157 nsresult rv
= HashingKey(key
);
1158 if (NS_FAILED(rv
)) {
1162 CacheStorageService::Self()->ForceEntryValidFor(mStorageID
, key
, aSecondsToTheFuture
);
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
;
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
);
1200 RefPtr
<CacheEntryHandle
> selfHandle
= NewHandle();
1202 nsCOMPtr
<nsIInputStream
> stream
;
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.
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
);
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
);
1237 nsresult
CacheEntry::OpenOutputStream(int64_t offset
, int64_t predictedSize
, nsIOutputStream
* *_retval
)
1239 LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
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
);
1257 rv
= OpenOutputStreamInternal(offset
, _retval
);
1258 if (NS_FAILED(rv
)) return rv
;
1261 // Entry considered ready when writer opens output stream.
1265 // Invoke any pending readers now.
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()));
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
);
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();
1309 LOG((" doomed..."));
1310 return NS_ERROR_NOT_AVAILABLE
;
1313 MOZ_ASSERT(mState
> LOADING
);
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.
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.
1341 stream
.swap(*_retval
);
1345 nsresult
CacheEntry::GetSecurityInfo(nsISupports
* *aSecurityInfo
)
1348 mozilla::MutexAutoLock
lock(mLock
);
1349 if (mSecurityInfoLoaded
) {
1350 NS_IF_ADDREF(*aSecurityInfo
= mSecurityInfo
);
1355 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1358 nsCOMPtr
<nsISupports
> secInfo
;
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
);
1380 nsresult
CacheEntry::SetSecurityInfo(nsISupports
*aSecurityInfo
)
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
;
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
);
1410 nsresult
CacheEntry::GetStorageDataSize(uint32_t *aStorageDataSize
)
1412 NS_ENSURE_ARG(aStorageDataSize
);
1415 nsresult rv
= GetDataSize(&dataSize
);
1419 *aStorageDataSize
= (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize
);
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();
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
1444 // b) a new entry with the same file
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
)
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
);
1503 outputStream
.swap(mOutputStream
);
1507 LOG((" abandoning phantom output stream"));
1508 outputStream
->Close();
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);
1523 handle
.forget(_retval
);
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));
1537 mozilla::MutexAutoLock
lock(mLock
);
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
));
1558 nsresult
CacheEntry::GetAltDataSize(int64_t *aDataSize
)
1560 LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
1561 if (NS_FAILED(mFileStatus
)) {
1564 return mFile
->GetAltDataSize(aDataSize
);
1568 nsresult
CacheEntry::MarkValid()
1570 // NOT IMPLEMENTED ACTUALLY
1574 nsresult
CacheEntry::MaybeMarkValid()
1576 // NOT IMPLEMENTED ACTUALLY
1580 nsresult
CacheEntry::HasWriteAccess(bool aWriteAllowed
, bool *aWriteAccess
)
1582 *aWriteAccess
= aWriteAllowed
;
1586 nsresult
CacheEntry::Close()
1588 // NOT IMPLEMENTED ACTUALLY
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
);
1605 return NS_ERROR_FAILURE
;
1615 NS_IMETHODIMP
CacheEntry::Run()
1617 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1619 mozilla::MutexAutoLock
lock(mLock
);
1621 BackgroundOp(mBackgroundOperations
.Grab());
1625 // Management methods
1627 double CacheEntry::GetFrecency() const
1629 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
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());
1656 MOZ_ASSERT(mRegistration
== NEVERREGISTERED
);
1657 mRegistration
= REGISTERED
;
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
1687 bool CacheEntry::Purge(uint32_t aWhat
)
1689 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat
));
1691 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
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
1698 LOG((" not using disk"));
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
1709 LOG((" state=%s, frecency=%1.10f", StateString(mState
), mFrecency
));
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"));
1723 case PURGE_WHOLE_ONLY_DISK_BACKED
:
1726 if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1727 LOG((" not purging, still referenced"));
1731 CacheStorageService::Self()->UnregisterEntry(this);
1733 // Entry removed it self from control arrays, 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)
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();
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).
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
1797 // Tell the file to kill the handle, i.e. bypass any I/O operations
1798 // on it except removing the file.
1802 // Always calls the callback asynchronously.
1803 rv
= mFile
->Doom(mDoomCallback
? this : nullptr);
1804 if (NS_SUCCEEDED(rv
)) {
1805 LOG((" file doomed"));
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
1818 // Always posts to the main thread.
1822 void CacheEntry::RemoveForcedValidity()
1824 mLock
.AssertCurrentThreadOwns();
1832 nsAutoCString entryKey
;
1833 rv
= HashingKey(entryKey
);
1834 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
));
1854 mozilla::MutexAutoUnlock
unlock(mLock
);
1856 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1858 if (aOperations
& Ops::FRECENCYUPDATE
) {
1862 #define M_LN2 0.69314718055994530942
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
;
1876 // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1) but
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",
1887 &CacheEntry::StoreFrecency
,
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);
1904 if (aOperations
& Ops::CALLBACKS
) {
1905 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
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")
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();
1944 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf
) const
1948 n
+= mCallbacks
.ShallowSizeOfExcludingThis(mallocSizeOf
);
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
1962 // mSecurityInfo doesn't impl nsISizeOf.
1967 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf
) const
1969 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf
);
1973 } // namespace mozilla