1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "CacheEntry.h"
10 #include "CacheFileUtils.h"
11 #include "CacheIndex.h"
13 #include "CacheObserver.h"
14 #include "CacheStorageService.h"
15 #include "mozilla/IntegerPrintfMacros.h"
16 #include "mozilla/Telemetry.h"
17 #include "mozilla/psm/TransportSecurityInfo.h"
18 #include "nsComponentManagerUtils.h"
19 #include "nsIAsyncOutputStream.h"
20 #include "nsICacheEntryOpenCallback.h"
21 #include "nsICacheStorage.h"
22 #include "nsIInputStream.h"
23 #include "nsIOutputStream.h"
24 #include "nsISeekableStream.h"
25 #include "nsISizeOf.h"
28 #include "nsProxyRelease.h"
29 #include "nsServiceManagerUtils.h"
31 #include "nsThreadUtils.h"
33 namespace mozilla::net
{
35 static uint32_t const ENTRY_WANTED
= nsICacheEntryOpenCallback::ENTRY_WANTED
;
36 static uint32_t const RECHECK_AFTER_WRITE_FINISHED
=
37 nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED
;
38 static uint32_t const ENTRY_NEEDS_REVALIDATION
=
39 nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION
;
40 static uint32_t const ENTRY_NOT_WANTED
=
41 nsICacheEntryOpenCallback::ENTRY_NOT_WANTED
;
43 NS_IMPL_ISUPPORTS(CacheEntryHandle
, nsICacheEntry
)
47 CacheEntryHandle::CacheEntryHandle(CacheEntry
* aEntry
) : mEntry(aEntry
) {
49 if (!mEntry
->HandlesCount()) {
50 // CacheEntry.mHandlesCount must go from zero to one only under
51 // the service lock. Can access CacheStorageService::Self() w/o a check
52 // since CacheEntry hrefs it.
53 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
57 mEntry
->AddHandleRef();
59 LOG(("New CacheEntryHandle %p for entry %p", this, aEntry
));
62 NS_IMETHODIMP
CacheEntryHandle::Dismiss() {
63 LOG(("CacheEntryHandle::Dismiss %p", this));
65 if (mClosed
.compareExchange(false, true)) {
66 mEntry
->OnHandleClosed(this);
70 LOG((" already dropped"));
71 return NS_ERROR_UNEXPECTED
;
74 CacheEntryHandle::~CacheEntryHandle() {
75 mEntry
->ReleaseHandleRef();
78 LOG(("CacheEntryHandle::~CacheEntryHandle %p", this));
81 // CacheEntry::Callback
83 CacheEntry::Callback::Callback(CacheEntry
* aEntry
,
84 nsICacheEntryOpenCallback
* aCallback
,
85 bool aReadOnly
, bool aCheckOnAnyThread
,
89 mTarget(GetCurrentSerialEventTarget()),
92 mCheckOnAnyThread(aCheckOnAnyThread
),
93 mRecheckAfterWrite(false),
96 mDoomWhenFoundPinned(false),
97 mDoomWhenFoundNonPinned(false) {
98 MOZ_COUNT_CTOR(CacheEntry::Callback
);
100 // The counter may go from zero to non-null only under the service lock
101 // but here we expect it to be already positive.
102 MOZ_ASSERT(mEntry
->HandlesCount());
103 mEntry
->AddHandleRef();
106 CacheEntry::Callback::Callback(CacheEntry
* aEntry
,
107 bool aDoomWhenFoundInPinStatus
)
110 mRevalidating(false),
111 mCheckOnAnyThread(true),
112 mRecheckAfterWrite(false),
115 mDoomWhenFoundPinned(aDoomWhenFoundInPinStatus
),
116 mDoomWhenFoundNonPinned(!aDoomWhenFoundInPinStatus
) {
117 MOZ_COUNT_CTOR(CacheEntry::Callback
);
118 MOZ_ASSERT(mEntry
->HandlesCount());
119 mEntry
->AddHandleRef();
122 CacheEntry::Callback::Callback(CacheEntry::Callback
const& aThat
)
123 : mEntry(aThat
.mEntry
),
124 mCallback(aThat
.mCallback
),
125 mTarget(aThat
.mTarget
),
126 mReadOnly(aThat
.mReadOnly
),
127 mRevalidating(aThat
.mRevalidating
),
128 mCheckOnAnyThread(aThat
.mCheckOnAnyThread
),
129 mRecheckAfterWrite(aThat
.mRecheckAfterWrite
),
130 mNotWanted(aThat
.mNotWanted
),
131 mSecret(aThat
.mSecret
),
132 mDoomWhenFoundPinned(aThat
.mDoomWhenFoundPinned
),
133 mDoomWhenFoundNonPinned(aThat
.mDoomWhenFoundNonPinned
) {
134 MOZ_COUNT_CTOR(CacheEntry::Callback
);
136 // The counter may go from zero to non-null only under the service lock
137 // but here we expect it to be already positive.
138 MOZ_ASSERT(mEntry
->HandlesCount());
139 mEntry
->AddHandleRef();
142 CacheEntry::Callback::~Callback() {
143 ProxyRelease("CacheEntry::Callback::mCallback", mCallback
, mTarget
);
145 mEntry
->ReleaseHandleRef();
146 MOZ_COUNT_DTOR(CacheEntry::Callback
);
149 // We have locks on both this and aEntry
150 void CacheEntry::Callback::ExchangeEntry(CacheEntry
* aEntry
) {
151 aEntry
->mLock
.AssertCurrentThreadOwns();
152 mEntry
->mLock
.AssertCurrentThreadOwns();
153 if (mEntry
== aEntry
) return;
155 // The counter may go from zero to non-null only under the service lock
156 // but here we expect it to be already positive.
157 MOZ_ASSERT(aEntry
->HandlesCount());
158 aEntry
->AddHandleRef();
159 mEntry
->ReleaseHandleRef();
163 // This is called on entries in another entry's mCallback array, under the lock
164 // of that other entry. No other threads can access this entry at this time.
165 bool CacheEntry::Callback::DeferDoom(bool* aDoom
) const
166 MOZ_NO_THREAD_SAFETY_ANALYSIS
{
167 MOZ_ASSERT(mEntry
->mPinningKnown
);
169 if (MOZ_UNLIKELY(mDoomWhenFoundNonPinned
) ||
170 MOZ_UNLIKELY(mDoomWhenFoundPinned
)) {
172 (MOZ_UNLIKELY(mDoomWhenFoundNonPinned
) &&
173 MOZ_LIKELY(!mEntry
->mPinned
)) ||
174 (MOZ_UNLIKELY(mDoomWhenFoundPinned
) && MOZ_UNLIKELY(mEntry
->mPinned
));
182 nsresult
CacheEntry::Callback::OnCheckThread(bool* aOnCheckThread
) const {
183 if (!mCheckOnAnyThread
) {
184 // Check we are on the target
185 return mTarget
->IsOnCurrentThread(aOnCheckThread
);
188 // We can invoke check anywhere
189 *aOnCheckThread
= true;
193 nsresult
CacheEntry::Callback::OnAvailThread(bool* aOnAvailThread
) const {
194 return mTarget
->IsOnCurrentThread(aOnAvailThread
);
199 NS_IMPL_ISUPPORTS(CacheEntry
, nsIRunnable
, CacheFileListener
)
202 uint64_t CacheEntry::GetNextId() {
203 static Atomic
<uint64_t, Relaxed
> id(0);
207 CacheEntry::CacheEntry(const nsACString
& aStorageID
, const nsACString
& aURI
,
208 const nsACString
& aEnhanceID
, bool aUseDisk
,
209 bool aSkipSizeCheck
, bool aPin
)
211 mEnhanceID(aEnhanceID
),
212 mStorageID(aStorageID
),
214 mSkipSizeCheck(aSkipSizeCheck
),
216 mSecurityInfoLoaded(false),
217 mPreventCallbacks(false),
219 mPinningKnown(false),
220 mCacheEntryId(GetNextId()) {
221 LOG(("CacheEntry::CacheEntry [this=%p]", this));
223 mService
= CacheStorageService::Self();
225 CacheStorageService::Self()->RecordMemoryOnlyEntry(this, !aUseDisk
,
226 true /* overwrite */);
229 CacheEntry::~CacheEntry() { LOG(("CacheEntry::~CacheEntry [this=%p]", this)); }
231 char const* CacheEntry::StateString(uint32_t aState
) {
244 return "REVALIDATING";
250 nsresult
CacheEntry::HashingKeyWithStorage(nsACString
& aResult
) const {
251 return HashingKey(mStorageID
, mEnhanceID
, mURI
, aResult
);
254 nsresult
CacheEntry::HashingKey(nsACString
& aResult
) const {
255 return HashingKey(""_ns
, mEnhanceID
, mURI
, aResult
);
259 nsresult
CacheEntry::HashingKey(const nsACString
& aStorageID
,
260 const nsACString
& aEnhanceID
, nsIURI
* aURI
,
261 nsACString
& aResult
) {
263 nsresult rv
= aURI
->GetAsciiSpec(spec
);
264 NS_ENSURE_SUCCESS(rv
, rv
);
266 return HashingKey(aStorageID
, aEnhanceID
, spec
, aResult
);
270 nsresult
CacheEntry::HashingKey(const nsACString
& aStorageID
,
271 const nsACString
& aEnhanceID
,
272 const nsACString
& aURISpec
,
273 nsACString
& aResult
) {
275 * This key is used to salt hash that is a base for disk file name.
276 * Changing it will cause we will not be able to find files on disk.
279 aResult
.Assign(aStorageID
);
281 if (!aEnhanceID
.IsEmpty()) {
282 CacheFileUtils::AppendTagWithValue(aResult
, '~', aEnhanceID
);
285 // Appending directly
287 aResult
.Append(aURISpec
);
292 void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback
* aCallback
,
294 bool readonly
= aFlags
& nsICacheStorage::OPEN_READONLY
;
295 bool bypassIfBusy
= aFlags
& nsICacheStorage::OPEN_BYPASS_IF_BUSY
;
296 bool truncate
= aFlags
& nsICacheStorage::OPEN_TRUNCATE
;
297 bool priority
= aFlags
& nsICacheStorage::OPEN_PRIORITY
;
298 bool multithread
= aFlags
& nsICacheStorage::CHECK_MULTITHREADED
;
299 bool secret
= aFlags
& nsICacheStorage::OPEN_SECRETLY
;
301 if (MOZ_LOG_TEST(gCache2Log
, LogLevel::Debug
)) {
302 MutexAutoLock
lock(mLock
);
303 LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]",
304 this, StateString(mState
), aFlags
, aCallback
));
308 // yes, if logging is on in DEBUG we'll take the lock twice in a row
309 MutexAutoLock
lock(mLock
);
310 MOZ_ASSERT(!readonly
|| !truncate
, "Bad flags combination");
311 MOZ_ASSERT(!(truncate
&& mState
> LOADING
),
312 "Must not call truncate on already loaded entry");
316 Callback
callback(this, aCallback
, readonly
, multithread
, secret
);
318 if (!Open(callback
, truncate
, priority
, bypassIfBusy
)) {
319 // We get here when the callback wants to bypass cache when it's busy.
320 LOG((" writing or revalidating, callback wants to bypass cache"));
321 callback
.mNotWanted
= true;
322 InvokeAvailableCallback(callback
);
326 bool CacheEntry::Open(Callback
& aCallback
, bool aTruncate
, bool aPriority
,
327 bool aBypassIfBusy
) {
328 mozilla::MutexAutoLock
lock(mLock
);
330 // Check state under the lock
331 if (aBypassIfBusy
&& (mState
== WRITING
|| mState
== REVALIDATING
)) {
335 RememberCallback(aCallback
);
337 // Load() opens the lock
338 if (Load(aTruncate
, aPriority
)) {
339 // Loading is in progress...
348 bool CacheEntry::Load(bool aTruncate
, bool aPriority
) MOZ_REQUIRES(mLock
) {
349 LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate
));
351 mLock
.AssertCurrentThreadOwns();
353 if (mState
> LOADING
) {
354 LOG((" already loaded"));
358 if (mState
== LOADING
) {
359 LOG((" already loading"));
369 nsAutoCString fileKey
;
370 rv
= HashingKeyWithStorage(fileKey
);
372 bool reportMiss
= false;
374 // Check the index under two conditions for two states and take appropriate
376 // 1. When this is a disk entry and not told to truncate, check there is a
378 // If not, set the 'truncate' flag to true so that this entry will open
379 // instantly as a new one.
380 // 2. When this is a memory-only entry, check there is a disk file.
381 // If there is or could be, doom that file.
382 if ((!aTruncate
|| !mUseDisk
) && NS_SUCCEEDED(rv
)) {
383 // Check the index right now to know we have or have not the entry
384 // as soon as possible.
385 CacheIndex::EntryStatus status
;
386 if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey
, &status
))) {
388 case CacheIndex::DOES_NOT_EXIST
:
389 // Doesn't apply to memory-only entries, Load() is called only once
390 // for them and never again for their session lifetime.
391 if (!aTruncate
&& mUseDisk
) {
393 (" entry doesn't exist according information from the index, "
399 case CacheIndex::EXISTS
:
400 case CacheIndex::DO_NOT_KNOW
:
403 (" entry open as memory-only, but there is a file, status=%d, "
406 CacheFileIOManager::DoomFileByKey(fileKey
, nullptr);
413 mFile
= new CacheFile();
415 BackgroundOp(Ops::REGISTER
);
417 bool directLoad
= aTruncate
|| !mUseDisk
;
419 // mLoadStart will be used to calculate telemetry of life-time of this
420 // entry. Low resulution is then enough.
421 mLoadStart
= TimeStamp::NowLoRes();
422 mPinningKnown
= true;
424 mLoadStart
= TimeStamp::Now();
428 mozilla::MutexAutoUnlock
unlock(mLock
);
431 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
432 CacheFileUtils::DetailedCacheHitTelemetry::MISS
, mLoadStart
);
435 LOG((" performing load, file=%p", mFile
.get()));
436 if (NS_SUCCEEDED(rv
)) {
437 rv
= mFile
->Init(fileKey
, aTruncate
, !mUseDisk
, mSkipSizeCheck
, aPriority
,
438 mPinned
, directLoad
? nullptr : this);
449 // Just fake the load has already been done as "new".
454 return mState
== LOADING
;
457 NS_IMETHODIMP
CacheEntry::OnFileReady(nsresult aResult
, bool aIsNew
) {
458 LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08" PRIx32
", new=%d]", this,
459 static_cast<uint32_t>(aResult
), aIsNew
));
461 MOZ_ASSERT(!mLoadStart
.IsNull());
463 if (NS_SUCCEEDED(aResult
)) {
465 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
466 CacheFileUtils::DetailedCacheHitTelemetry::MISS
, mLoadStart
);
468 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
469 CacheFileUtils::DetailedCacheHitTelemetry::HIT
, mLoadStart
);
473 // OnFileReady, that is the only code that can transit from LOADING
474 // to any follow-on state and can only be invoked ones on an entry.
475 // Until this moment there is no consumer that could manipulate
478 mozilla::MutexAutoLock
lock(mLock
);
480 MOZ_ASSERT(mState
== LOADING
);
482 mState
= (aIsNew
|| NS_FAILED(aResult
)) ? EMPTY
: READY
;
484 mFileStatus
= aResult
;
486 mPinned
= mFile
->IsPinned();
488 mPinningKnown
= true;
489 LOG((" pinning=%d", (bool)mPinned
));
491 if (mState
== READY
) {
495 mFile
->GetFrecency(&frecency
);
496 // mFrecency is held in a double to increase computance precision.
497 // It is ok to persist frecency only as a uint32 with some math involved.
498 mFrecency
= INT2FRECENCY(frecency
);
506 NS_IMETHODIMP
CacheEntry::OnFileDoomed(nsresult aResult
) {
508 RefPtr
<DoomCallbackRunnable
> event
=
509 new DoomCallbackRunnable(this, aResult
);
510 NS_DispatchToMainThread(event
);
516 already_AddRefed
<CacheEntryHandle
> CacheEntry::ReopenTruncated(
517 bool aMemoryOnly
, nsICacheEntryOpenCallback
* aCallback
)
518 MOZ_REQUIRES(mLock
) {
519 LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
521 mLock
.AssertCurrentThreadOwns();
523 // Hold callbacks invocation, AddStorageEntry would invoke from doom
525 mPreventCallbacks
= true;
527 RefPtr
<CacheEntryHandle
> handle
;
528 RefPtr
<CacheEntry
> newEntry
;
531 MOZ_ASSERT(mUseDisk
);
532 // We want to pin even no-store entries (the case we recreate a disk entry
533 // as a memory-only entry.)
537 mozilla::MutexAutoUnlock
unlock(mLock
);
539 // The following call dooms this entry (calls DoomAlreadyRemoved on us)
540 nsresult rv
= CacheStorageService::Self()->AddStorageEntry(
541 GetStorageID(), GetURI(), GetEnhanceID(), mUseDisk
&& !aMemoryOnly
,
542 mSkipSizeCheck
, mPinned
,
543 nsICacheStorage::OPEN_TRUNCATE
, // truncate existing (this one)
544 getter_AddRefs(handle
));
546 if (NS_SUCCEEDED(rv
)) {
547 newEntry
= handle
->Entry();
548 LOG((" exchanged entry %p by entry %p, rv=0x%08" PRIx32
, this,
549 newEntry
.get(), static_cast<uint32_t>(rv
)));
550 newEntry
->AsyncOpen(aCallback
, nsICacheStorage::OPEN_TRUNCATE
);
552 LOG((" exchanged of entry %p failed, rv=0x%08" PRIx32
, this,
553 static_cast<uint32_t>(rv
)));
558 mPreventCallbacks
= false;
560 if (!newEntry
) return nullptr;
562 newEntry
->TransferCallbacks(*this);
565 // Must return a new write handle, since the consumer is expected to
566 // write to this newly recreated entry. The |handle| is only a common
567 // reference counter and doesn't revert entry state back when write
568 // fails and also doesn't update the entry frecency. Not updating
569 // frecency causes entries to not be purged from our memory pools.
570 RefPtr
<CacheEntryHandle
> writeHandle
= newEntry
->NewWriteHandle();
571 return writeHandle
.forget();
574 void CacheEntry::TransferCallbacks(CacheEntry
& aFromEntry
) {
575 mozilla::MutexAutoLock
lock(mLock
);
576 aFromEntry
.mLock
.AssertCurrentThreadOwns();
578 LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]", this, &aFromEntry
));
580 if (!mCallbacks
.Length()) {
581 mCallbacks
.SwapElements(aFromEntry
.mCallbacks
);
583 mCallbacks
.AppendElements(aFromEntry
.mCallbacks
);
586 uint32_t callbacksLength
= mCallbacks
.Length();
587 if (callbacksLength
) {
588 // Carry the entry reference (unfortunately, needs to be done manually...)
589 for (uint32_t i
= 0; i
< callbacksLength
; ++i
) {
590 mCallbacks
[i
].ExchangeEntry(this);
593 BackgroundOp(Ops::CALLBACKS
, true);
597 void CacheEntry::RememberCallback(Callback
& aCallback
) {
598 mLock
.AssertCurrentThreadOwns();
600 LOG(("CacheEntry::RememberCallback [this=%p, cb=%p, state=%s]", this,
601 aCallback
.mCallback
.get(), StateString(mState
)));
603 mCallbacks
.AppendElement(aCallback
);
606 void CacheEntry::InvokeCallbacksLock() {
607 mozilla::MutexAutoLock
lock(mLock
);
611 void CacheEntry::InvokeCallbacks() {
612 mLock
.AssertCurrentThreadOwns();
614 LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
616 // Invoke first all r/w callbacks, then all r/o callbacks.
617 if (InvokeCallbacks(false)) InvokeCallbacks(true);
619 LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
622 bool CacheEntry::InvokeCallbacks(bool aReadOnly
) MOZ_REQUIRES(mLock
) {
623 mLock
.AssertCurrentThreadOwns();
625 RefPtr
<CacheEntryHandle
> recreatedHandle
;
628 while (i
< mCallbacks
.Length()) {
629 if (mPreventCallbacks
) {
630 LOG((" callbacks prevented!"));
634 if (!mIsDoomed
&& (mState
== WRITING
|| mState
== REVALIDATING
)) {
635 LOG((" entry is being written/revalidated"));
640 if (mCallbacks
[i
].DeferDoom(&recreate
)) {
641 mCallbacks
.RemoveElementAt(i
);
646 LOG((" defer doom marker callback hit positive, recreating"));
647 recreatedHandle
= ReopenTruncated(!mUseDisk
, nullptr);
651 if (mCallbacks
[i
].mReadOnly
!= aReadOnly
) {
652 // Callback is not r/w or r/o, go to another one in line
658 nsresult rv
= mCallbacks
[i
].OnCheckThread(&onCheckThread
);
660 if (NS_SUCCEEDED(rv
) && !onCheckThread
) {
661 // Redispatch to the target thread
662 rv
= mCallbacks
[i
].mTarget
->Dispatch(
663 NewRunnableMethod("net::CacheEntry::InvokeCallbacksLock", this,
664 &CacheEntry::InvokeCallbacksLock
),
665 nsIEventTarget::DISPATCH_NORMAL
);
666 if (NS_SUCCEEDED(rv
)) {
667 LOG((" re-dispatching to target thread"));
672 Callback callback
= mCallbacks
[i
];
673 mCallbacks
.RemoveElementAt(i
);
675 if (NS_SUCCEEDED(rv
) && !InvokeCallback(callback
)) {
676 // Callback didn't fire, put it back and go to another one in line.
677 // Only reason InvokeCallback returns false is that onCacheEntryCheck
678 // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other
679 // readers or potential writers would be unnecessarily kept from being
681 size_t pos
= std::min(mCallbacks
.Length(), static_cast<size_t>(i
));
682 mCallbacks
.InsertElementAt(pos
, callback
);
687 if (recreatedHandle
) {
688 // Must be released outside of the lock, enters InvokeCallback on the new
690 mozilla::MutexAutoUnlock
unlock(mLock
);
691 recreatedHandle
= nullptr;
697 bool CacheEntry::InvokeCallback(Callback
& aCallback
) MOZ_REQUIRES(mLock
) {
698 mLock
.AssertCurrentThreadOwns();
699 LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", this,
700 StateString(mState
), aCallback
.mCallback
.get()));
702 // When this entry is doomed we want to notify the callback any time
704 // When we are here, the entry must be loaded from disk
705 MOZ_ASSERT(mState
> LOADING
);
707 if (mState
== WRITING
|| mState
== REVALIDATING
) {
708 // Prevent invoking other callbacks since one of them is now writing
709 // or revalidating this entry. No consumers should get this entry
710 // until metadata are filled with values downloaded from the server
711 // or the entry revalidated and output stream has been opened.
712 LOG((" entry is being written/revalidated, callback bypassed"));
716 // mRecheckAfterWrite flag already set means the callback has already passed
717 // the onCacheEntryCheck call. Until the current write is not finished this
718 // callback will be bypassed.
719 if (!aCallback
.mRecheckAfterWrite
) {
720 if (!aCallback
.mReadOnly
) {
721 if (mState
== EMPTY
) {
722 // Advance to writing state, we expect to invoke the callback and let
723 // it fill content of this entry. Must set and check the state here
724 // to prevent more then one
726 LOG((" advancing to WRITING state"));
729 if (!aCallback
.mCallback
) {
730 // We can be given no callback only in case of recreate, it is ok
731 // to advance to WRITING state since the caller of recreate is
732 // expected to write this entry now.
737 if (mState
== READY
) {
738 // Metadata present, validate the entry
739 uint32_t checkResult
;
741 // mayhemer: TODO check and solve any potential races of concurent
743 mozilla::MutexAutoUnlock
unlock(mLock
);
745 RefPtr
<CacheEntryHandle
> handle
= NewHandle();
748 aCallback
.mCallback
->OnCacheEntryCheck(handle
, &checkResult
);
749 LOG((" OnCacheEntryCheck: rv=0x%08" PRIx32
", result=%" PRId32
,
750 static_cast<uint32_t>(rv
), static_cast<uint32_t>(checkResult
)));
752 if (NS_FAILED(rv
)) checkResult
= ENTRY_NOT_WANTED
;
755 aCallback
.mRevalidating
= checkResult
== ENTRY_NEEDS_REVALIDATION
;
757 switch (checkResult
) {
759 // Nothing more to do here, the consumer is responsible to handle
760 // the result of OnCacheEntryCheck it self.
761 // Proceed to callback...
764 case RECHECK_AFTER_WRITE_FINISHED
:
766 (" consumer will check on the entry again after write is "
768 // The consumer wants the entry to complete first.
769 aCallback
.mRecheckAfterWrite
= true;
772 case ENTRY_NEEDS_REVALIDATION
:
773 LOG((" will be holding callbacks until entry is revalidated"));
774 // State is READY now and from that state entry cannot transit to
775 // any other state then REVALIDATING for which cocurrency is not an
776 // issue. Potentially no need to lock here.
777 mState
= REVALIDATING
;
780 case ENTRY_NOT_WANTED
:
781 LOG((" consumer not interested in the entry"));
782 // Do not give this entry to the consumer, it is not interested in
784 aCallback
.mNotWanted
= true;
791 if (aCallback
.mCallback
) {
792 if (!mIsDoomed
&& aCallback
.mRecheckAfterWrite
) {
793 // If we don't have data and the callback wants a complete entry,
795 bool bypass
= !mHasData
;
796 if (!bypass
&& NS_SUCCEEDED(mFileStatus
)) {
798 bypass
= !mFile
->DataSize(&_unused
);
802 LOG((" bypassing, entry data still being written"));
806 // Entry is complete now, do the check+avail call again
807 aCallback
.mRecheckAfterWrite
= false;
808 return InvokeCallback(aCallback
);
811 mozilla::MutexAutoUnlock
unlock(mLock
);
812 InvokeAvailableCallback(aCallback
);
818 void CacheEntry::InvokeAvailableCallback(Callback
const& aCallback
) {
822 mozilla::MutexAutoLock
lock(mLock
);
825 ("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, "
828 this, StateString(mState
), aCallback
.mCallback
.get(),
829 aCallback
.mReadOnly
, aCallback
.mNotWanted
));
831 // When we are here, the entry must be loaded from disk
832 MOZ_ASSERT(state
> LOADING
|| mIsDoomed
);
836 rv
= aCallback
.OnAvailThread(&onAvailThread
);
838 LOG((" target thread dead?"));
842 if (!onAvailThread
) {
843 // Dispatch to the right thread
844 RefPtr
<AvailableCallbackRunnable
> event
=
845 new AvailableCallbackRunnable(this, aCallback
);
847 rv
= aCallback
.mTarget
->Dispatch(event
, nsIEventTarget::DISPATCH_NORMAL
);
848 LOG((" redispatched, (rv = 0x%08" PRIx32
")", static_cast<uint32_t>(rv
)));
852 if (mIsDoomed
|| aCallback
.mNotWanted
) {
854 (" doomed or not wanted, notifying OCEA with "
855 "NS_ERROR_CACHE_KEY_NOT_FOUND"));
856 aCallback
.mCallback
->OnCacheEntryAvailable(nullptr, false,
857 NS_ERROR_CACHE_KEY_NOT_FOUND
);
861 if (state
== READY
) {
862 LOG((" ready/has-meta, notifying OCEA with entry and NS_OK"));
864 if (!aCallback
.mSecret
) {
865 mozilla::MutexAutoLock
lock(mLock
);
866 BackgroundOp(Ops::FRECENCYUPDATE
);
869 OnFetched(aCallback
);
871 RefPtr
<CacheEntryHandle
> handle
= NewHandle();
872 aCallback
.mCallback
->OnCacheEntryAvailable(handle
, false, NS_OK
);
876 // R/O callbacks may do revalidation, let them fall through
877 if (aCallback
.mReadOnly
&& !aCallback
.mRevalidating
) {
879 (" r/o and not ready, notifying OCEA with "
880 "NS_ERROR_CACHE_KEY_NOT_FOUND"));
881 aCallback
.mCallback
->OnCacheEntryAvailable(nullptr, false,
882 NS_ERROR_CACHE_KEY_NOT_FOUND
);
886 // This is a new or potentially non-valid entry and needs to be fetched first.
887 // The CacheEntryHandle blocks other consumers until the channel
888 // either releases the entry or marks metadata as filled or whole entry valid,
889 // i.e. until MetaDataReady() or SetValid() on the entry is called
892 // Consumer will be responsible to fill or validate the entry metadata and
895 OnFetched(aCallback
);
897 RefPtr
<CacheEntryHandle
> handle
= NewWriteHandle();
898 rv
= aCallback
.mCallback
->OnCacheEntryAvailable(handle
, state
== WRITING
,
902 LOG((" writing/revalidating failed (0x%08" PRIx32
")",
903 static_cast<uint32_t>(rv
)));
905 // Consumer given a new entry failed to take care of the entry.
906 OnHandleClosed(handle
);
910 LOG((" writing/revalidating"));
913 void CacheEntry::OnFetched(Callback
const& aCallback
) {
914 if (NS_SUCCEEDED(mFileStatus
) && !aCallback
.mSecret
) {
915 // Let the last-fetched and fetch-count properties be updated.
920 CacheEntryHandle
* CacheEntry::NewHandle() { return new CacheEntryHandle(this); }
922 CacheEntryHandle
* CacheEntry::NewWriteHandle() {
923 mozilla::MutexAutoLock
lock(mLock
);
925 // Ignore the OPEN_SECRETLY flag on purpose here, which should actually be
926 // used only along with OPEN_READONLY, but there is no need to enforce that.
927 BackgroundOp(Ops::FRECENCYUPDATE
);
929 return (mWriter
= NewHandle());
932 void CacheEntry::OnHandleClosed(CacheEntryHandle
const* aHandle
) {
933 mozilla::MutexAutoLock
lock(mLock
);
934 LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this,
935 StateString(mState
), aHandle
));
937 if (mIsDoomed
&& NS_SUCCEEDED(mFileStatus
) &&
938 // Note: mHandlesCount is dropped before this method is called
939 (mHandlesCount
== 0 ||
940 (mHandlesCount
== 1 && mWriter
&& mWriter
!= aHandle
))) {
941 // This entry is no longer referenced from outside and is doomed.
942 // We can do this also when there is just reference from the writer,
943 // no one else could ever reach the written data.
944 // Tell the file to kill the handle, i.e. bypass any I/O operations
945 // on it except removing the file.
949 if (mWriter
!= aHandle
) {
950 LOG((" not the writer"));
955 LOG((" abandoning phantom output stream"));
956 // No one took our internal output stream, so there are no data
957 // and output stream has to be open symultaneously with input stream
958 // on this entry again.
960 // This asynchronously ends up invoking callbacks on this entry
961 // through OnOutputClosed() call.
962 mOutputStream
->Close();
963 mOutputStream
= nullptr;
965 // We must always redispatch, otherwise there is a risk of stack
966 // overflow. This code can recurse deeply. It won't execute sooner
967 // than we release mLock.
968 BackgroundOp(Ops::CALLBACKS
, true);
973 if (mState
== WRITING
) {
974 LOG((" reverting to state EMPTY - write failed"));
976 } else if (mState
== REVALIDATING
) {
977 LOG((" reverting to state READY - reval failed"));
981 if (mState
== READY
&& !mHasData
) {
982 // We may get to this state when following steps happen:
983 // 1. a new entry is given to a consumer
984 // 2. the consumer calls MetaDataReady(), we transit to READY
985 // 3. abandons the entry w/o opening the output stream, mHasData left false
987 // In this case any following consumer will get a ready entry (with
988 // metadata) but in state like the entry data write was still happening (was
989 // in progress) and will indefinitely wait for the entry data or even the
990 // entry itself when RECHECK_AFTER_WRITE is returned from onCacheEntryCheck.
992 (" we are in READY state, pretend we have data regardless it"
993 " has actully been never touched"));
998 void CacheEntry::OnOutputClosed() {
999 // Called when the file's output stream is closed. Invoke any callbacks
1000 // waiting for complete entry.
1002 mozilla::MutexAutoLock
lock(mLock
);
1006 bool CacheEntry::IsReferenced() const {
1007 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
1009 // Increasing this counter from 0 to non-null and this check both happen only
1010 // under the service lock.
1011 return mHandlesCount
> 0;
1014 bool CacheEntry::IsFileDoomed() {
1015 if (NS_SUCCEEDED(mFileStatus
)) {
1016 return mFile
->IsDoomed();
1022 uint32_t CacheEntry::GetMetadataMemoryConsumption() {
1023 NS_ENSURE_SUCCESS(mFileStatus
, 0);
1026 if (NS_FAILED(mFile
->ElementsSize(&size
))) return 0;
1033 nsresult
CacheEntry::GetPersistent(bool* aPersistToDisk
) {
1034 // No need to sync when only reading.
1035 // When consumer needs to be consistent with state of the memory storage
1036 // entries table, then let it use GetUseDisk getter that must be called under
1037 // the service lock.
1038 *aPersistToDisk
= mUseDisk
;
1042 nsresult
CacheEntry::GetKey(nsACString
& aKey
) {
1047 nsresult
CacheEntry::GetCacheEntryId(uint64_t* aCacheEntryId
) {
1048 *aCacheEntryId
= mCacheEntryId
;
1052 nsresult
CacheEntry::GetFetchCount(uint32_t* aFetchCount
) {
1053 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1055 return mFile
->GetFetchCount(aFetchCount
);
1058 nsresult
CacheEntry::GetLastFetched(uint32_t* aLastFetched
) {
1059 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1061 return mFile
->GetLastFetched(aLastFetched
);
1064 nsresult
CacheEntry::GetLastModified(uint32_t* aLastModified
) {
1065 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1067 return mFile
->GetLastModified(aLastModified
);
1070 nsresult
CacheEntry::GetExpirationTime(uint32_t* aExpirationTime
) {
1071 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1073 return mFile
->GetExpirationTime(aExpirationTime
);
1076 nsresult
CacheEntry::GetOnStartTime(uint64_t* aTime
) {
1077 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1078 return mFile
->GetOnStartTime(aTime
);
1081 nsresult
CacheEntry::GetOnStopTime(uint64_t* aTime
) {
1082 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1083 return mFile
->GetOnStopTime(aTime
);
1086 nsresult
CacheEntry::SetNetworkTimes(uint64_t aOnStartTime
,
1087 uint64_t aOnStopTime
) {
1088 if (NS_SUCCEEDED(mFileStatus
)) {
1089 return mFile
->SetNetworkTimes(aOnStartTime
, aOnStopTime
);
1091 return NS_ERROR_NOT_AVAILABLE
;
1094 nsresult
CacheEntry::SetContentType(uint8_t aContentType
) {
1095 NS_ENSURE_ARG_MAX(aContentType
, nsICacheEntry::CONTENT_TYPE_LAST
- 1);
1097 if (NS_SUCCEEDED(mFileStatus
)) {
1098 return mFile
->SetContentType(aContentType
);
1100 return NS_ERROR_NOT_AVAILABLE
;
1103 nsresult
CacheEntry::GetIsForcedValid(bool* aIsForcedValid
) {
1104 NS_ENSURE_ARG(aIsForcedValid
);
1108 mozilla::MutexAutoLock
lock(mLock
);
1109 MOZ_ASSERT(mState
> LOADING
);
1113 *aIsForcedValid
= true;
1118 nsresult rv
= HashingKey(key
);
1119 if (NS_FAILED(rv
)) {
1124 CacheStorageService::Self()->IsForcedValidEntry(mStorageID
, key
);
1125 LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this,
1131 nsresult
CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture
) {
1132 LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this,
1133 aSecondsToTheFuture
));
1136 nsresult rv
= HashingKey(key
);
1137 if (NS_FAILED(rv
)) {
1141 CacheStorageService::Self()->ForceEntryValidFor(mStorageID
, key
,
1142 aSecondsToTheFuture
);
1147 nsresult
CacheEntry::MarkForcedValidUse() {
1148 LOG(("CacheEntry::MarkForcedValidUse [this=%p, ]", this));
1151 nsresult rv
= HashingKey(key
);
1152 if (NS_FAILED(rv
)) {
1156 CacheStorageService::Self()->MarkForcedValidEntryUse(mStorageID
, key
);
1160 nsresult
CacheEntry::SetExpirationTime(uint32_t aExpirationTime
) {
1161 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1163 nsresult rv
= mFile
->SetExpirationTime(aExpirationTime
);
1164 NS_ENSURE_SUCCESS(rv
, rv
);
1166 // Aligned assignment, thus atomic.
1167 mSortingExpirationTime
= aExpirationTime
;
1171 nsresult
CacheEntry::OpenInputStream(int64_t offset
, nsIInputStream
** _retval
) {
1172 LOG(("CacheEntry::OpenInputStream [this=%p]", this));
1173 return OpenInputStreamInternal(offset
, nullptr, _retval
);
1176 nsresult
CacheEntry::OpenAlternativeInputStream(const nsACString
& type
,
1177 nsIInputStream
** _retval
) {
1178 LOG(("CacheEntry::OpenAlternativeInputStream [this=%p, type=%s]", this,
1179 PromiseFlatCString(type
).get()));
1180 return OpenInputStreamInternal(0, PromiseFlatCString(type
).get(), _retval
);
1183 nsresult
CacheEntry::OpenInputStreamInternal(int64_t offset
,
1184 const char* aAltDataType
,
1185 nsIInputStream
** _retval
) {
1186 LOG(("CacheEntry::OpenInputStreamInternal [this=%p]", this));
1188 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1192 RefPtr
<CacheEntryHandle
> selfHandle
= NewHandle();
1194 nsCOMPtr
<nsIInputStream
> stream
;
1196 rv
= mFile
->OpenAlternativeInputStream(selfHandle
, aAltDataType
,
1197 getter_AddRefs(stream
));
1198 if (NS_FAILED(rv
)) {
1199 // Failure of this method may be legal when the alternative data requested
1200 // is not avaialble or of a different type. Console error logs are
1201 // ensured by CacheFile::OpenAlternativeInputStream.
1205 rv
= mFile
->OpenInputStream(selfHandle
, getter_AddRefs(stream
));
1206 NS_ENSURE_SUCCESS(rv
, rv
);
1209 nsCOMPtr
<nsISeekableStream
> seekable
= do_QueryInterface(stream
, &rv
);
1210 NS_ENSURE_SUCCESS(rv
, rv
);
1212 rv
= seekable
->Seek(nsISeekableStream::NS_SEEK_SET
, offset
);
1213 NS_ENSURE_SUCCESS(rv
, rv
);
1215 mozilla::MutexAutoLock
lock(mLock
);
1218 // So far output stream on this new entry not opened, do it now.
1219 LOG((" creating phantom output stream"));
1220 rv
= OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream
));
1221 NS_ENSURE_SUCCESS(rv
, rv
);
1224 stream
.forget(_retval
);
1228 nsresult
CacheEntry::OpenOutputStream(int64_t offset
, int64_t predictedSize
,
1229 nsIOutputStream
** _retval
) {
1230 LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
1234 mozilla::MutexAutoLock
lock(mLock
);
1236 MOZ_ASSERT(mState
> EMPTY
);
1238 if (mFile
->EntryWouldExceedLimit(0, predictedSize
, false)) {
1239 LOG((" entry would exceed size limit"));
1240 return NS_ERROR_FILE_TOO_BIG
;
1243 if (mOutputStream
&& !mIsDoomed
) {
1244 LOG((" giving phantom output stream"));
1245 mOutputStream
.forget(_retval
);
1247 rv
= OpenOutputStreamInternal(offset
, _retval
);
1248 if (NS_FAILED(rv
)) return rv
;
1251 // Entry considered ready when writer opens output stream.
1252 if (mState
< READY
) mState
= READY
;
1254 // Invoke any pending readers now.
1260 nsresult
CacheEntry::OpenAlternativeOutputStream(
1261 const nsACString
& type
, int64_t predictedSize
,
1262 nsIAsyncOutputStream
** _retval
) {
1263 LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
1264 PromiseFlatCString(type
).get()));
1268 if (type
.IsEmpty()) {
1269 // The empty string is reserved to mean no alt-data available.
1270 return NS_ERROR_INVALID_ARG
;
1273 mozilla::MutexAutoLock
lock(mLock
);
1275 if (!mHasData
|| mState
< READY
|| mOutputStream
|| mIsDoomed
) {
1276 LOG((" entry not in state to write alt-data"));
1277 return NS_ERROR_NOT_AVAILABLE
;
1280 if (mFile
->EntryWouldExceedLimit(0, predictedSize
, true)) {
1281 LOG((" entry would exceed size limit"));
1282 return NS_ERROR_FILE_TOO_BIG
;
1285 nsCOMPtr
<nsIAsyncOutputStream
> stream
;
1286 rv
= mFile
->OpenAlternativeOutputStream(
1287 nullptr, PromiseFlatCString(type
).get(), getter_AddRefs(stream
));
1288 NS_ENSURE_SUCCESS(rv
, rv
);
1290 stream
.swap(*_retval
);
1294 nsresult
CacheEntry::OpenOutputStreamInternal(int64_t offset
,
1295 nsIOutputStream
** _retval
) {
1296 LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
1298 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1300 mLock
.AssertCurrentThreadOwns();
1303 LOG((" doomed..."));
1304 return NS_ERROR_NOT_AVAILABLE
;
1307 MOZ_ASSERT(mState
> LOADING
);
1311 // No need to sync on mUseDisk here, we don't need to be consistent
1312 // with content of the memory storage entries hash table.
1314 rv
= mFile
->SetMemoryOnly();
1315 NS_ENSURE_SUCCESS(rv
, rv
);
1318 RefPtr
<CacheOutputCloseListener
> listener
=
1319 new CacheOutputCloseListener(this);
1321 nsCOMPtr
<nsIOutputStream
> stream
;
1322 rv
= mFile
->OpenOutputStream(listener
, getter_AddRefs(stream
));
1323 NS_ENSURE_SUCCESS(rv
, rv
);
1325 nsCOMPtr
<nsISeekableStream
> seekable
= do_QueryInterface(stream
, &rv
);
1326 NS_ENSURE_SUCCESS(rv
, rv
);
1328 rv
= seekable
->Seek(nsISeekableStream::NS_SEEK_SET
, offset
);
1329 NS_ENSURE_SUCCESS(rv
, rv
);
1331 // Prevent opening output stream again.
1334 stream
.swap(*_retval
);
1338 nsresult
CacheEntry::GetSecurityInfo(nsITransportSecurityInfo
** aSecurityInfo
) {
1340 mozilla::MutexAutoLock
lock(mLock
);
1341 if (mSecurityInfoLoaded
) {
1342 *aSecurityInfo
= do_AddRef(mSecurityInfo
).take();
1347 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1350 nsresult rv
= mFile
->GetElement("security-info", getter_Copies(info
));
1351 NS_ENSURE_SUCCESS(rv
, rv
);
1352 nsCOMPtr
<nsITransportSecurityInfo
> securityInfo
;
1353 if (!info
.IsVoid()) {
1354 rv
= mozilla::psm::TransportSecurityInfo::Read(
1355 info
, getter_AddRefs(securityInfo
));
1356 NS_ENSURE_SUCCESS(rv
, rv
);
1358 if (!securityInfo
) {
1359 return NS_ERROR_NOT_AVAILABLE
;
1363 mozilla::MutexAutoLock
lock(mLock
);
1365 mSecurityInfo
.swap(securityInfo
);
1366 mSecurityInfoLoaded
= true;
1368 *aSecurityInfo
= do_AddRef(mSecurityInfo
).take();
1374 nsresult
CacheEntry::SetSecurityInfo(nsITransportSecurityInfo
* aSecurityInfo
) {
1377 NS_ENSURE_SUCCESS(mFileStatus
, mFileStatus
);
1380 mozilla::MutexAutoLock
lock(mLock
);
1382 mSecurityInfo
= aSecurityInfo
;
1383 mSecurityInfoLoaded
= true;
1387 if (aSecurityInfo
) {
1388 rv
= aSecurityInfo
->ToString(info
);
1389 NS_ENSURE_SUCCESS(rv
, rv
);
1392 rv
= mFile
->SetElement("security-info", info
.Length() ? info
.get() : nullptr);
1393 NS_ENSURE_SUCCESS(rv
, rv
);
1398 nsresult
CacheEntry::GetStorageDataSize(uint32_t* aStorageDataSize
) {
1399 NS_ENSURE_ARG(aStorageDataSize
);
1402 nsresult rv
= GetDataSize(&dataSize
);
1403 if (NS_FAILED(rv
)) return rv
;
1405 *aStorageDataSize
= (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize
);
1410 nsresult
CacheEntry::AsyncDoom(nsICacheEntryDoomCallback
* aCallback
) {
1411 LOG(("CacheEntry::AsyncDoom [this=%p]", this));
1414 mozilla::MutexAutoLock
lock(mLock
);
1416 if (mIsDoomed
|| mDoomCallback
) {
1417 return NS_ERROR_IN_PROGRESS
; // to aggregate have DOOMING state
1420 RemoveForcedValidity();
1423 mDoomCallback
= aCallback
;
1426 // This immediately removes the entry from the master hashtable and also
1427 // immediately dooms the file. This way we make sure that any consumer
1428 // after this point asking for the same entry won't get
1430 // b) a new entry with the same file
1436 nsresult
CacheEntry::GetMetaDataElement(const char* aKey
, char** aRetval
) {
1437 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1439 return mFile
->GetElement(aKey
, aRetval
);
1442 nsresult
CacheEntry::SetMetaDataElement(const char* aKey
, const char* aValue
) {
1443 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1445 return mFile
->SetElement(aKey
, aValue
);
1448 nsresult
CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor
* aVisitor
) {
1449 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1451 return mFile
->VisitMetaData(aVisitor
);
1454 nsresult
CacheEntry::MetaDataReady() {
1455 mozilla::MutexAutoLock
lock(mLock
);
1457 LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this,
1458 StateString(mState
)));
1460 MOZ_ASSERT(mState
> EMPTY
);
1462 if (mState
== WRITING
) mState
= READY
;
1469 nsresult
CacheEntry::SetValid() {
1470 nsCOMPtr
<nsIOutputStream
> outputStream
;
1473 mozilla::MutexAutoLock
lock(mLock
);
1474 LOG(("CacheEntry::SetValid [this=%p, state=%s]", this,
1475 StateString(mState
)));
1477 MOZ_ASSERT(mState
> EMPTY
);
1484 outputStream
.swap(mOutputStream
);
1488 LOG((" abandoning phantom output stream"));
1489 outputStream
->Close();
1495 nsresult
CacheEntry::Recreate(bool aMemoryOnly
, nsICacheEntry
** _retval
) {
1496 mozilla::MutexAutoLock
lock(mLock
);
1497 LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState
)));
1499 RefPtr
<CacheEntryHandle
> handle
= ReopenTruncated(aMemoryOnly
, nullptr);
1501 handle
.forget(_retval
);
1505 BackgroundOp(Ops::CALLBACKS
, true);
1506 return NS_ERROR_NOT_AVAILABLE
;
1509 nsresult
CacheEntry::GetDataSize(int64_t* aDataSize
) {
1510 LOG(("CacheEntry::GetDataSize [this=%p]", this));
1514 mozilla::MutexAutoLock
lock(mLock
);
1517 LOG((" write in progress (no data)"));
1518 return NS_ERROR_IN_PROGRESS
;
1522 NS_ENSURE_SUCCESS(mFileStatus
, mFileStatus
);
1524 // mayhemer: TODO Problem with compression?
1525 if (!mFile
->DataSize(aDataSize
)) {
1526 LOG((" write in progress (stream active)"));
1527 return NS_ERROR_IN_PROGRESS
;
1530 LOG((" size=%" PRId64
, *aDataSize
));
1534 nsresult
CacheEntry::GetAltDataSize(int64_t* aDataSize
) {
1535 LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
1536 if (NS_FAILED(mFileStatus
)) {
1539 return mFile
->GetAltDataSize(aDataSize
);
1542 nsresult
CacheEntry::GetAltDataType(nsACString
& aType
) {
1543 LOG(("CacheEntry::GetAltDataType [this=%p]", this));
1544 if (NS_FAILED(mFileStatus
)) {
1547 return mFile
->GetAltDataType(aType
);
1550 nsresult
CacheEntry::GetDiskStorageSizeInKB(uint32_t* aDiskStorageSize
) {
1551 if (NS_FAILED(mFileStatus
)) {
1552 return NS_ERROR_NOT_AVAILABLE
;
1555 return mFile
->GetDiskStorageSizeInKB(aDiskStorageSize
);
1558 nsresult
CacheEntry::GetLoadContextInfo(nsILoadContextInfo
** aInfo
) {
1559 nsCOMPtr
<nsILoadContextInfo
> info
= CacheFileUtils::ParseKey(mStorageID
);
1561 return NS_ERROR_FAILURE
;
1571 NS_IMETHODIMP
CacheEntry::Run() {
1572 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1574 mozilla::MutexAutoLock
lock(mLock
);
1576 BackgroundOp(mBackgroundOperations
.Grab());
1580 // Management methods
1582 double CacheEntry::GetFrecency() const {
1583 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1587 uint32_t CacheEntry::GetExpirationTime() const {
1588 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1589 return mSortingExpirationTime
;
1592 bool CacheEntry::IsRegistered() const {
1593 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1594 return mRegistration
== REGISTERED
;
1597 bool CacheEntry::CanRegister() const {
1598 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1599 return mRegistration
== NEVERREGISTERED
;
1602 void CacheEntry::SetRegistered(bool aRegistered
) {
1603 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1606 MOZ_ASSERT(mRegistration
== NEVERREGISTERED
);
1607 mRegistration
= REGISTERED
;
1609 MOZ_ASSERT(mRegistration
== REGISTERED
);
1610 mRegistration
= DEREGISTERED
;
1614 bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned
) {
1615 LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this));
1617 mozilla::MutexAutoLock
lock(mLock
);
1618 if (mPinningKnown
) {
1619 LOG((" pinned=%d, caller=%d", (bool)mPinned
, aPinned
));
1620 // Bypass when the pin status of this entry doesn't match the pin status
1621 // caller wants to remove
1622 return mPinned
!= aPinned
;
1625 LOG((" pinning unknown, caller=%d", aPinned
));
1626 // Oterwise, remember to doom after the status is determined for any
1627 // callback opening the entry after this point...
1628 Callback
c(this, aPinned
);
1629 RememberCallback(c
);
1630 // ...and always bypass
1634 bool CacheEntry::Purge(uint32_t aWhat
) {
1635 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat
));
1637 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1640 case PURGE_DATA_ONLY_DISK_BACKED
:
1641 case PURGE_WHOLE_ONLY_DISK_BACKED
:
1642 // This is an in-memory only entry, don't purge it
1644 LOG((" not using disk"));
1650 mozilla::MutexAutoLock
lock(mLock
);
1652 if (mState
== WRITING
|| mState
== LOADING
|| mFrecency
== 0) {
1653 // In-progress (write or load) entries should (at least for consistency
1654 // and from the logical point of view) stay in memory. Zero-frecency
1655 // entries are those which have never been given to any consumer, those
1656 // are actually very fresh and should not go just because frecency had not
1658 LOG((" state=%s, frecency=%1.10f", StateString(mState
), mFrecency
));
1663 if (NS_SUCCEEDED(mFileStatus
) && mFile
->IsWriteInProgress()) {
1664 // The file is used when there are open streams or chunks/metadata still
1665 // waiting for write. In this case, this entry cannot be purged,
1666 // otherwise reopenned entry would may not even find the data on disk -
1667 // CacheFile is not shared and cannot be left orphan when its job is not
1668 // done, hence keep the whole entry.
1669 LOG((" file still under use"));
1674 case PURGE_WHOLE_ONLY_DISK_BACKED
:
1676 if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1677 LOG((" not purging, still referenced"));
1681 CacheStorageService::Self()->UnregisterEntry(this);
1683 // Entry removed it self from control arrays, return true
1687 case PURGE_DATA_ONLY_DISK_BACKED
: {
1688 NS_ENSURE_SUCCESS(mFileStatus
, false);
1690 mFile
->ThrowMemoryCachedData();
1692 // Entry has been left in control arrays, return false (not purged)
1701 void CacheEntry::PurgeAndDoom() {
1702 LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
1704 CacheStorageService::Self()->RemoveEntry(this);
1705 DoomAlreadyRemoved();
1708 void CacheEntry::DoomAlreadyRemoved() {
1709 LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
1711 mozilla::MutexAutoLock
lock(mLock
);
1713 RemoveForcedValidity();
1717 // Pretend pinning is know. This entry is now doomed for good, so don't
1718 // bother with defering doom because of unknown pinning state any more.
1719 mPinningKnown
= true;
1721 // This schedules dooming of the file, dooming is ensured to happen
1722 // sooner than demand to open the same file made after this point
1723 // so that we don't get this file for any newer opened entry(s).
1726 // Must force post here since may be indirectly called from
1727 // InvokeCallbacks of this entry and we don't want reentrancy here.
1728 BackgroundOp(Ops::CALLBACKS
, true);
1729 // Process immediately when on the management thread.
1730 BackgroundOp(Ops::UNREGISTER
);
1733 void CacheEntry::DoomFile() {
1734 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
1736 if (NS_SUCCEEDED(mFileStatus
)) {
1737 if (mHandlesCount
== 0 || (mHandlesCount
== 1 && mWriter
)) {
1738 // We kill the file also when there is just reference from the writer,
1739 // no one else could ever reach the written data. Obvisouly also
1740 // when there is no reference at all (should we ever end up here
1742 // Tell the file to kill the handle, i.e. bypass any I/O operations
1743 // on it except removing the file.
1747 // Always calls the callback asynchronously.
1748 rv
= mFile
->Doom(mDoomCallback
? this : nullptr);
1749 if (NS_SUCCEEDED(rv
)) {
1750 LOG((" file doomed"));
1754 if (NS_ERROR_FILE_NOT_FOUND
== rv
) {
1755 // File is set to be just memory-only, notify the callbacks
1756 // and pretend dooming has succeeded. From point of view of
1757 // the entry it actually did - the data is gone and cannot be
1763 // Always posts to the main thread.
1767 void CacheEntry::RemoveForcedValidity() {
1768 mLock
.AssertCurrentThreadOwns();
1776 nsAutoCString entryKey
;
1777 rv
= HashingKey(entryKey
);
1778 if (NS_WARN_IF(NS_FAILED(rv
))) {
1782 CacheStorageService::Self()->RemoveEntryForceValid(mStorageID
, entryKey
);
1785 void CacheEntry::BackgroundOp(uint32_t aOperations
, bool aForceAsync
)
1786 MOZ_REQUIRES(mLock
) {
1787 mLock
.AssertCurrentThreadOwns();
1789 if (!CacheStorageService::IsOnManagementThread() || aForceAsync
) {
1790 if (mBackgroundOperations
.Set(aOperations
)) {
1791 CacheStorageService::Self()->Dispatch(this);
1794 LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations
));
1799 mozilla::MutexAutoUnlock
unlock(mLock
);
1801 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1803 if (aOperations
& Ops::FRECENCYUPDATE
) {
1807 # define M_LN2 0.69314718055994530942
1810 // Half-life is dynamic, in seconds.
1811 static double half_life
= CacheObserver::HalfLifeSeconds();
1812 // Must convert from seconds to milliseconds since PR_Now() gives usecs.
1813 static double const decay
=
1814 (M_LN2
/ half_life
) / static_cast<double>(PR_USEC_PER_SEC
);
1816 double now_decay
= static_cast<double>(PR_Now()) * decay
;
1818 if (mFrecency
== 0) {
1819 mFrecency
= now_decay
;
1821 // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n +
1822 // 1) but more precise.
1823 mFrecency
= log(exp(mFrecency
- now_decay
) + 1) + now_decay
;
1825 LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this,
1828 // Because CacheFile::Set*() are not thread-safe to use (uses
1829 // WeakReference that is not thread-safe) we must post to the main
1831 NS_DispatchToMainThread(
1832 NewRunnableMethod
<double>("net::CacheEntry::StoreFrecency", this,
1833 &CacheEntry::StoreFrecency
, mFrecency
));
1836 if (aOperations
& Ops::REGISTER
) {
1837 LOG(("CacheEntry REGISTER [this=%p]", this));
1839 CacheStorageService::Self()->RegisterEntry(this);
1842 if (aOperations
& Ops::UNREGISTER
) {
1843 LOG(("CacheEntry UNREGISTER [this=%p]", this));
1845 CacheStorageService::Self()->UnregisterEntry(this);
1849 if (aOperations
& Ops::CALLBACKS
) {
1850 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
1856 void CacheEntry::StoreFrecency(double aFrecency
) {
1857 MOZ_ASSERT(NS_IsMainThread());
1859 if (NS_SUCCEEDED(mFileStatus
)) {
1860 mFile
->SetFrecency(FRECENCY2INT(aFrecency
));
1864 // CacheOutputCloseListener
1866 CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry
* aEntry
)
1867 : Runnable("net::CacheOutputCloseListener"), mEntry(aEntry
) {}
1869 void CacheOutputCloseListener::OnOutputClosed() {
1870 // We need this class and to redispatch since this callback is invoked
1871 // under the file's lock and to do the job we need to enter the entry's
1872 // lock too. That would lead to potential deadlocks.
1873 // This function may be reached while XPCOM is already shutting down,
1874 // and we might be unable to obtain the main thread or the sts. #1826661
1876 if (NS_IsMainThread()) {
1877 // If we're already on the main thread, dispatch to the main thread instead
1878 // of the sts. Always dispatching to the sts can cause problems late in
1879 // shutdown, when threadpools may no longer be available (bug 1806332).
1881 // This may also avoid some unnecessary thread-hops when invoking callbacks,
1882 // which can require that they be called on the main thread.
1884 nsCOMPtr
<nsIThread
> thread
;
1885 nsresult rv
= NS_GetMainThread(getter_AddRefs(thread
));
1886 if (NS_SUCCEEDED(rv
)) {
1887 MOZ_ALWAYS_SUCCEEDS(thread
->Dispatch(do_AddRef(this)));
1892 nsCOMPtr
<nsIEventTarget
> sts
=
1893 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
1894 MOZ_DIAGNOSTIC_ASSERT(sts
);
1896 MOZ_ALWAYS_SUCCEEDS(sts
->Dispatch(do_AddRef(this)));
1900 NS_IMETHODIMP
CacheOutputCloseListener::Run() {
1901 mEntry
->OnOutputClosed();
1907 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf
) {
1910 MutexAutoLock
lock(mLock
);
1911 n
+= mCallbacks
.ShallowSizeOfExcludingThis(mallocSizeOf
);
1913 n
+= mFile
->SizeOfIncludingThis(mallocSizeOf
);
1916 n
+= mURI
.SizeOfExcludingThisIfUnshared(mallocSizeOf
);
1917 n
+= mEnhanceID
.SizeOfExcludingThisIfUnshared(mallocSizeOf
);
1918 n
+= mStorageID
.SizeOfExcludingThisIfUnshared(mallocSizeOf
);
1920 // mDoomCallback is an arbitrary class that is probably reported elsewhere.
1921 // mOutputStream is reported in mFile.
1922 // mWriter is one of many handles we create, but (intentionally) not keep
1923 // any reference to, so those unfortunately cannot be reported. Handles are
1925 // mSecurityInfo doesn't impl nsISizeOf.
1930 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf
) {
1931 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf
);
1934 } // namespace mozilla::net