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
) {
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 LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
520 mLock
.AssertCurrentThreadOwns();
522 // Hold callbacks invocation, AddStorageEntry would invoke from doom
524 mPreventCallbacks
= true;
526 RefPtr
<CacheEntryHandle
> handle
;
527 RefPtr
<CacheEntry
> newEntry
;
530 MOZ_ASSERT(mUseDisk
);
531 // We want to pin even no-store entries (the case we recreate a disk entry
532 // as a memory-only entry.)
536 mozilla::MutexAutoUnlock
unlock(mLock
);
538 // The following call dooms this entry (calls DoomAlreadyRemoved on us)
539 nsresult rv
= CacheStorageService::Self()->AddStorageEntry(
540 GetStorageID(), GetURI(), GetEnhanceID(), mUseDisk
&& !aMemoryOnly
,
541 mSkipSizeCheck
, mPinned
,
542 nsICacheStorage::OPEN_TRUNCATE
, // truncate existing (this one)
543 getter_AddRefs(handle
));
545 if (NS_SUCCEEDED(rv
)) {
546 newEntry
= handle
->Entry();
547 LOG((" exchanged entry %p by entry %p, rv=0x%08" PRIx32
, this,
548 newEntry
.get(), static_cast<uint32_t>(rv
)));
549 newEntry
->AsyncOpen(aCallback
, nsICacheStorage::OPEN_TRUNCATE
);
551 LOG((" exchanged of entry %p failed, rv=0x%08" PRIx32
, this,
552 static_cast<uint32_t>(rv
)));
557 mPreventCallbacks
= false;
559 if (!newEntry
) return nullptr;
561 newEntry
->TransferCallbacks(*this);
564 // Must return a new write handle, since the consumer is expected to
565 // write to this newly recreated entry. The |handle| is only a common
566 // reference counter and doesn't revert entry state back when write
567 // fails and also doesn't update the entry frecency. Not updating
568 // frecency causes entries to not be purged from our memory pools.
569 RefPtr
<CacheEntryHandle
> writeHandle
= newEntry
->NewWriteHandle();
570 return writeHandle
.forget();
573 void CacheEntry::TransferCallbacks(CacheEntry
& aFromEntry
) {
574 mozilla::MutexAutoLock
lock(mLock
);
575 aFromEntry
.mLock
.AssertCurrentThreadOwns();
577 LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]", this, &aFromEntry
));
579 if (!mCallbacks
.Length()) {
580 mCallbacks
.SwapElements(aFromEntry
.mCallbacks
);
582 mCallbacks
.AppendElements(aFromEntry
.mCallbacks
);
585 uint32_t callbacksLength
= mCallbacks
.Length();
586 if (callbacksLength
) {
587 // Carry the entry reference (unfortunately, needs to be done manually...)
588 for (uint32_t i
= 0; i
< callbacksLength
; ++i
) {
589 mCallbacks
[i
].ExchangeEntry(this);
592 BackgroundOp(Ops::CALLBACKS
, true);
596 void CacheEntry::RememberCallback(Callback
& aCallback
) {
597 mLock
.AssertCurrentThreadOwns();
599 LOG(("CacheEntry::RememberCallback [this=%p, cb=%p, state=%s]", this,
600 aCallback
.mCallback
.get(), StateString(mState
)));
602 mCallbacks
.AppendElement(aCallback
);
605 void CacheEntry::InvokeCallbacksLock() {
606 mozilla::MutexAutoLock
lock(mLock
);
610 void CacheEntry::InvokeCallbacks() {
611 mLock
.AssertCurrentThreadOwns();
613 LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
615 // Invoke first all r/w callbacks, then all r/o callbacks.
616 if (InvokeCallbacks(false)) InvokeCallbacks(true);
618 LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
621 bool CacheEntry::InvokeCallbacks(bool aReadOnly
) {
622 mLock
.AssertCurrentThreadOwns();
624 RefPtr
<CacheEntryHandle
> recreatedHandle
;
627 while (i
< mCallbacks
.Length()) {
628 if (mPreventCallbacks
) {
629 LOG((" callbacks prevented!"));
633 if (!mIsDoomed
&& (mState
== WRITING
|| mState
== REVALIDATING
)) {
634 LOG((" entry is being written/revalidated"));
639 if (mCallbacks
[i
].DeferDoom(&recreate
)) {
640 mCallbacks
.RemoveElementAt(i
);
645 LOG((" defer doom marker callback hit positive, recreating"));
646 recreatedHandle
= ReopenTruncated(!mUseDisk
, nullptr);
650 if (mCallbacks
[i
].mReadOnly
!= aReadOnly
) {
651 // Callback is not r/w or r/o, go to another one in line
657 nsresult rv
= mCallbacks
[i
].OnCheckThread(&onCheckThread
);
659 if (NS_SUCCEEDED(rv
) && !onCheckThread
) {
660 // Redispatch to the target thread
661 rv
= mCallbacks
[i
].mTarget
->Dispatch(
662 NewRunnableMethod("net::CacheEntry::InvokeCallbacksLock", this,
663 &CacheEntry::InvokeCallbacksLock
),
664 nsIEventTarget::DISPATCH_NORMAL
);
665 if (NS_SUCCEEDED(rv
)) {
666 LOG((" re-dispatching to target thread"));
671 Callback callback
= mCallbacks
[i
];
672 mCallbacks
.RemoveElementAt(i
);
674 if (NS_SUCCEEDED(rv
) && !InvokeCallback(callback
)) {
675 // Callback didn't fire, put it back and go to another one in line.
676 // Only reason InvokeCallback returns false is that onCacheEntryCheck
677 // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other
678 // readers or potential writers would be unnecessarily kept from being
680 size_t pos
= std::min(mCallbacks
.Length(), static_cast<size_t>(i
));
681 mCallbacks
.InsertElementAt(pos
, callback
);
686 if (recreatedHandle
) {
687 // Must be released outside of the lock, enters InvokeCallback on the new
689 mozilla::MutexAutoUnlock
unlock(mLock
);
690 recreatedHandle
= nullptr;
696 bool CacheEntry::InvokeCallback(Callback
& aCallback
) {
697 mLock
.AssertCurrentThreadOwns();
698 LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", this,
699 StateString(mState
), aCallback
.mCallback
.get()));
701 // When this entry is doomed we want to notify the callback any time
703 // When we are here, the entry must be loaded from disk
704 MOZ_ASSERT(mState
> LOADING
);
706 if (mState
== WRITING
|| mState
== REVALIDATING
) {
707 // Prevent invoking other callbacks since one of them is now writing
708 // or revalidating this entry. No consumers should get this entry
709 // until metadata are filled with values downloaded from the server
710 // or the entry revalidated and output stream has been opened.
711 LOG((" entry is being written/revalidated, callback bypassed"));
715 // mRecheckAfterWrite flag already set means the callback has already passed
716 // the onCacheEntryCheck call. Until the current write is not finished this
717 // callback will be bypassed.
718 if (!aCallback
.mRecheckAfterWrite
) {
719 if (!aCallback
.mReadOnly
) {
720 if (mState
== EMPTY
) {
721 // Advance to writing state, we expect to invoke the callback and let
722 // it fill content of this entry. Must set and check the state here
723 // to prevent more then one
725 LOG((" advancing to WRITING state"));
728 if (!aCallback
.mCallback
) {
729 // We can be given no callback only in case of recreate, it is ok
730 // to advance to WRITING state since the caller of recreate is
731 // expected to write this entry now.
736 if (mState
== READY
) {
737 // Metadata present, validate the entry
738 uint32_t checkResult
;
740 // mayhemer: TODO check and solve any potential races of concurent
742 mozilla::MutexAutoUnlock
unlock(mLock
);
744 RefPtr
<CacheEntryHandle
> handle
= NewHandle();
747 aCallback
.mCallback
->OnCacheEntryCheck(handle
, &checkResult
);
748 LOG((" OnCacheEntryCheck: rv=0x%08" PRIx32
", result=%" PRId32
,
749 static_cast<uint32_t>(rv
), static_cast<uint32_t>(checkResult
)));
751 if (NS_FAILED(rv
)) checkResult
= ENTRY_NOT_WANTED
;
754 aCallback
.mRevalidating
= checkResult
== ENTRY_NEEDS_REVALIDATION
;
756 switch (checkResult
) {
758 // Nothing more to do here, the consumer is responsible to handle
759 // the result of OnCacheEntryCheck it self.
760 // Proceed to callback...
763 case RECHECK_AFTER_WRITE_FINISHED
:
765 (" consumer will check on the entry again after write is "
767 // The consumer wants the entry to complete first.
768 aCallback
.mRecheckAfterWrite
= true;
771 case ENTRY_NEEDS_REVALIDATION
:
772 LOG((" will be holding callbacks until entry is revalidated"));
773 // State is READY now and from that state entry cannot transit to
774 // any other state then REVALIDATING for which cocurrency is not an
775 // issue. Potentially no need to lock here.
776 mState
= REVALIDATING
;
779 case ENTRY_NOT_WANTED
:
780 LOG((" consumer not interested in the entry"));
781 // Do not give this entry to the consumer, it is not interested in
783 aCallback
.mNotWanted
= true;
790 if (aCallback
.mCallback
) {
791 if (!mIsDoomed
&& aCallback
.mRecheckAfterWrite
) {
792 // If we don't have data and the callback wants a complete entry,
794 bool bypass
= !mHasData
;
795 if (!bypass
&& NS_SUCCEEDED(mFileStatus
)) {
797 bypass
= !mFile
->DataSize(&_unused
);
801 LOG((" bypassing, entry data still being written"));
805 // Entry is complete now, do the check+avail call again
806 aCallback
.mRecheckAfterWrite
= false;
807 return InvokeCallback(aCallback
);
810 mozilla::MutexAutoUnlock
unlock(mLock
);
811 InvokeAvailableCallback(aCallback
);
817 void CacheEntry::InvokeAvailableCallback(Callback
const& aCallback
) {
821 mozilla::MutexAutoLock
lock(mLock
);
824 ("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, "
827 this, StateString(mState
), aCallback
.mCallback
.get(),
828 aCallback
.mReadOnly
, aCallback
.mNotWanted
));
830 // When we are here, the entry must be loaded from disk
831 MOZ_ASSERT(state
> LOADING
|| mIsDoomed
);
835 rv
= aCallback
.OnAvailThread(&onAvailThread
);
837 LOG((" target thread dead?"));
841 if (!onAvailThread
) {
842 // Dispatch to the right thread
843 RefPtr
<AvailableCallbackRunnable
> event
=
844 new AvailableCallbackRunnable(this, aCallback
);
846 rv
= aCallback
.mTarget
->Dispatch(event
, nsIEventTarget::DISPATCH_NORMAL
);
847 LOG((" redispatched, (rv = 0x%08" PRIx32
")", static_cast<uint32_t>(rv
)));
851 if (mIsDoomed
|| aCallback
.mNotWanted
) {
853 (" doomed or not wanted, notifying OCEA with "
854 "NS_ERROR_CACHE_KEY_NOT_FOUND"));
855 aCallback
.mCallback
->OnCacheEntryAvailable(nullptr, false,
856 NS_ERROR_CACHE_KEY_NOT_FOUND
);
860 if (state
== READY
) {
861 LOG((" ready/has-meta, notifying OCEA with entry and NS_OK"));
863 if (!aCallback
.mSecret
) {
864 mozilla::MutexAutoLock
lock(mLock
);
865 BackgroundOp(Ops::FRECENCYUPDATE
);
868 OnFetched(aCallback
);
870 RefPtr
<CacheEntryHandle
> handle
= NewHandle();
871 aCallback
.mCallback
->OnCacheEntryAvailable(handle
, false, NS_OK
);
875 // R/O callbacks may do revalidation, let them fall through
876 if (aCallback
.mReadOnly
&& !aCallback
.mRevalidating
) {
878 (" r/o and not ready, notifying OCEA with "
879 "NS_ERROR_CACHE_KEY_NOT_FOUND"));
880 aCallback
.mCallback
->OnCacheEntryAvailable(nullptr, false,
881 NS_ERROR_CACHE_KEY_NOT_FOUND
);
885 // This is a new or potentially non-valid entry and needs to be fetched first.
886 // The CacheEntryHandle blocks other consumers until the channel
887 // either releases the entry or marks metadata as filled or whole entry valid,
888 // i.e. until MetaDataReady() or SetValid() on the entry is called
891 // Consumer will be responsible to fill or validate the entry metadata and
894 OnFetched(aCallback
);
896 RefPtr
<CacheEntryHandle
> handle
= NewWriteHandle();
897 rv
= aCallback
.mCallback
->OnCacheEntryAvailable(handle
, state
== WRITING
,
901 LOG((" writing/revalidating failed (0x%08" PRIx32
")",
902 static_cast<uint32_t>(rv
)));
904 // Consumer given a new entry failed to take care of the entry.
905 OnHandleClosed(handle
);
909 LOG((" writing/revalidating"));
912 void CacheEntry::OnFetched(Callback
const& aCallback
) {
913 if (NS_SUCCEEDED(mFileStatus
) && !aCallback
.mSecret
) {
914 // Let the last-fetched and fetch-count properties be updated.
919 CacheEntryHandle
* CacheEntry::NewHandle() { return new CacheEntryHandle(this); }
921 CacheEntryHandle
* CacheEntry::NewWriteHandle() {
922 mozilla::MutexAutoLock
lock(mLock
);
924 // Ignore the OPEN_SECRETLY flag on purpose here, which should actually be
925 // used only along with OPEN_READONLY, but there is no need to enforce that.
926 BackgroundOp(Ops::FRECENCYUPDATE
);
928 return (mWriter
= NewHandle());
931 void CacheEntry::OnHandleClosed(CacheEntryHandle
const* aHandle
) {
932 mozilla::MutexAutoLock
lock(mLock
);
933 LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this,
934 StateString(mState
), aHandle
));
936 if (mIsDoomed
&& NS_SUCCEEDED(mFileStatus
) &&
937 // Note: mHandlesCount is dropped before this method is called
938 (mHandlesCount
== 0 ||
939 (mHandlesCount
== 1 && mWriter
&& mWriter
!= aHandle
))) {
940 // This entry is no longer referenced from outside and is doomed.
941 // We can do this also when there is just reference from the writer,
942 // no one else could ever reach the written data.
943 // Tell the file to kill the handle, i.e. bypass any I/O operations
944 // on it except removing the file.
948 if (mWriter
!= aHandle
) {
949 LOG((" not the writer"));
954 LOG((" abandoning phantom output stream"));
955 // No one took our internal output stream, so there are no data
956 // and output stream has to be open symultaneously with input stream
957 // on this entry again.
959 // This asynchronously ends up invoking callbacks on this entry
960 // through OnOutputClosed() call.
961 mOutputStream
->Close();
962 mOutputStream
= nullptr;
964 // We must always redispatch, otherwise there is a risk of stack
965 // overflow. This code can recurse deeply. It won't execute sooner
966 // than we release mLock.
967 BackgroundOp(Ops::CALLBACKS
, true);
972 if (mState
== WRITING
) {
973 LOG((" reverting to state EMPTY - write failed"));
975 } else if (mState
== REVALIDATING
) {
976 LOG((" reverting to state READY - reval failed"));
980 if (mState
== READY
&& !mHasData
) {
981 // We may get to this state when following steps happen:
982 // 1. a new entry is given to a consumer
983 // 2. the consumer calls MetaDataReady(), we transit to READY
984 // 3. abandons the entry w/o opening the output stream, mHasData left false
986 // In this case any following consumer will get a ready entry (with
987 // metadata) but in state like the entry data write was still happening (was
988 // in progress) and will indefinitely wait for the entry data or even the
989 // entry itself when RECHECK_AFTER_WRITE is returned from onCacheEntryCheck.
991 (" we are in READY state, pretend we have data regardless it"
992 " has actully been never touched"));
997 void CacheEntry::OnOutputClosed() {
998 // Called when the file's output stream is closed. Invoke any callbacks
999 // waiting for complete entry.
1001 mozilla::MutexAutoLock
lock(mLock
);
1005 bool CacheEntry::IsReferenced() const {
1006 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
1008 // Increasing this counter from 0 to non-null and this check both happen only
1009 // under the service lock.
1010 return mHandlesCount
> 0;
1013 bool CacheEntry::IsFileDoomed() {
1014 if (NS_SUCCEEDED(mFileStatus
)) {
1015 return mFile
->IsDoomed();
1021 uint32_t CacheEntry::GetMetadataMemoryConsumption() {
1022 NS_ENSURE_SUCCESS(mFileStatus
, 0);
1025 if (NS_FAILED(mFile
->ElementsSize(&size
))) return 0;
1032 nsresult
CacheEntry::GetPersistent(bool* aPersistToDisk
) {
1033 // No need to sync when only reading.
1034 // When consumer needs to be consistent with state of the memory storage
1035 // entries table, then let it use GetUseDisk getter that must be called under
1036 // the service lock.
1037 *aPersistToDisk
= mUseDisk
;
1041 nsresult
CacheEntry::GetKey(nsACString
& aKey
) {
1046 nsresult
CacheEntry::GetCacheEntryId(uint64_t* aCacheEntryId
) {
1047 *aCacheEntryId
= mCacheEntryId
;
1051 nsresult
CacheEntry::GetFetchCount(uint32_t* aFetchCount
) {
1052 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1054 return mFile
->GetFetchCount(aFetchCount
);
1057 nsresult
CacheEntry::GetLastFetched(uint32_t* aLastFetched
) {
1058 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1060 return mFile
->GetLastFetched(aLastFetched
);
1063 nsresult
CacheEntry::GetLastModified(uint32_t* aLastModified
) {
1064 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1066 return mFile
->GetLastModified(aLastModified
);
1069 nsresult
CacheEntry::GetExpirationTime(uint32_t* aExpirationTime
) {
1070 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1072 return mFile
->GetExpirationTime(aExpirationTime
);
1075 nsresult
CacheEntry::GetOnStartTime(uint64_t* aTime
) {
1076 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1077 return mFile
->GetOnStartTime(aTime
);
1080 nsresult
CacheEntry::GetOnStopTime(uint64_t* aTime
) {
1081 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1082 return mFile
->GetOnStopTime(aTime
);
1085 nsresult
CacheEntry::SetNetworkTimes(uint64_t aOnStartTime
,
1086 uint64_t aOnStopTime
) {
1087 if (NS_SUCCEEDED(mFileStatus
)) {
1088 return mFile
->SetNetworkTimes(aOnStartTime
, aOnStopTime
);
1090 return NS_ERROR_NOT_AVAILABLE
;
1093 nsresult
CacheEntry::SetContentType(uint8_t aContentType
) {
1094 NS_ENSURE_ARG_MAX(aContentType
, nsICacheEntry::CONTENT_TYPE_LAST
- 1);
1096 if (NS_SUCCEEDED(mFileStatus
)) {
1097 return mFile
->SetContentType(aContentType
);
1099 return NS_ERROR_NOT_AVAILABLE
;
1102 nsresult
CacheEntry::GetIsForcedValid(bool* aIsForcedValid
) {
1103 NS_ENSURE_ARG(aIsForcedValid
);
1107 mozilla::MutexAutoLock
lock(mLock
);
1108 MOZ_ASSERT(mState
> LOADING
);
1112 *aIsForcedValid
= true;
1117 nsresult rv
= HashingKey(key
);
1118 if (NS_FAILED(rv
)) {
1123 CacheStorageService::Self()->IsForcedValidEntry(mStorageID
, key
);
1124 LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this,
1130 nsresult
CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture
) {
1131 LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this,
1132 aSecondsToTheFuture
));
1135 nsresult rv
= HashingKey(key
);
1136 if (NS_FAILED(rv
)) {
1140 CacheStorageService::Self()->ForceEntryValidFor(mStorageID
, key
,
1141 aSecondsToTheFuture
);
1146 nsresult
CacheEntry::MarkForcedValidUse() {
1147 LOG(("CacheEntry::MarkForcedValidUse [this=%p, ]", this));
1150 nsresult rv
= HashingKey(key
);
1151 if (NS_FAILED(rv
)) {
1155 CacheStorageService::Self()->MarkForcedValidEntryUse(mStorageID
, key
);
1159 nsresult
CacheEntry::SetExpirationTime(uint32_t aExpirationTime
) {
1160 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1162 nsresult rv
= mFile
->SetExpirationTime(aExpirationTime
);
1163 NS_ENSURE_SUCCESS(rv
, rv
);
1165 // Aligned assignment, thus atomic.
1166 mSortingExpirationTime
= aExpirationTime
;
1170 nsresult
CacheEntry::OpenInputStream(int64_t offset
, nsIInputStream
** _retval
) {
1171 LOG(("CacheEntry::OpenInputStream [this=%p]", this));
1172 return OpenInputStreamInternal(offset
, nullptr, _retval
);
1175 nsresult
CacheEntry::OpenAlternativeInputStream(const nsACString
& type
,
1176 nsIInputStream
** _retval
) {
1177 LOG(("CacheEntry::OpenAlternativeInputStream [this=%p, type=%s]", this,
1178 PromiseFlatCString(type
).get()));
1179 return OpenInputStreamInternal(0, PromiseFlatCString(type
).get(), _retval
);
1182 nsresult
CacheEntry::OpenInputStreamInternal(int64_t offset
,
1183 const char* aAltDataType
,
1184 nsIInputStream
** _retval
) {
1185 LOG(("CacheEntry::OpenInputStreamInternal [this=%p]", this));
1187 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1191 RefPtr
<CacheEntryHandle
> selfHandle
= NewHandle();
1193 nsCOMPtr
<nsIInputStream
> stream
;
1195 rv
= mFile
->OpenAlternativeInputStream(selfHandle
, aAltDataType
,
1196 getter_AddRefs(stream
));
1197 if (NS_FAILED(rv
)) {
1198 // Failure of this method may be legal when the alternative data requested
1199 // is not avaialble or of a different type. Console error logs are
1200 // ensured by CacheFile::OpenAlternativeInputStream.
1204 rv
= mFile
->OpenInputStream(selfHandle
, getter_AddRefs(stream
));
1205 NS_ENSURE_SUCCESS(rv
, rv
);
1208 nsCOMPtr
<nsISeekableStream
> seekable
= do_QueryInterface(stream
, &rv
);
1209 NS_ENSURE_SUCCESS(rv
, rv
);
1211 rv
= seekable
->Seek(nsISeekableStream::NS_SEEK_SET
, offset
);
1212 NS_ENSURE_SUCCESS(rv
, rv
);
1214 mozilla::MutexAutoLock
lock(mLock
);
1217 // So far output stream on this new entry not opened, do it now.
1218 LOG((" creating phantom output stream"));
1219 rv
= OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream
));
1220 NS_ENSURE_SUCCESS(rv
, rv
);
1223 stream
.forget(_retval
);
1227 nsresult
CacheEntry::OpenOutputStream(int64_t offset
, int64_t predictedSize
,
1228 nsIOutputStream
** _retval
) {
1229 LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
1233 mozilla::MutexAutoLock
lock(mLock
);
1235 MOZ_ASSERT(mState
> EMPTY
);
1237 if (mFile
->EntryWouldExceedLimit(0, predictedSize
, false)) {
1238 LOG((" entry would exceed size limit"));
1239 return NS_ERROR_FILE_TOO_BIG
;
1242 if (mOutputStream
&& !mIsDoomed
) {
1243 LOG((" giving phantom output stream"));
1244 mOutputStream
.forget(_retval
);
1246 rv
= OpenOutputStreamInternal(offset
, _retval
);
1247 if (NS_FAILED(rv
)) return rv
;
1250 // Entry considered ready when writer opens output stream.
1251 if (mState
< READY
) mState
= READY
;
1253 // Invoke any pending readers now.
1259 nsresult
CacheEntry::OpenAlternativeOutputStream(
1260 const nsACString
& type
, int64_t predictedSize
,
1261 nsIAsyncOutputStream
** _retval
) {
1262 LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
1263 PromiseFlatCString(type
).get()));
1267 if (type
.IsEmpty()) {
1268 // The empty string is reserved to mean no alt-data available.
1269 return NS_ERROR_INVALID_ARG
;
1272 mozilla::MutexAutoLock
lock(mLock
);
1274 if (!mHasData
|| mState
< READY
|| mOutputStream
|| mIsDoomed
) {
1275 LOG((" entry not in state to write alt-data"));
1276 return NS_ERROR_NOT_AVAILABLE
;
1279 if (mFile
->EntryWouldExceedLimit(0, predictedSize
, true)) {
1280 LOG((" entry would exceed size limit"));
1281 return NS_ERROR_FILE_TOO_BIG
;
1284 nsCOMPtr
<nsIAsyncOutputStream
> stream
;
1285 rv
= mFile
->OpenAlternativeOutputStream(
1286 nullptr, PromiseFlatCString(type
).get(), getter_AddRefs(stream
));
1287 NS_ENSURE_SUCCESS(rv
, rv
);
1289 stream
.swap(*_retval
);
1293 nsresult
CacheEntry::OpenOutputStreamInternal(int64_t offset
,
1294 nsIOutputStream
** _retval
) {
1295 LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
1297 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1299 mLock
.AssertCurrentThreadOwns();
1302 LOG((" doomed..."));
1303 return NS_ERROR_NOT_AVAILABLE
;
1306 MOZ_ASSERT(mState
> LOADING
);
1310 // No need to sync on mUseDisk here, we don't need to be consistent
1311 // with content of the memory storage entries hash table.
1313 rv
= mFile
->SetMemoryOnly();
1314 NS_ENSURE_SUCCESS(rv
, rv
);
1317 RefPtr
<CacheOutputCloseListener
> listener
=
1318 new CacheOutputCloseListener(this);
1320 nsCOMPtr
<nsIOutputStream
> stream
;
1321 rv
= mFile
->OpenOutputStream(listener
, getter_AddRefs(stream
));
1322 NS_ENSURE_SUCCESS(rv
, rv
);
1324 nsCOMPtr
<nsISeekableStream
> seekable
= do_QueryInterface(stream
, &rv
);
1325 NS_ENSURE_SUCCESS(rv
, rv
);
1327 rv
= seekable
->Seek(nsISeekableStream::NS_SEEK_SET
, offset
);
1328 NS_ENSURE_SUCCESS(rv
, rv
);
1330 // Prevent opening output stream again.
1333 stream
.swap(*_retval
);
1337 nsresult
CacheEntry::GetSecurityInfo(nsITransportSecurityInfo
** aSecurityInfo
) {
1339 mozilla::MutexAutoLock
lock(mLock
);
1340 if (mSecurityInfoLoaded
) {
1341 *aSecurityInfo
= do_AddRef(mSecurityInfo
).take();
1346 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1349 nsresult rv
= mFile
->GetElement("security-info", getter_Copies(info
));
1350 NS_ENSURE_SUCCESS(rv
, rv
);
1351 nsCOMPtr
<nsITransportSecurityInfo
> securityInfo
;
1352 if (!info
.IsVoid()) {
1353 rv
= mozilla::psm::TransportSecurityInfo::Read(
1354 info
, getter_AddRefs(securityInfo
));
1355 NS_ENSURE_SUCCESS(rv
, rv
);
1357 if (!securityInfo
) {
1358 return NS_ERROR_NOT_AVAILABLE
;
1362 mozilla::MutexAutoLock
lock(mLock
);
1364 mSecurityInfo
.swap(securityInfo
);
1365 mSecurityInfoLoaded
= true;
1367 *aSecurityInfo
= do_AddRef(mSecurityInfo
).take();
1373 nsresult
CacheEntry::SetSecurityInfo(nsITransportSecurityInfo
* aSecurityInfo
) {
1376 NS_ENSURE_SUCCESS(mFileStatus
, mFileStatus
);
1379 mozilla::MutexAutoLock
lock(mLock
);
1381 mSecurityInfo
= aSecurityInfo
;
1382 mSecurityInfoLoaded
= true;
1386 if (aSecurityInfo
) {
1387 rv
= aSecurityInfo
->ToString(info
);
1388 NS_ENSURE_SUCCESS(rv
, rv
);
1391 rv
= mFile
->SetElement("security-info", info
.Length() ? info
.get() : nullptr);
1392 NS_ENSURE_SUCCESS(rv
, rv
);
1397 nsresult
CacheEntry::GetStorageDataSize(uint32_t* aStorageDataSize
) {
1398 NS_ENSURE_ARG(aStorageDataSize
);
1401 nsresult rv
= GetDataSize(&dataSize
);
1402 if (NS_FAILED(rv
)) return rv
;
1404 *aStorageDataSize
= (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize
);
1409 nsresult
CacheEntry::AsyncDoom(nsICacheEntryDoomCallback
* aCallback
) {
1410 LOG(("CacheEntry::AsyncDoom [this=%p]", this));
1413 mozilla::MutexAutoLock
lock(mLock
);
1415 if (mIsDoomed
|| mDoomCallback
) {
1416 return NS_ERROR_IN_PROGRESS
; // to aggregate have DOOMING state
1419 RemoveForcedValidity();
1422 mDoomCallback
= aCallback
;
1425 // This immediately removes the entry from the master hashtable and also
1426 // immediately dooms the file. This way we make sure that any consumer
1427 // after this point asking for the same entry won't get
1429 // b) a new entry with the same file
1435 nsresult
CacheEntry::GetMetaDataElement(const char* aKey
, char** aRetval
) {
1436 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1438 return mFile
->GetElement(aKey
, aRetval
);
1441 nsresult
CacheEntry::SetMetaDataElement(const char* aKey
, const char* aValue
) {
1442 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1444 return mFile
->SetElement(aKey
, aValue
);
1447 nsresult
CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor
* aVisitor
) {
1448 NS_ENSURE_SUCCESS(mFileStatus
, NS_ERROR_NOT_AVAILABLE
);
1450 return mFile
->VisitMetaData(aVisitor
);
1453 nsresult
CacheEntry::MetaDataReady() {
1454 mozilla::MutexAutoLock
lock(mLock
);
1456 LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this,
1457 StateString(mState
)));
1459 MOZ_ASSERT(mState
> EMPTY
);
1461 if (mState
== WRITING
) mState
= READY
;
1468 nsresult
CacheEntry::SetValid() {
1469 nsCOMPtr
<nsIOutputStream
> outputStream
;
1472 mozilla::MutexAutoLock
lock(mLock
);
1473 LOG(("CacheEntry::SetValid [this=%p, state=%s]", this,
1474 StateString(mState
)));
1476 MOZ_ASSERT(mState
> EMPTY
);
1483 outputStream
.swap(mOutputStream
);
1487 LOG((" abandoning phantom output stream"));
1488 outputStream
->Close();
1494 nsresult
CacheEntry::Recreate(bool aMemoryOnly
, nsICacheEntry
** _retval
) {
1495 mozilla::MutexAutoLock
lock(mLock
);
1496 LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState
)));
1498 RefPtr
<CacheEntryHandle
> handle
= ReopenTruncated(aMemoryOnly
, nullptr);
1500 handle
.forget(_retval
);
1504 BackgroundOp(Ops::CALLBACKS
, true);
1505 return NS_ERROR_NOT_AVAILABLE
;
1508 nsresult
CacheEntry::GetDataSize(int64_t* aDataSize
) {
1509 LOG(("CacheEntry::GetDataSize [this=%p]", this));
1513 mozilla::MutexAutoLock
lock(mLock
);
1516 LOG((" write in progress (no data)"));
1517 return NS_ERROR_IN_PROGRESS
;
1521 NS_ENSURE_SUCCESS(mFileStatus
, mFileStatus
);
1523 // mayhemer: TODO Problem with compression?
1524 if (!mFile
->DataSize(aDataSize
)) {
1525 LOG((" write in progress (stream active)"));
1526 return NS_ERROR_IN_PROGRESS
;
1529 LOG((" size=%" PRId64
, *aDataSize
));
1533 nsresult
CacheEntry::GetAltDataSize(int64_t* aDataSize
) {
1534 LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
1535 if (NS_FAILED(mFileStatus
)) {
1538 return mFile
->GetAltDataSize(aDataSize
);
1541 nsresult
CacheEntry::GetAltDataType(nsACString
& aType
) {
1542 LOG(("CacheEntry::GetAltDataType [this=%p]", this));
1543 if (NS_FAILED(mFileStatus
)) {
1546 return mFile
->GetAltDataType(aType
);
1549 nsresult
CacheEntry::MarkValid() {
1550 // NOT IMPLEMENTED ACTUALLY
1554 nsresult
CacheEntry::MaybeMarkValid() {
1555 // NOT IMPLEMENTED ACTUALLY
1559 nsresult
CacheEntry::HasWriteAccess(bool aWriteAllowed
, bool* aWriteAccess
) {
1560 *aWriteAccess
= aWriteAllowed
;
1564 nsresult
CacheEntry::Close() {
1565 // NOT IMPLEMENTED ACTUALLY
1569 nsresult
CacheEntry::GetDiskStorageSizeInKB(uint32_t* aDiskStorageSize
) {
1570 if (NS_FAILED(mFileStatus
)) {
1571 return NS_ERROR_NOT_AVAILABLE
;
1574 return mFile
->GetDiskStorageSizeInKB(aDiskStorageSize
);
1577 nsresult
CacheEntry::GetLoadContextInfo(nsILoadContextInfo
** aInfo
) {
1578 nsCOMPtr
<nsILoadContextInfo
> info
= CacheFileUtils::ParseKey(mStorageID
);
1580 return NS_ERROR_FAILURE
;
1590 NS_IMETHODIMP
CacheEntry::Run() {
1591 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1593 mozilla::MutexAutoLock
lock(mLock
);
1595 BackgroundOp(mBackgroundOperations
.Grab());
1599 // Management methods
1601 double CacheEntry::GetFrecency() const {
1602 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1606 uint32_t CacheEntry::GetExpirationTime() const {
1607 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1608 return mSortingExpirationTime
;
1611 bool CacheEntry::IsRegistered() const {
1612 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1613 return mRegistration
== REGISTERED
;
1616 bool CacheEntry::CanRegister() const {
1617 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1618 return mRegistration
== NEVERREGISTERED
;
1621 void CacheEntry::SetRegistered(bool aRegistered
) {
1622 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1625 MOZ_ASSERT(mRegistration
== NEVERREGISTERED
);
1626 mRegistration
= REGISTERED
;
1628 MOZ_ASSERT(mRegistration
== REGISTERED
);
1629 mRegistration
= DEREGISTERED
;
1633 bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned
) {
1634 LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this));
1636 mozilla::MutexAutoLock
lock(mLock
);
1637 if (mPinningKnown
) {
1638 LOG((" pinned=%d, caller=%d", (bool)mPinned
, aPinned
));
1639 // Bypass when the pin status of this entry doesn't match the pin status
1640 // caller wants to remove
1641 return mPinned
!= aPinned
;
1644 LOG((" pinning unknown, caller=%d", aPinned
));
1645 // Oterwise, remember to doom after the status is determined for any
1646 // callback opening the entry after this point...
1647 Callback
c(this, aPinned
);
1648 RememberCallback(c
);
1649 // ...and always bypass
1653 bool CacheEntry::Purge(uint32_t aWhat
) {
1654 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat
));
1656 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1659 case PURGE_DATA_ONLY_DISK_BACKED
:
1660 case PURGE_WHOLE_ONLY_DISK_BACKED
:
1661 // This is an in-memory only entry, don't purge it
1663 LOG((" not using disk"));
1669 mozilla::MutexAutoLock
lock(mLock
);
1671 if (mState
== WRITING
|| mState
== LOADING
|| mFrecency
== 0) {
1672 // In-progress (write or load) entries should (at least for consistency
1673 // and from the logical point of view) stay in memory. Zero-frecency
1674 // entries are those which have never been given to any consumer, those
1675 // are actually very fresh and should not go just because frecency had not
1677 LOG((" state=%s, frecency=%1.10f", StateString(mState
), mFrecency
));
1682 if (NS_SUCCEEDED(mFileStatus
) && mFile
->IsWriteInProgress()) {
1683 // The file is used when there are open streams or chunks/metadata still
1684 // waiting for write. In this case, this entry cannot be purged,
1685 // otherwise reopenned entry would may not even find the data on disk -
1686 // CacheFile is not shared and cannot be left orphan when its job is not
1687 // done, hence keep the whole entry.
1688 LOG((" file still under use"));
1693 case PURGE_WHOLE_ONLY_DISK_BACKED
:
1695 if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1696 LOG((" not purging, still referenced"));
1700 CacheStorageService::Self()->UnregisterEntry(this);
1702 // Entry removed it self from control arrays, return true
1706 case PURGE_DATA_ONLY_DISK_BACKED
: {
1707 NS_ENSURE_SUCCESS(mFileStatus
, false);
1709 mFile
->ThrowMemoryCachedData();
1711 // Entry has been left in control arrays, return false (not purged)
1720 void CacheEntry::PurgeAndDoom() {
1721 LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
1723 CacheStorageService::Self()->RemoveEntry(this);
1724 DoomAlreadyRemoved();
1727 void CacheEntry::DoomAlreadyRemoved() {
1728 LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
1730 mozilla::MutexAutoLock
lock(mLock
);
1732 RemoveForcedValidity();
1736 // Pretend pinning is know. This entry is now doomed for good, so don't
1737 // bother with defering doom because of unknown pinning state any more.
1738 mPinningKnown
= true;
1740 // This schedules dooming of the file, dooming is ensured to happen
1741 // sooner than demand to open the same file made after this point
1742 // so that we don't get this file for any newer opened entry(s).
1745 // Must force post here since may be indirectly called from
1746 // InvokeCallbacks of this entry and we don't want reentrancy here.
1747 BackgroundOp(Ops::CALLBACKS
, true);
1748 // Process immediately when on the management thread.
1749 BackgroundOp(Ops::UNREGISTER
);
1752 void CacheEntry::DoomFile() {
1753 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
1755 if (NS_SUCCEEDED(mFileStatus
)) {
1756 if (mHandlesCount
== 0 || (mHandlesCount
== 1 && mWriter
)) {
1757 // We kill the file also when there is just reference from the writer,
1758 // no one else could ever reach the written data. Obvisouly also
1759 // when there is no reference at all (should we ever end up here
1761 // Tell the file to kill the handle, i.e. bypass any I/O operations
1762 // on it except removing the file.
1766 // Always calls the callback asynchronously.
1767 rv
= mFile
->Doom(mDoomCallback
? this : nullptr);
1768 if (NS_SUCCEEDED(rv
)) {
1769 LOG((" file doomed"));
1773 if (NS_ERROR_FILE_NOT_FOUND
== rv
) {
1774 // File is set to be just memory-only, notify the callbacks
1775 // and pretend dooming has succeeded. From point of view of
1776 // the entry it actually did - the data is gone and cannot be
1782 // Always posts to the main thread.
1786 void CacheEntry::RemoveForcedValidity() {
1787 mLock
.AssertCurrentThreadOwns();
1795 nsAutoCString entryKey
;
1796 rv
= HashingKey(entryKey
);
1797 if (NS_WARN_IF(NS_FAILED(rv
))) {
1801 CacheStorageService::Self()->RemoveEntryForceValid(mStorageID
, entryKey
);
1804 void CacheEntry::BackgroundOp(uint32_t aOperations
, bool aForceAsync
) {
1805 mLock
.AssertCurrentThreadOwns();
1807 if (!CacheStorageService::IsOnManagementThread() || aForceAsync
) {
1808 if (mBackgroundOperations
.Set(aOperations
)) {
1809 CacheStorageService::Self()->Dispatch(this);
1812 LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations
));
1817 mozilla::MutexAutoUnlock
unlock(mLock
);
1819 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1821 if (aOperations
& Ops::FRECENCYUPDATE
) {
1825 # define M_LN2 0.69314718055994530942
1828 // Half-life is dynamic, in seconds.
1829 static double half_life
= CacheObserver::HalfLifeSeconds();
1830 // Must convert from seconds to milliseconds since PR_Now() gives usecs.
1831 static double const decay
=
1832 (M_LN2
/ half_life
) / static_cast<double>(PR_USEC_PER_SEC
);
1834 double now_decay
= static_cast<double>(PR_Now()) * decay
;
1836 if (mFrecency
== 0) {
1837 mFrecency
= now_decay
;
1839 // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n +
1840 // 1) but more precise.
1841 mFrecency
= log(exp(mFrecency
- now_decay
) + 1) + now_decay
;
1843 LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this,
1846 // Because CacheFile::Set*() are not thread-safe to use (uses
1847 // WeakReference that is not thread-safe) we must post to the main
1849 NS_DispatchToMainThread(
1850 NewRunnableMethod
<double>("net::CacheEntry::StoreFrecency", this,
1851 &CacheEntry::StoreFrecency
, mFrecency
));
1854 if (aOperations
& Ops::REGISTER
) {
1855 LOG(("CacheEntry REGISTER [this=%p]", this));
1857 CacheStorageService::Self()->RegisterEntry(this);
1860 if (aOperations
& Ops::UNREGISTER
) {
1861 LOG(("CacheEntry UNREGISTER [this=%p]", this));
1863 CacheStorageService::Self()->UnregisterEntry(this);
1867 if (aOperations
& Ops::CALLBACKS
) {
1868 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
1874 void CacheEntry::StoreFrecency(double aFrecency
) {
1875 MOZ_ASSERT(NS_IsMainThread());
1877 if (NS_SUCCEEDED(mFileStatus
)) {
1878 mFile
->SetFrecency(FRECENCY2INT(aFrecency
));
1882 // CacheOutputCloseListener
1884 CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry
* aEntry
)
1885 : Runnable("net::CacheOutputCloseListener"), mEntry(aEntry
) {}
1887 void CacheOutputCloseListener::OnOutputClosed() {
1888 // We need this class and to redispatch since this callback is invoked
1889 // under the file's lock and to do the job we need to enter the entry's
1890 // lock too. That would lead to potential deadlocks.
1891 // This function may be reached while XPCOM is already shutting down,
1892 // and we might be unable to obtain the main thread or the sts. #1826661
1894 if (NS_IsMainThread()) {
1895 // If we're already on the main thread, dispatch to the main thread instead
1896 // of the sts. Always dispatching to the sts can cause problems late in
1897 // shutdown, when threadpools may no longer be available (bug 1806332).
1899 // This may also avoid some unnecessary thread-hops when invoking callbacks,
1900 // which can require that they be called on the main thread.
1902 nsCOMPtr
<nsIThread
> thread
;
1903 nsresult rv
= NS_GetMainThread(getter_AddRefs(thread
));
1904 if (NS_SUCCEEDED(rv
)) {
1905 MOZ_ALWAYS_SUCCEEDS(thread
->Dispatch(do_AddRef(this)));
1910 nsCOMPtr
<nsIEventTarget
> sts
=
1911 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
1912 MOZ_DIAGNOSTIC_ASSERT(sts
);
1914 MOZ_ALWAYS_SUCCEEDS(sts
->Dispatch(do_AddRef(this)));
1918 NS_IMETHODIMP
CacheOutputCloseListener::Run() {
1919 mEntry
->OnOutputClosed();
1925 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf
) {
1928 MutexAutoLock
lock(mLock
);
1929 n
+= mCallbacks
.ShallowSizeOfExcludingThis(mallocSizeOf
);
1931 n
+= mFile
->SizeOfIncludingThis(mallocSizeOf
);
1934 n
+= mURI
.SizeOfExcludingThisIfUnshared(mallocSizeOf
);
1935 n
+= mEnhanceID
.SizeOfExcludingThisIfUnshared(mallocSizeOf
);
1936 n
+= mStorageID
.SizeOfExcludingThisIfUnshared(mallocSizeOf
);
1938 // mDoomCallback is an arbitrary class that is probably reported elsewhere.
1939 // mOutputStream is reported in mFile.
1940 // mWriter is one of many handles we create, but (intentionally) not keep
1941 // any reference to, so those unfortunately cannot be reported. Handles are
1943 // mSecurityInfo doesn't impl nsISizeOf.
1948 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf
) {
1949 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf
);
1952 } // namespace mozilla::net