Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / netwerk / cache2 / CacheEntry.cpp
blob9a4c40f049ec88a288c4c5c62b05eb4b6cb00d3e
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include <algorithm>
6 #include <math.h>
8 #include "CacheEntry.h"
10 #include "CacheFileUtils.h"
11 #include "CacheIndex.h"
12 #include "CacheLog.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"
26 #include "nsIURI.h"
27 #include "nsNetCID.h"
28 #include "nsProxyRelease.h"
29 #include "nsServiceManagerUtils.h"
30 #include "nsString.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)
45 // CacheEntryHandle
47 CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry) : mEntry(aEntry) {
48 #ifdef DEBUG
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();
55 #endif
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);
67 return NS_OK;
70 LOG((" already dropped"));
71 return NS_ERROR_UNEXPECTED;
74 CacheEntryHandle::~CacheEntryHandle() {
75 mEntry->ReleaseHandleRef();
76 Dismiss();
78 LOG(("CacheEntryHandle::~CacheEntryHandle %p", this));
81 // CacheEntry::Callback
83 CacheEntry::Callback::Callback(CacheEntry* aEntry,
84 nsICacheEntryOpenCallback* aCallback,
85 bool aReadOnly, bool aCheckOnAnyThread,
86 bool aSecret)
87 : mEntry(aEntry),
88 mCallback(aCallback),
89 mTarget(GetCurrentSerialEventTarget()),
90 mReadOnly(aReadOnly),
91 mRevalidating(false),
92 mCheckOnAnyThread(aCheckOnAnyThread),
93 mRecheckAfterWrite(false),
94 mNotWanted(false),
95 mSecret(aSecret),
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)
108 : mEntry(aEntry),
109 mReadOnly(false),
110 mRevalidating(false),
111 mCheckOnAnyThread(true),
112 mRecheckAfterWrite(false),
113 mNotWanted(false),
114 mSecret(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();
160 mEntry = aEntry;
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)) {
171 *aDoom =
172 (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) &&
173 MOZ_LIKELY(!mEntry->mPinned)) ||
174 (MOZ_UNLIKELY(mDoomWhenFoundPinned) && MOZ_UNLIKELY(mEntry->mPinned));
176 return true;
179 return false;
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;
190 return NS_OK;
193 nsresult CacheEntry::Callback::OnAvailThread(bool* aOnAvailThread) const {
194 return mTarget->IsOnCurrentThread(aOnAvailThread);
197 // CacheEntry
199 NS_IMPL_ISUPPORTS(CacheEntry, nsIRunnable, CacheFileListener)
201 /* static */
202 uint64_t CacheEntry::GetNextId() {
203 static Atomic<uint64_t, Relaxed> id(0);
204 return ++id;
207 CacheEntry::CacheEntry(const nsACString& aStorageID, const nsACString& aURI,
208 const nsACString& aEnhanceID, bool aUseDisk,
209 bool aSkipSizeCheck, bool aPin)
210 : mURI(aURI),
211 mEnhanceID(aEnhanceID),
212 mStorageID(aStorageID),
213 mUseDisk(aUseDisk),
214 mSkipSizeCheck(aSkipSizeCheck),
215 mPinned(aPin),
216 mSecurityInfoLoaded(false),
217 mPreventCallbacks(false),
218 mHasData(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) {
232 switch (aState) {
233 case NOTLOADED:
234 return "NOTLOADED";
235 case LOADING:
236 return "LOADING";
237 case EMPTY:
238 return "EMPTY";
239 case WRITING:
240 return "WRITING";
241 case READY:
242 return "READY";
243 case REVALIDATING:
244 return "REVALIDATING";
247 return "?";
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);
258 // static
259 nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
260 const nsACString& aEnhanceID, nsIURI* aURI,
261 nsACString& aResult) {
262 nsAutoCString spec;
263 nsresult rv = aURI->GetAsciiSpec(spec);
264 NS_ENSURE_SUCCESS(rv, rv);
266 return HashingKey(aStorageID, aEnhanceID, spec, aResult);
269 // static
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
286 aResult.Append(':');
287 aResult.Append(aURISpec);
289 return NS_OK;
292 void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback,
293 uint32_t aFlags) {
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));
306 #ifdef DEBUG
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");
314 #endif
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)) {
332 return false;
335 RememberCallback(aCallback);
337 // Load() opens the lock
338 if (Load(aTruncate, aPriority)) {
339 // Loading is in progress...
340 return true;
343 InvokeCallbacks();
345 return true;
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"));
355 return false;
358 if (mState == LOADING) {
359 LOG((" already loading"));
360 return true;
363 mState = LOADING;
365 MOZ_ASSERT(!mFile);
367 nsresult rv;
369 nsAutoCString fileKey;
370 rv = HashingKeyWithStorage(fileKey);
372 bool reportMiss = false;
374 // Check the index under two conditions for two states and take appropriate
375 // action:
376 // 1. When this is a disk entry and not told to truncate, check there is a
377 // disk file.
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))) {
387 switch (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) {
392 LOG(
393 (" entry doesn't exist according information from the index, "
394 "truncating"));
395 reportMiss = true;
396 aTruncate = true;
398 break;
399 case CacheIndex::EXISTS:
400 case CacheIndex::DO_NOT_KNOW:
401 if (!mUseDisk) {
402 LOG(
403 (" entry open as memory-only, but there is a file, status=%d, "
404 "dooming it",
405 status));
406 CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
408 break;
413 mFile = new CacheFile();
415 BackgroundOp(Ops::REGISTER);
417 bool directLoad = aTruncate || !mUseDisk;
418 if (directLoad) {
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;
423 } else {
424 mLoadStart = TimeStamp::Now();
428 mozilla::MutexAutoUnlock unlock(mLock);
430 if (reportMiss) {
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);
441 if (NS_FAILED(rv)) {
442 mFileStatus = rv;
443 AsyncDoom(nullptr);
444 return false;
448 if (directLoad) {
449 // Just fake the load has already been done as "new".
450 mFileStatus = NS_OK;
451 mState = EMPTY;
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)) {
464 if (aIsNew) {
465 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
466 CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
467 } else {
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
476 // the entry state.
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) {
492 mHasData = true;
494 uint32_t frecency;
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);
501 InvokeCallbacks();
503 return NS_OK;
506 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult) {
507 if (mDoomCallback) {
508 RefPtr<DoomCallbackRunnable> event =
509 new DoomCallbackRunnable(this, aResult);
510 NS_DispatchToMainThread(event);
513 return NS_OK;
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
524 // prematurly
525 mPreventCallbacks = true;
527 RefPtr<CacheEntryHandle> handle;
528 RefPtr<CacheEntry> newEntry;
530 if (mPinned) {
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.)
534 aMemoryOnly = false;
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);
551 } else {
552 LOG((" exchanged of entry %p failed, rv=0x%08" PRIx32, this,
553 static_cast<uint32_t>(rv)));
554 AsyncDoom(nullptr);
558 mPreventCallbacks = false;
560 if (!newEntry) return nullptr;
562 newEntry->TransferCallbacks(*this);
563 mCallbacks.Clear();
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);
582 } else {
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);
608 InvokeCallbacks();
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;
627 uint32_t i = 0;
628 while (i < mCallbacks.Length()) {
629 if (mPreventCallbacks) {
630 LOG((" callbacks prevented!"));
631 return false;
634 if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
635 LOG((" entry is being written/revalidated"));
636 return false;
639 bool recreate;
640 if (mCallbacks[i].DeferDoom(&recreate)) {
641 mCallbacks.RemoveElementAt(i);
642 if (!recreate) {
643 continue;
646 LOG((" defer doom marker callback hit positive, recreating"));
647 recreatedHandle = ReopenTruncated(!mUseDisk, nullptr);
648 break;
651 if (mCallbacks[i].mReadOnly != aReadOnly) {
652 // Callback is not r/w or r/o, go to another one in line
653 ++i;
654 continue;
657 bool onCheckThread;
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"));
668 return false;
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
680 // invoked.
681 size_t pos = std::min(mCallbacks.Length(), static_cast<size_t>(i));
682 mCallbacks.InsertElementAt(pos, callback);
683 ++i;
687 if (recreatedHandle) {
688 // Must be released outside of the lock, enters InvokeCallback on the new
689 // entry
690 mozilla::MutexAutoUnlock unlock(mLock);
691 recreatedHandle = nullptr;
694 return true;
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
703 if (!mIsDoomed) {
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"));
713 return false;
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
725 mState = WRITING;
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.
733 return true;
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
742 // OnCacheEntryCheck
743 mozilla::MutexAutoUnlock unlock(mLock);
745 RefPtr<CacheEntryHandle> handle = NewHandle();
747 nsresult rv =
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) {
758 case ENTRY_WANTED:
759 // Nothing more to do here, the consumer is responsible to handle
760 // the result of OnCacheEntryCheck it self.
761 // Proceed to callback...
762 break;
764 case RECHECK_AFTER_WRITE_FINISHED:
765 LOG(
766 (" consumer will check on the entry again after write is "
767 "done"));
768 // The consumer wants the entry to complete first.
769 aCallback.mRecheckAfterWrite = true;
770 break;
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;
778 break;
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
783 // us.
784 aCallback.mNotWanted = true;
785 break;
791 if (aCallback.mCallback) {
792 if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
793 // If we don't have data and the callback wants a complete entry,
794 // don't invoke now.
795 bool bypass = !mHasData;
796 if (!bypass && NS_SUCCEEDED(mFileStatus)) {
797 int64_t _unused;
798 bypass = !mFile->DataSize(&_unused);
801 if (bypass) {
802 LOG((" bypassing, entry data still being written"));
803 return false;
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);
815 return true;
818 void CacheEntry::InvokeAvailableCallback(Callback const& aCallback) {
819 nsresult rv;
820 uint32_t state;
822 mozilla::MutexAutoLock lock(mLock);
823 state = mState;
824 LOG(
825 ("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, "
826 "r/o=%d, "
827 "n/w=%d]",
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);
835 bool onAvailThread;
836 rv = aCallback.OnAvailThread(&onAvailThread);
837 if (NS_FAILED(rv)) {
838 LOG((" target thread dead?"));
839 return;
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)));
849 return;
852 if (mIsDoomed || aCallback.mNotWanted) {
853 LOG(
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);
858 return;
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);
873 return;
876 // R/O callbacks may do revalidation, let them fall through
877 if (aCallback.mReadOnly && !aCallback.mRevalidating) {
878 LOG(
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);
883 return;
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
890 // respectively.
892 // Consumer will be responsible to fill or validate the entry metadata and
893 // data.
895 OnFetched(aCallback);
897 RefPtr<CacheEntryHandle> handle = NewWriteHandle();
898 rv = aCallback.mCallback->OnCacheEntryAvailable(handle, state == WRITING,
899 NS_OK);
901 if (NS_FAILED(rv)) {
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);
907 return;
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.
916 mFile->OnFetched();
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.
946 mFile->Kill();
949 if (mWriter != aHandle) {
950 LOG((" not the writer"));
951 return;
954 if (mOutputStream) {
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.
959 mHasData = false;
960 // This asynchronously ends up invoking callbacks on this entry
961 // through OnOutputClosed() call.
962 mOutputStream->Close();
963 mOutputStream = nullptr;
964 } else {
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);
971 mWriter = nullptr;
973 if (mState == WRITING) {
974 LOG((" reverting to state EMPTY - write failed"));
975 mState = EMPTY;
976 } else if (mState == REVALIDATING) {
977 LOG((" reverting to state READY - reval failed"));
978 mState = READY;
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.
991 LOG(
992 (" we are in READY state, pretend we have data regardless it"
993 " has actully been never touched"));
994 mHasData = true;
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);
1003 InvokeCallbacks();
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();
1019 return false;
1022 uint32_t CacheEntry::GetMetadataMemoryConsumption() {
1023 NS_ENSURE_SUCCESS(mFileStatus, 0);
1025 uint32_t size;
1026 if (NS_FAILED(mFile->ElementsSize(&size))) return 0;
1028 return size;
1031 // nsICacheEntry
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;
1039 return NS_OK;
1042 nsresult CacheEntry::GetKey(nsACString& aKey) {
1043 aKey.Assign(mURI);
1044 return NS_OK;
1047 nsresult CacheEntry::GetCacheEntryId(uint64_t* aCacheEntryId) {
1048 *aCacheEntryId = mCacheEntryId;
1049 return NS_OK;
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);
1106 #ifdef DEBUG
1108 mozilla::MutexAutoLock lock(mLock);
1109 MOZ_ASSERT(mState > LOADING);
1111 #endif
1112 if (mPinned) {
1113 *aIsForcedValid = true;
1114 return NS_OK;
1117 nsAutoCString key;
1118 nsresult rv = HashingKey(key);
1119 if (NS_FAILED(rv)) {
1120 return rv;
1123 *aIsForcedValid =
1124 CacheStorageService::Self()->IsForcedValidEntry(mStorageID, key);
1125 LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this,
1126 *aIsForcedValid));
1128 return NS_OK;
1131 nsresult CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture) {
1132 LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this,
1133 aSecondsToTheFuture));
1135 nsAutoCString key;
1136 nsresult rv = HashingKey(key);
1137 if (NS_FAILED(rv)) {
1138 return rv;
1141 CacheStorageService::Self()->ForceEntryValidFor(mStorageID, key,
1142 aSecondsToTheFuture);
1144 return NS_OK;
1147 nsresult CacheEntry::MarkForcedValidUse() {
1148 LOG(("CacheEntry::MarkForcedValidUse [this=%p, ]", this));
1150 nsAutoCString key;
1151 nsresult rv = HashingKey(key);
1152 if (NS_FAILED(rv)) {
1153 return rv;
1156 CacheStorageService::Self()->MarkForcedValidEntryUse(mStorageID, key);
1157 return NS_OK;
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;
1168 return NS_OK;
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);
1190 nsresult rv;
1192 RefPtr<CacheEntryHandle> selfHandle = NewHandle();
1194 nsCOMPtr<nsIInputStream> stream;
1195 if (aAltDataType) {
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.
1202 return rv;
1204 } else {
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);
1217 if (!mHasData) {
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);
1225 return NS_OK;
1228 nsresult CacheEntry::OpenOutputStream(int64_t offset, int64_t predictedSize,
1229 nsIOutputStream** _retval) {
1230 LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
1232 nsresult rv;
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);
1246 } else {
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.
1255 InvokeCallbacks();
1257 return NS_OK;
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()));
1266 nsresult rv;
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);
1291 return NS_OK;
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();
1302 if (mIsDoomed) {
1303 LOG((" doomed..."));
1304 return NS_ERROR_NOT_AVAILABLE;
1307 MOZ_ASSERT(mState > LOADING);
1309 nsresult rv;
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.
1313 if (!mUseDisk) {
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.
1332 mHasData = true;
1334 stream.swap(*_retval);
1335 return NS_OK;
1338 nsresult CacheEntry::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
1340 mozilla::MutexAutoLock lock(mLock);
1341 if (mSecurityInfoLoaded) {
1342 *aSecurityInfo = do_AddRef(mSecurityInfo).take();
1343 return NS_OK;
1347 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1349 nsCString info;
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();
1371 return NS_OK;
1374 nsresult CacheEntry::SetSecurityInfo(nsITransportSecurityInfo* aSecurityInfo) {
1375 nsresult rv;
1377 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1380 mozilla::MutexAutoLock lock(mLock);
1382 mSecurityInfo = aSecurityInfo;
1383 mSecurityInfoLoaded = true;
1386 nsCString info;
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);
1395 return NS_OK;
1398 nsresult CacheEntry::GetStorageDataSize(uint32_t* aStorageDataSize) {
1399 NS_ENSURE_ARG(aStorageDataSize);
1401 int64_t dataSize;
1402 nsresult rv = GetDataSize(&dataSize);
1403 if (NS_FAILED(rv)) return rv;
1405 *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
1407 return NS_OK;
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();
1422 mIsDoomed = true;
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
1429 // a) this entry
1430 // b) a new entry with the same file
1431 PurgeAndDoom();
1433 return NS_OK;
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;
1464 InvokeCallbacks();
1466 return NS_OK;
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);
1479 mState = READY;
1480 mHasData = true;
1482 InvokeCallbacks();
1484 outputStream.swap(mOutputStream);
1487 if (outputStream) {
1488 LOG((" abandoning phantom output stream"));
1489 outputStream->Close();
1492 return NS_OK;
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);
1500 if (handle) {
1501 handle.forget(_retval);
1502 return NS_OK;
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));
1511 *aDataSize = 0;
1514 mozilla::MutexAutoLock lock(mLock);
1516 if (!mHasData) {
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));
1531 return NS_OK;
1534 nsresult CacheEntry::GetAltDataSize(int64_t* aDataSize) {
1535 LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
1536 if (NS_FAILED(mFileStatus)) {
1537 return mFileStatus;
1539 return mFile->GetAltDataSize(aDataSize);
1542 nsresult CacheEntry::GetAltDataType(nsACString& aType) {
1543 LOG(("CacheEntry::GetAltDataType [this=%p]", this));
1544 if (NS_FAILED(mFileStatus)) {
1545 return 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);
1560 if (!info) {
1561 return NS_ERROR_FAILURE;
1564 info.forget(aInfo);
1566 return NS_OK;
1569 // nsIRunnable
1571 NS_IMETHODIMP CacheEntry::Run() {
1572 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1574 mozilla::MutexAutoLock lock(mLock);
1576 BackgroundOp(mBackgroundOperations.Grab());
1577 return NS_OK;
1580 // Management methods
1582 double CacheEntry::GetFrecency() const {
1583 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1584 return mFrecency;
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());
1605 if (aRegistered) {
1606 MOZ_ASSERT(mRegistration == NEVERREGISTERED);
1607 mRegistration = REGISTERED;
1608 } else {
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
1631 return true;
1634 bool CacheEntry::Purge(uint32_t aWhat) {
1635 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
1637 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1639 switch (aWhat) {
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
1643 if (!mUseDisk) {
1644 LOG((" not using disk"));
1645 return false;
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
1657 // been set so far.
1658 LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency));
1659 return false;
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"));
1670 return false;
1673 switch (aWhat) {
1674 case PURGE_WHOLE_ONLY_DISK_BACKED:
1675 case PURGE_WHOLE: {
1676 if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1677 LOG((" not purging, still referenced"));
1678 return false;
1681 CacheStorageService::Self()->UnregisterEntry(this);
1683 // Entry removed it self from control arrays, return true
1684 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)
1693 return false;
1697 LOG((" ?"));
1698 return false;
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();
1715 mIsDoomed = true;
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).
1724 DoomFile();
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
1741 // in that case.)
1742 // Tell the file to kill the handle, i.e. bypass any I/O operations
1743 // on it except removing the file.
1744 mFile->Kill();
1747 // Always calls the callback asynchronously.
1748 rv = mFile->Doom(mDoomCallback ? this : nullptr);
1749 if (NS_SUCCEEDED(rv)) {
1750 LOG((" file doomed"));
1751 return;
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
1758 // reused.
1759 rv = NS_OK;
1763 // Always posts to the main thread.
1764 OnFileDoomed(rv);
1767 void CacheEntry::RemoveForcedValidity() {
1768 mLock.AssertCurrentThreadOwns();
1770 nsresult rv;
1772 if (mIsDoomed) {
1773 return;
1776 nsAutoCString entryKey;
1777 rv = HashingKey(entryKey);
1778 if (NS_WARN_IF(NS_FAILED(rv))) {
1779 return;
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));
1795 return;
1799 mozilla::MutexAutoUnlock unlock(mLock);
1801 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1803 if (aOperations & Ops::FRECENCYUPDATE) {
1804 ++mUseCount;
1806 #ifndef M_LN2
1807 # define M_LN2 0.69314718055994530942
1808 #endif
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;
1820 } else {
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,
1826 mFrecency));
1828 // Because CacheFile::Set*() are not thread-safe to use (uses
1829 // WeakReference that is not thread-safe) we must post to the main
1830 // thread...
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);
1847 } // unlock
1849 if (aOperations & Ops::CALLBACKS) {
1850 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
1852 InvokeCallbacks();
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)));
1889 return;
1892 nsCOMPtr<nsIEventTarget> sts =
1893 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
1894 MOZ_DIAGNOSTIC_ASSERT(sts);
1895 if (sts) {
1896 MOZ_ALWAYS_SUCCEEDS(sts->Dispatch(do_AddRef(this)));
1900 NS_IMETHODIMP CacheOutputCloseListener::Run() {
1901 mEntry->OnOutputClosed();
1902 return NS_OK;
1905 // Memory reporting
1907 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
1908 size_t n = 0;
1910 MutexAutoLock lock(mLock);
1911 n += mCallbacks.ShallowSizeOfExcludingThis(mallocSizeOf);
1912 if (mFile) {
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
1924 // small, though.
1925 // mSecurityInfo doesn't impl nsISizeOf.
1927 return n;
1930 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
1931 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1934 } // namespace mozilla::net