Bug 1861709 replace AudioCallbackDriver::ThreadRunning() assertions that mean to...
[gecko.git] / netwerk / cache2 / CacheEntry.cpp
blob142d32d2745b3d055386641991a4f6917b29f8d4
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) {
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 LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
520 mLock.AssertCurrentThreadOwns();
522 // Hold callbacks invocation, AddStorageEntry would invoke from doom
523 // prematurly
524 mPreventCallbacks = true;
526 RefPtr<CacheEntryHandle> handle;
527 RefPtr<CacheEntry> newEntry;
529 if (mPinned) {
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.)
533 aMemoryOnly = false;
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);
550 } else {
551 LOG((" exchanged of entry %p failed, rv=0x%08" PRIx32, this,
552 static_cast<uint32_t>(rv)));
553 AsyncDoom(nullptr);
557 mPreventCallbacks = false;
559 if (!newEntry) return nullptr;
561 newEntry->TransferCallbacks(*this);
562 mCallbacks.Clear();
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);
581 } else {
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);
607 InvokeCallbacks();
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;
626 uint32_t i = 0;
627 while (i < mCallbacks.Length()) {
628 if (mPreventCallbacks) {
629 LOG((" callbacks prevented!"));
630 return false;
633 if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
634 LOG((" entry is being written/revalidated"));
635 return false;
638 bool recreate;
639 if (mCallbacks[i].DeferDoom(&recreate)) {
640 mCallbacks.RemoveElementAt(i);
641 if (!recreate) {
642 continue;
645 LOG((" defer doom marker callback hit positive, recreating"));
646 recreatedHandle = ReopenTruncated(!mUseDisk, nullptr);
647 break;
650 if (mCallbacks[i].mReadOnly != aReadOnly) {
651 // Callback is not r/w or r/o, go to another one in line
652 ++i;
653 continue;
656 bool onCheckThread;
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"));
667 return false;
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
679 // invoked.
680 size_t pos = std::min(mCallbacks.Length(), static_cast<size_t>(i));
681 mCallbacks.InsertElementAt(pos, callback);
682 ++i;
686 if (recreatedHandle) {
687 // Must be released outside of the lock, enters InvokeCallback on the new
688 // entry
689 mozilla::MutexAutoUnlock unlock(mLock);
690 recreatedHandle = nullptr;
693 return true;
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
702 if (!mIsDoomed) {
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"));
712 return false;
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
724 mState = WRITING;
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.
732 return true;
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
741 // OnCacheEntryCheck
742 mozilla::MutexAutoUnlock unlock(mLock);
744 RefPtr<CacheEntryHandle> handle = NewHandle();
746 nsresult rv =
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) {
757 case ENTRY_WANTED:
758 // Nothing more to do here, the consumer is responsible to handle
759 // the result of OnCacheEntryCheck it self.
760 // Proceed to callback...
761 break;
763 case RECHECK_AFTER_WRITE_FINISHED:
764 LOG(
765 (" consumer will check on the entry again after write is "
766 "done"));
767 // The consumer wants the entry to complete first.
768 aCallback.mRecheckAfterWrite = true;
769 break;
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;
777 break;
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
782 // us.
783 aCallback.mNotWanted = true;
784 break;
790 if (aCallback.mCallback) {
791 if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
792 // If we don't have data and the callback wants a complete entry,
793 // don't invoke now.
794 bool bypass = !mHasData;
795 if (!bypass && NS_SUCCEEDED(mFileStatus)) {
796 int64_t _unused;
797 bypass = !mFile->DataSize(&_unused);
800 if (bypass) {
801 LOG((" bypassing, entry data still being written"));
802 return false;
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);
814 return true;
817 void CacheEntry::InvokeAvailableCallback(Callback const& aCallback) {
818 nsresult rv;
819 uint32_t state;
821 mozilla::MutexAutoLock lock(mLock);
822 state = mState;
823 LOG(
824 ("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, "
825 "r/o=%d, "
826 "n/w=%d]",
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);
834 bool onAvailThread;
835 rv = aCallback.OnAvailThread(&onAvailThread);
836 if (NS_FAILED(rv)) {
837 LOG((" target thread dead?"));
838 return;
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)));
848 return;
851 if (mIsDoomed || aCallback.mNotWanted) {
852 LOG(
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);
857 return;
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);
872 return;
875 // R/O callbacks may do revalidation, let them fall through
876 if (aCallback.mReadOnly && !aCallback.mRevalidating) {
877 LOG(
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);
882 return;
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
889 // respectively.
891 // Consumer will be responsible to fill or validate the entry metadata and
892 // data.
894 OnFetched(aCallback);
896 RefPtr<CacheEntryHandle> handle = NewWriteHandle();
897 rv = aCallback.mCallback->OnCacheEntryAvailable(handle, state == WRITING,
898 NS_OK);
900 if (NS_FAILED(rv)) {
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);
906 return;
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.
915 mFile->OnFetched();
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.
945 mFile->Kill();
948 if (mWriter != aHandle) {
949 LOG((" not the writer"));
950 return;
953 if (mOutputStream) {
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.
958 mHasData = false;
959 // This asynchronously ends up invoking callbacks on this entry
960 // through OnOutputClosed() call.
961 mOutputStream->Close();
962 mOutputStream = nullptr;
963 } else {
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);
970 mWriter = nullptr;
972 if (mState == WRITING) {
973 LOG((" reverting to state EMPTY - write failed"));
974 mState = EMPTY;
975 } else if (mState == REVALIDATING) {
976 LOG((" reverting to state READY - reval failed"));
977 mState = READY;
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.
990 LOG(
991 (" we are in READY state, pretend we have data regardless it"
992 " has actully been never touched"));
993 mHasData = true;
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);
1002 InvokeCallbacks();
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();
1018 return false;
1021 uint32_t CacheEntry::GetMetadataMemoryConsumption() {
1022 NS_ENSURE_SUCCESS(mFileStatus, 0);
1024 uint32_t size;
1025 if (NS_FAILED(mFile->ElementsSize(&size))) return 0;
1027 return size;
1030 // nsICacheEntry
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;
1038 return NS_OK;
1041 nsresult CacheEntry::GetKey(nsACString& aKey) {
1042 aKey.Assign(mURI);
1043 return NS_OK;
1046 nsresult CacheEntry::GetCacheEntryId(uint64_t* aCacheEntryId) {
1047 *aCacheEntryId = mCacheEntryId;
1048 return NS_OK;
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);
1105 #ifdef DEBUG
1107 mozilla::MutexAutoLock lock(mLock);
1108 MOZ_ASSERT(mState > LOADING);
1110 #endif
1111 if (mPinned) {
1112 *aIsForcedValid = true;
1113 return NS_OK;
1116 nsAutoCString key;
1117 nsresult rv = HashingKey(key);
1118 if (NS_FAILED(rv)) {
1119 return rv;
1122 *aIsForcedValid =
1123 CacheStorageService::Self()->IsForcedValidEntry(mStorageID, key);
1124 LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this,
1125 *aIsForcedValid));
1127 return NS_OK;
1130 nsresult CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture) {
1131 LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this,
1132 aSecondsToTheFuture));
1134 nsAutoCString key;
1135 nsresult rv = HashingKey(key);
1136 if (NS_FAILED(rv)) {
1137 return rv;
1140 CacheStorageService::Self()->ForceEntryValidFor(mStorageID, key,
1141 aSecondsToTheFuture);
1143 return NS_OK;
1146 nsresult CacheEntry::MarkForcedValidUse() {
1147 LOG(("CacheEntry::MarkForcedValidUse [this=%p, ]", this));
1149 nsAutoCString key;
1150 nsresult rv = HashingKey(key);
1151 if (NS_FAILED(rv)) {
1152 return rv;
1155 CacheStorageService::Self()->MarkForcedValidEntryUse(mStorageID, key);
1156 return NS_OK;
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;
1167 return NS_OK;
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);
1189 nsresult rv;
1191 RefPtr<CacheEntryHandle> selfHandle = NewHandle();
1193 nsCOMPtr<nsIInputStream> stream;
1194 if (aAltDataType) {
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.
1201 return rv;
1203 } else {
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);
1216 if (!mHasData) {
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);
1224 return NS_OK;
1227 nsresult CacheEntry::OpenOutputStream(int64_t offset, int64_t predictedSize,
1228 nsIOutputStream** _retval) {
1229 LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
1231 nsresult rv;
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);
1245 } else {
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.
1254 InvokeCallbacks();
1256 return NS_OK;
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()));
1265 nsresult rv;
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);
1290 return NS_OK;
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();
1301 if (mIsDoomed) {
1302 LOG((" doomed..."));
1303 return NS_ERROR_NOT_AVAILABLE;
1306 MOZ_ASSERT(mState > LOADING);
1308 nsresult rv;
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.
1312 if (!mUseDisk) {
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.
1331 mHasData = true;
1333 stream.swap(*_retval);
1334 return NS_OK;
1337 nsresult CacheEntry::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
1339 mozilla::MutexAutoLock lock(mLock);
1340 if (mSecurityInfoLoaded) {
1341 *aSecurityInfo = do_AddRef(mSecurityInfo).take();
1342 return NS_OK;
1346 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1348 nsCString info;
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();
1370 return NS_OK;
1373 nsresult CacheEntry::SetSecurityInfo(nsITransportSecurityInfo* aSecurityInfo) {
1374 nsresult rv;
1376 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1379 mozilla::MutexAutoLock lock(mLock);
1381 mSecurityInfo = aSecurityInfo;
1382 mSecurityInfoLoaded = true;
1385 nsCString info;
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);
1394 return NS_OK;
1397 nsresult CacheEntry::GetStorageDataSize(uint32_t* aStorageDataSize) {
1398 NS_ENSURE_ARG(aStorageDataSize);
1400 int64_t dataSize;
1401 nsresult rv = GetDataSize(&dataSize);
1402 if (NS_FAILED(rv)) return rv;
1404 *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
1406 return NS_OK;
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();
1421 mIsDoomed = true;
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
1428 // a) this entry
1429 // b) a new entry with the same file
1430 PurgeAndDoom();
1432 return NS_OK;
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;
1463 InvokeCallbacks();
1465 return NS_OK;
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);
1478 mState = READY;
1479 mHasData = true;
1481 InvokeCallbacks();
1483 outputStream.swap(mOutputStream);
1486 if (outputStream) {
1487 LOG((" abandoning phantom output stream"));
1488 outputStream->Close();
1491 return NS_OK;
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);
1499 if (handle) {
1500 handle.forget(_retval);
1501 return NS_OK;
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));
1510 *aDataSize = 0;
1513 mozilla::MutexAutoLock lock(mLock);
1515 if (!mHasData) {
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));
1530 return NS_OK;
1533 nsresult CacheEntry::GetAltDataSize(int64_t* aDataSize) {
1534 LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
1535 if (NS_FAILED(mFileStatus)) {
1536 return mFileStatus;
1538 return mFile->GetAltDataSize(aDataSize);
1541 nsresult CacheEntry::GetAltDataType(nsACString& aType) {
1542 LOG(("CacheEntry::GetAltDataType [this=%p]", this));
1543 if (NS_FAILED(mFileStatus)) {
1544 return mFileStatus;
1546 return mFile->GetAltDataType(aType);
1549 nsresult CacheEntry::MarkValid() {
1550 // NOT IMPLEMENTED ACTUALLY
1551 return NS_OK;
1554 nsresult CacheEntry::MaybeMarkValid() {
1555 // NOT IMPLEMENTED ACTUALLY
1556 return NS_OK;
1559 nsresult CacheEntry::HasWriteAccess(bool aWriteAllowed, bool* aWriteAccess) {
1560 *aWriteAccess = aWriteAllowed;
1561 return NS_OK;
1564 nsresult CacheEntry::Close() {
1565 // NOT IMPLEMENTED ACTUALLY
1566 return NS_OK;
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);
1579 if (!info) {
1580 return NS_ERROR_FAILURE;
1583 info.forget(aInfo);
1585 return NS_OK;
1588 // nsIRunnable
1590 NS_IMETHODIMP CacheEntry::Run() {
1591 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1593 mozilla::MutexAutoLock lock(mLock);
1595 BackgroundOp(mBackgroundOperations.Grab());
1596 return NS_OK;
1599 // Management methods
1601 double CacheEntry::GetFrecency() const {
1602 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1603 return mFrecency;
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());
1624 if (aRegistered) {
1625 MOZ_ASSERT(mRegistration == NEVERREGISTERED);
1626 mRegistration = REGISTERED;
1627 } else {
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
1650 return true;
1653 bool CacheEntry::Purge(uint32_t aWhat) {
1654 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
1656 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1658 switch (aWhat) {
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
1662 if (!mUseDisk) {
1663 LOG((" not using disk"));
1664 return false;
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
1676 // been set so far.
1677 LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency));
1678 return false;
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"));
1689 return false;
1692 switch (aWhat) {
1693 case PURGE_WHOLE_ONLY_DISK_BACKED:
1694 case PURGE_WHOLE: {
1695 if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1696 LOG((" not purging, still referenced"));
1697 return false;
1700 CacheStorageService::Self()->UnregisterEntry(this);
1702 // Entry removed it self from control arrays, return true
1703 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)
1712 return false;
1716 LOG((" ?"));
1717 return false;
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();
1734 mIsDoomed = true;
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).
1743 DoomFile();
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
1760 // in that case.)
1761 // Tell the file to kill the handle, i.e. bypass any I/O operations
1762 // on it except removing the file.
1763 mFile->Kill();
1766 // Always calls the callback asynchronously.
1767 rv = mFile->Doom(mDoomCallback ? this : nullptr);
1768 if (NS_SUCCEEDED(rv)) {
1769 LOG((" file doomed"));
1770 return;
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
1777 // reused.
1778 rv = NS_OK;
1782 // Always posts to the main thread.
1783 OnFileDoomed(rv);
1786 void CacheEntry::RemoveForcedValidity() {
1787 mLock.AssertCurrentThreadOwns();
1789 nsresult rv;
1791 if (mIsDoomed) {
1792 return;
1795 nsAutoCString entryKey;
1796 rv = HashingKey(entryKey);
1797 if (NS_WARN_IF(NS_FAILED(rv))) {
1798 return;
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));
1813 return;
1817 mozilla::MutexAutoUnlock unlock(mLock);
1819 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1821 if (aOperations & Ops::FRECENCYUPDATE) {
1822 ++mUseCount;
1824 #ifndef M_LN2
1825 # define M_LN2 0.69314718055994530942
1826 #endif
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;
1838 } else {
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,
1844 mFrecency));
1846 // Because CacheFile::Set*() are not thread-safe to use (uses
1847 // WeakReference that is not thread-safe) we must post to the main
1848 // thread...
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);
1865 } // unlock
1867 if (aOperations & Ops::CALLBACKS) {
1868 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
1870 InvokeCallbacks();
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)));
1907 return;
1910 nsCOMPtr<nsIEventTarget> sts =
1911 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
1912 MOZ_DIAGNOSTIC_ASSERT(sts);
1913 if (sts) {
1914 MOZ_ALWAYS_SUCCEEDS(sts->Dispatch(do_AddRef(this)));
1918 NS_IMETHODIMP CacheOutputCloseListener::Run() {
1919 mEntry->OnOutputClosed();
1920 return NS_OK;
1923 // Memory reporting
1925 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
1926 size_t n = 0;
1928 MutexAutoLock lock(mLock);
1929 n += mCallbacks.ShallowSizeOfExcludingThis(mallocSizeOf);
1930 if (mFile) {
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
1942 // small, though.
1943 // mSecurityInfo doesn't impl nsISizeOf.
1945 return n;
1948 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
1949 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1952 } // namespace mozilla::net