Bug 1708422: part 8) Move `mozInlineSpellChecker::CheckWordsAndAddRangesForMisspellin...
[gecko.git] / netwerk / cache2 / CacheEntry.cpp
blobb63b6e5e09656797004d456bd403b57395a74d7d
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 "CacheLog.h"
6 #include "CacheEntry.h"
7 #include "CacheStorageService.h"
8 #include "CacheObserver.h"
9 #include "CacheFileUtils.h"
10 #include "CacheIndex.h"
12 #include "nsIAsyncOutputStream.h"
13 #include "nsIInputStream.h"
14 #include "nsIOutputStream.h"
15 #include "nsISeekableStream.h"
16 #include "nsIURI.h"
17 #include "nsICacheEntryOpenCallback.h"
18 #include "nsICacheStorage.h"
19 #include "nsISerializable.h"
20 #include "nsISizeOf.h"
22 #include "nsComponentManagerUtils.h"
23 #include "nsServiceManagerUtils.h"
24 #include "nsString.h"
25 #include "nsProxyRelease.h"
26 #include "nsSerializationHelper.h"
27 #include "nsThreadUtils.h"
28 #include "mozilla/Telemetry.h"
29 #include "mozilla/IntegerPrintfMacros.h"
30 #include <math.h>
31 #include <algorithm>
33 namespace mozilla {
34 namespace net {
36 static uint32_t const ENTRY_WANTED = nsICacheEntryOpenCallback::ENTRY_WANTED;
37 static uint32_t const RECHECK_AFTER_WRITE_FINISHED =
38 nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
39 static uint32_t const ENTRY_NEEDS_REVALIDATION =
40 nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION;
41 static uint32_t const ENTRY_NOT_WANTED =
42 nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
44 NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry)
46 // CacheEntryHandle
48 CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry)
49 : mEntry(aEntry), mClosed(false) {
50 #ifdef DEBUG
51 if (!mEntry->HandlesCount()) {
52 // CacheEntry.mHandlesCount must go from zero to one only under
53 // the service lock. Can access CacheStorageService::Self() w/o a check
54 // since CacheEntry hrefs it.
55 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
57 #endif
59 mEntry->AddHandleRef();
61 LOG(("New CacheEntryHandle %p for entry %p", this, aEntry));
64 NS_IMETHODIMP CacheEntryHandle::Dismiss() {
65 LOG(("CacheEntryHandle::Dismiss %p", this));
67 if (mClosed.compareExchange(false, true)) {
68 mEntry->OnHandleClosed(this);
69 return NS_OK;
72 LOG((" already dropped"));
73 return NS_ERROR_UNEXPECTED;
76 CacheEntryHandle::~CacheEntryHandle() {
77 mEntry->ReleaseHandleRef();
78 Dismiss();
80 LOG(("CacheEntryHandle::~CacheEntryHandle %p", this));
83 // CacheEntry::Callback
85 CacheEntry::Callback::Callback(CacheEntry* aEntry,
86 nsICacheEntryOpenCallback* aCallback,
87 bool aReadOnly, bool aCheckOnAnyThread,
88 bool aSecret)
89 : mEntry(aEntry),
90 mCallback(aCallback),
91 mTarget(GetCurrentEventTarget()),
92 mReadOnly(aReadOnly),
93 mRevalidating(false),
94 mCheckOnAnyThread(aCheckOnAnyThread),
95 mRecheckAfterWrite(false),
96 mNotWanted(false),
97 mSecret(aSecret),
98 mDoomWhenFoundPinned(false),
99 mDoomWhenFoundNonPinned(false) {
100 MOZ_COUNT_CTOR(CacheEntry::Callback);
102 // The counter may go from zero to non-null only under the service lock
103 // but here we expect it to be already positive.
104 MOZ_ASSERT(mEntry->HandlesCount());
105 mEntry->AddHandleRef();
108 CacheEntry::Callback::Callback(CacheEntry* aEntry,
109 bool aDoomWhenFoundInPinStatus)
110 : mEntry(aEntry),
111 mReadOnly(false),
112 mRevalidating(false),
113 mCheckOnAnyThread(true),
114 mRecheckAfterWrite(false),
115 mNotWanted(false),
116 mSecret(false),
117 mDoomWhenFoundPinned(aDoomWhenFoundInPinStatus == true),
118 mDoomWhenFoundNonPinned(aDoomWhenFoundInPinStatus == false) {
119 MOZ_COUNT_CTOR(CacheEntry::Callback);
120 MOZ_ASSERT(mEntry->HandlesCount());
121 mEntry->AddHandleRef();
124 CacheEntry::Callback::Callback(CacheEntry::Callback const& aThat)
125 : mEntry(aThat.mEntry),
126 mCallback(aThat.mCallback),
127 mTarget(aThat.mTarget),
128 mReadOnly(aThat.mReadOnly),
129 mRevalidating(aThat.mRevalidating),
130 mCheckOnAnyThread(aThat.mCheckOnAnyThread),
131 mRecheckAfterWrite(aThat.mRecheckAfterWrite),
132 mNotWanted(aThat.mNotWanted),
133 mSecret(aThat.mSecret),
134 mDoomWhenFoundPinned(aThat.mDoomWhenFoundPinned),
135 mDoomWhenFoundNonPinned(aThat.mDoomWhenFoundNonPinned) {
136 MOZ_COUNT_CTOR(CacheEntry::Callback);
138 // The counter may go from zero to non-null only under the service lock
139 // but here we expect it to be already positive.
140 MOZ_ASSERT(mEntry->HandlesCount());
141 mEntry->AddHandleRef();
144 CacheEntry::Callback::~Callback() {
145 ProxyRelease("CacheEntry::Callback::mCallback", mCallback, mTarget);
147 mEntry->ReleaseHandleRef();
148 MOZ_COUNT_DTOR(CacheEntry::Callback);
151 void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry) {
152 if (mEntry == aEntry) return;
154 // The counter may go from zero to non-null only under the service lock
155 // but here we expect it to be already positive.
156 MOZ_ASSERT(aEntry->HandlesCount());
157 aEntry->AddHandleRef();
158 mEntry->ReleaseHandleRef();
159 mEntry = aEntry;
162 bool CacheEntry::Callback::DeferDoom(bool* aDoom) const {
163 MOZ_ASSERT(mEntry->mPinningKnown);
165 if (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) ||
166 MOZ_UNLIKELY(mDoomWhenFoundPinned)) {
167 *aDoom =
168 (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) &&
169 MOZ_LIKELY(!mEntry->mPinned)) ||
170 (MOZ_UNLIKELY(mDoomWhenFoundPinned) && MOZ_UNLIKELY(mEntry->mPinned));
172 return true;
175 return false;
178 nsresult CacheEntry::Callback::OnCheckThread(bool* aOnCheckThread) const {
179 if (!mCheckOnAnyThread) {
180 // Check we are on the target
181 return mTarget->IsOnCurrentThread(aOnCheckThread);
184 // We can invoke check anywhere
185 *aOnCheckThread = true;
186 return NS_OK;
189 nsresult CacheEntry::Callback::OnAvailThread(bool* aOnAvailThread) const {
190 return mTarget->IsOnCurrentThread(aOnAvailThread);
193 // CacheEntry
195 NS_IMPL_ISUPPORTS(CacheEntry, nsIRunnable, CacheFileListener)
197 /* static */
198 uint64_t CacheEntry::GetNextId() {
199 static Atomic<uint64_t, Relaxed> id(0);
200 return ++id;
203 CacheEntry::CacheEntry(const nsACString& aStorageID, const nsACString& aURI,
204 const nsACString& aEnhanceID, bool aUseDisk,
205 bool aSkipSizeCheck, bool aPin)
206 : mFrecency(0),
207 mSortingExpirationTime(uint32_t(-1)),
208 mLock("CacheEntry"),
209 mFileStatus(NS_ERROR_NOT_INITIALIZED),
210 mURI(aURI),
211 mEnhanceID(aEnhanceID),
212 mStorageID(aStorageID),
213 mUseDisk(aUseDisk),
214 mSkipSizeCheck(aSkipSizeCheck),
215 mIsDoomed(false),
216 mSecurityInfoLoaded(false),
217 mPreventCallbacks(false),
218 mHasData(false),
219 mPinned(aPin),
220 mPinningKnown(false),
221 mState(NOTLOADED),
222 mRegistration(NEVERREGISTERED),
223 mWriter(nullptr),
224 mUseCount(0),
225 mCacheEntryId(GetNextId()) {
226 LOG(("CacheEntry::CacheEntry [this=%p]", this));
228 mService = CacheStorageService::Self();
230 CacheStorageService::Self()->RecordMemoryOnlyEntry(this, !aUseDisk,
231 true /* overwrite */);
234 CacheEntry::~CacheEntry() { LOG(("CacheEntry::~CacheEntry [this=%p]", this)); }
236 char const* CacheEntry::StateString(uint32_t aState) {
237 switch (aState) {
238 case NOTLOADED:
239 return "NOTLOADED";
240 case LOADING:
241 return "LOADING";
242 case EMPTY:
243 return "EMPTY";
244 case WRITING:
245 return "WRITING";
246 case READY:
247 return "READY";
248 case REVALIDATING:
249 return "REVALIDATING";
252 return "?";
255 nsresult CacheEntry::HashingKeyWithStorage(nsACString& aResult) const {
256 return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
259 nsresult CacheEntry::HashingKey(nsACString& aResult) const {
260 return HashingKey(""_ns, mEnhanceID, mURI, aResult);
263 // static
264 nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
265 const nsACString& aEnhanceID, nsIURI* aURI,
266 nsACString& aResult) {
267 nsAutoCString spec;
268 nsresult rv = aURI->GetAsciiSpec(spec);
269 NS_ENSURE_SUCCESS(rv, rv);
271 return HashingKey(aStorageID, aEnhanceID, spec, aResult);
274 // static
275 nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
276 const nsACString& aEnhanceID,
277 const nsACString& aURISpec,
278 nsACString& aResult) {
280 * This key is used to salt hash that is a base for disk file name.
281 * Changing it will cause we will not be able to find files on disk.
284 aResult.Assign(aStorageID);
286 if (!aEnhanceID.IsEmpty()) {
287 CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID);
290 // Appending directly
291 aResult.Append(':');
292 aResult.Append(aURISpec);
294 return NS_OK;
297 void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback,
298 uint32_t aFlags) {
299 LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]", this,
300 StateString(mState), aFlags, aCallback));
302 bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
303 bool bypassIfBusy = aFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY;
304 bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
305 bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
306 bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
307 bool secret = aFlags & nsICacheStorage::OPEN_SECRETLY;
309 MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
310 MOZ_ASSERT(!(truncate && mState > LOADING),
311 "Must not call truncate on already loaded entry");
313 Callback callback(this, aCallback, readonly, multithread, secret);
315 if (!Open(callback, truncate, priority, bypassIfBusy)) {
316 // We get here when the callback wants to bypass cache when it's busy.
317 LOG((" writing or revalidating, callback wants to bypass cache"));
318 callback.mNotWanted = true;
319 InvokeAvailableCallback(callback);
323 bool CacheEntry::Open(Callback& aCallback, bool aTruncate, bool aPriority,
324 bool aBypassIfBusy) {
325 mozilla::MutexAutoLock lock(mLock);
327 // Check state under the lock
328 if (aBypassIfBusy && (mState == WRITING || mState == REVALIDATING)) {
329 return false;
332 RememberCallback(aCallback);
334 // Load() opens the lock
335 if (Load(aTruncate, aPriority)) {
336 // Loading is in progress...
337 return true;
340 InvokeCallbacks();
342 return true;
345 bool CacheEntry::Load(bool aTruncate, bool aPriority) {
346 LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
348 mLock.AssertCurrentThreadOwns();
350 if (mState > LOADING) {
351 LOG((" already loaded"));
352 return false;
355 if (mState == LOADING) {
356 LOG((" already loading"));
357 return true;
360 mState = LOADING;
362 MOZ_ASSERT(!mFile);
364 nsresult rv;
366 nsAutoCString fileKey;
367 rv = HashingKeyWithStorage(fileKey);
369 bool reportMiss = false;
371 // Check the index under two conditions for two states and take appropriate
372 // action:
373 // 1. When this is a disk entry and not told to truncate, check there is a
374 // disk file.
375 // If not, set the 'truncate' flag to true so that this entry will open
376 // instantly as a new one.
377 // 2. When this is a memory-only entry, check there is a disk file.
378 // If there is or could be, doom that file.
379 if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) {
380 // Check the index right now to know we have or have not the entry
381 // as soon as possible.
382 CacheIndex::EntryStatus status;
383 if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) {
384 switch (status) {
385 case CacheIndex::DOES_NOT_EXIST:
386 // Doesn't apply to memory-only entries, Load() is called only once
387 // for them and never again for their session lifetime.
388 if (!aTruncate && mUseDisk) {
389 LOG(
390 (" entry doesn't exist according information from the index, "
391 "truncating"));
392 reportMiss = true;
393 aTruncate = true;
395 break;
396 case CacheIndex::EXISTS:
397 case CacheIndex::DO_NOT_KNOW:
398 if (!mUseDisk) {
399 LOG(
400 (" entry open as memory-only, but there is a file, status=%d, "
401 "dooming it",
402 status));
403 CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
405 break;
410 mFile = new CacheFile();
412 BackgroundOp(Ops::REGISTER);
414 bool directLoad = aTruncate || !mUseDisk;
415 if (directLoad) {
416 // mLoadStart will be used to calculate telemetry of life-time of this
417 // entry. Low resulution is then enough.
418 mLoadStart = TimeStamp::NowLoRes();
419 mPinningKnown = true;
420 } else {
421 mLoadStart = TimeStamp::Now();
425 mozilla::MutexAutoUnlock unlock(mLock);
427 if (reportMiss) {
428 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
429 CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
432 LOG((" performing load, file=%p", mFile.get()));
433 if (NS_SUCCEEDED(rv)) {
434 rv = mFile->Init(fileKey, aTruncate, !mUseDisk, mSkipSizeCheck, aPriority,
435 mPinned, directLoad ? nullptr : this);
438 if (NS_FAILED(rv)) {
439 mFileStatus = rv;
440 AsyncDoom(nullptr);
441 return false;
445 if (directLoad) {
446 // Just fake the load has already been done as "new".
447 mFileStatus = NS_OK;
448 mState = EMPTY;
451 return mState == LOADING;
454 NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew) {
455 LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08" PRIx32 ", new=%d]", this,
456 static_cast<uint32_t>(aResult), aIsNew));
458 MOZ_ASSERT(!mLoadStart.IsNull());
460 if (NS_SUCCEEDED(aResult)) {
461 if (aIsNew) {
462 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
463 CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
464 } else {
465 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
466 CacheFileUtils::DetailedCacheHitTelemetry::HIT, mLoadStart);
470 // OnFileReady, that is the only code that can transit from LOADING
471 // to any follow-on state and can only be invoked ones on an entry.
472 // Until this moment there is no consumer that could manipulate
473 // the entry state.
475 mozilla::MutexAutoLock lock(mLock);
477 MOZ_ASSERT(mState == LOADING);
479 mState = (aIsNew || NS_FAILED(aResult)) ? EMPTY : READY;
481 mFileStatus = aResult;
483 mPinned = mFile->IsPinned();
485 mPinningKnown = true;
486 LOG((" pinning=%d", mPinned));
488 if (mState == READY) {
489 mHasData = true;
491 uint32_t frecency;
492 mFile->GetFrecency(&frecency);
493 // mFrecency is held in a double to increase computance precision.
494 // It is ok to persist frecency only as a uint32 with some math involved.
495 mFrecency = INT2FRECENCY(frecency);
498 InvokeCallbacks();
500 return NS_OK;
503 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult) {
504 if (mDoomCallback) {
505 RefPtr<DoomCallbackRunnable> event =
506 new DoomCallbackRunnable(this, aResult);
507 NS_DispatchToMainThread(event);
510 return NS_OK;
513 already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(
514 bool aMemoryOnly, nsICacheEntryOpenCallback* aCallback) {
515 LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
517 mLock.AssertCurrentThreadOwns();
519 // Hold callbacks invocation, AddStorageEntry would invoke from doom
520 // prematurly
521 mPreventCallbacks = true;
523 RefPtr<CacheEntryHandle> handle;
524 RefPtr<CacheEntry> newEntry;
526 if (mPinned) {
527 MOZ_ASSERT(mUseDisk);
528 // We want to pin even no-store entries (the case we recreate a disk entry
529 // as a memory-only entry.)
530 aMemoryOnly = false;
533 mozilla::MutexAutoUnlock unlock(mLock);
535 // The following call dooms this entry (calls DoomAlreadyRemoved on us)
536 nsresult rv = CacheStorageService::Self()->AddStorageEntry(
537 GetStorageID(), GetURI(), GetEnhanceID(), mUseDisk && !aMemoryOnly,
538 mSkipSizeCheck, mPinned,
539 true, // truncate existing (this one)
540 getter_AddRefs(handle));
542 if (NS_SUCCEEDED(rv)) {
543 newEntry = handle->Entry();
544 LOG((" exchanged entry %p by entry %p, rv=0x%08" PRIx32, this,
545 newEntry.get(), static_cast<uint32_t>(rv)));
546 newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
547 } else {
548 LOG((" exchanged of entry %p failed, rv=0x%08" PRIx32, this,
549 static_cast<uint32_t>(rv)));
550 AsyncDoom(nullptr);
554 mPreventCallbacks = false;
556 if (!newEntry) return nullptr;
558 newEntry->TransferCallbacks(*this);
559 mCallbacks.Clear();
561 // Must return a new write handle, since the consumer is expected to
562 // write to this newly recreated entry. The |handle| is only a common
563 // reference counter and doesn't revert entry state back when write
564 // fails and also doesn't update the entry frecency. Not updating
565 // frecency causes entries to not be purged from our memory pools.
566 RefPtr<CacheEntryHandle> writeHandle = newEntry->NewWriteHandle();
567 return writeHandle.forget();
570 void CacheEntry::TransferCallbacks(CacheEntry& aFromEntry) {
571 mozilla::MutexAutoLock lock(mLock);
573 LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]", this, &aFromEntry));
575 if (!mCallbacks.Length())
576 mCallbacks.SwapElements(aFromEntry.mCallbacks);
577 else
578 mCallbacks.AppendElements(aFromEntry.mCallbacks);
580 uint32_t callbacksLength = mCallbacks.Length();
581 if (callbacksLength) {
582 // Carry the entry reference (unfortunately, needs to be done manually...)
583 for (uint32_t i = 0; i < callbacksLength; ++i)
584 mCallbacks[i].ExchangeEntry(this);
586 BackgroundOp(Ops::CALLBACKS, true);
590 void CacheEntry::RememberCallback(Callback& aCallback) {
591 mLock.AssertCurrentThreadOwns();
593 LOG(("CacheEntry::RememberCallback [this=%p, cb=%p, state=%s]", this,
594 aCallback.mCallback.get(), StateString(mState)));
596 mCallbacks.AppendElement(aCallback);
599 void CacheEntry::InvokeCallbacksLock() {
600 mozilla::MutexAutoLock lock(mLock);
601 InvokeCallbacks();
604 void CacheEntry::InvokeCallbacks() {
605 mLock.AssertCurrentThreadOwns();
607 LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
609 // Invoke first all r/w callbacks, then all r/o callbacks.
610 if (InvokeCallbacks(false)) InvokeCallbacks(true);
612 LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
615 bool CacheEntry::InvokeCallbacks(bool aReadOnly) {
616 mLock.AssertCurrentThreadOwns();
618 RefPtr<CacheEntryHandle> recreatedHandle;
620 uint32_t i = 0;
621 while (i < mCallbacks.Length()) {
622 if (mPreventCallbacks) {
623 LOG((" callbacks prevented!"));
624 return false;
627 if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
628 LOG((" entry is being written/revalidated"));
629 return false;
632 bool recreate;
633 if (mCallbacks[i].DeferDoom(&recreate)) {
634 mCallbacks.RemoveElementAt(i);
635 if (!recreate) {
636 continue;
639 LOG((" defer doom marker callback hit positive, recreating"));
640 recreatedHandle = ReopenTruncated(!mUseDisk, nullptr);
641 break;
644 if (mCallbacks[i].mReadOnly != aReadOnly) {
645 // Callback is not r/w or r/o, go to another one in line
646 ++i;
647 continue;
650 bool onCheckThread;
651 nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
653 if (NS_SUCCEEDED(rv) && !onCheckThread) {
654 // Redispatch to the target thread
655 rv = mCallbacks[i].mTarget->Dispatch(
656 NewRunnableMethod("net::CacheEntry::InvokeCallbacksLock", this,
657 &CacheEntry::InvokeCallbacksLock),
658 nsIEventTarget::DISPATCH_NORMAL);
659 if (NS_SUCCEEDED(rv)) {
660 LOG((" re-dispatching to target thread"));
661 return false;
665 Callback callback = mCallbacks[i];
666 mCallbacks.RemoveElementAt(i);
668 if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
669 // Callback didn't fire, put it back and go to another one in line.
670 // Only reason InvokeCallback returns false is that onCacheEntryCheck
671 // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other
672 // readers or potential writers would be unnecessarily kept from being
673 // invoked.
674 size_t pos = std::min(mCallbacks.Length(), static_cast<size_t>(i));
675 mCallbacks.InsertElementAt(pos, callback);
676 ++i;
680 if (recreatedHandle) {
681 // Must be released outside of the lock, enters InvokeCallback on the new
682 // entry
683 mozilla::MutexAutoUnlock unlock(mLock);
684 recreatedHandle = nullptr;
687 return true;
690 bool CacheEntry::InvokeCallback(Callback& aCallback) {
691 LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", this,
692 StateString(mState), aCallback.mCallback.get()));
694 mLock.AssertCurrentThreadOwns();
696 // When this entry is doomed we want to notify the callback any time
697 if (!mIsDoomed) {
698 // When we are here, the entry must be loaded from disk
699 MOZ_ASSERT(mState > LOADING);
701 if (mState == WRITING || mState == REVALIDATING) {
702 // Prevent invoking other callbacks since one of them is now writing
703 // or revalidating this entry. No consumers should get this entry
704 // until metadata are filled with values downloaded from the server
705 // or the entry revalidated and output stream has been opened.
706 LOG((" entry is being written/revalidated, callback bypassed"));
707 return false;
710 // mRecheckAfterWrite flag already set means the callback has already passed
711 // the onCacheEntryCheck call. Until the current write is not finished this
712 // callback will be bypassed.
713 if (!aCallback.mRecheckAfterWrite) {
714 if (!aCallback.mReadOnly) {
715 if (mState == EMPTY) {
716 // Advance to writing state, we expect to invoke the callback and let
717 // it fill content of this entry. Must set and check the state here
718 // to prevent more then one
719 mState = WRITING;
720 LOG((" advancing to WRITING state"));
723 if (!aCallback.mCallback) {
724 // We can be given no callback only in case of recreate, it is ok
725 // to advance to WRITING state since the caller of recreate is
726 // expected to write this entry now.
727 return true;
731 if (mState == READY) {
732 // Metadata present, validate the entry
733 uint32_t checkResult;
735 // mayhemer: TODO check and solve any potential races of concurent
736 // OnCacheEntryCheck
737 mozilla::MutexAutoUnlock unlock(mLock);
739 RefPtr<CacheEntryHandle> handle = NewHandle();
741 nsresult rv =
742 aCallback.mCallback->OnCacheEntryCheck(handle, &checkResult);
743 LOG((" OnCacheEntryCheck: rv=0x%08" PRIx32 ", result=%" PRId32,
744 static_cast<uint32_t>(rv), static_cast<uint32_t>(checkResult)));
746 if (NS_FAILED(rv)) checkResult = ENTRY_NOT_WANTED;
749 aCallback.mRevalidating = checkResult == ENTRY_NEEDS_REVALIDATION;
751 switch (checkResult) {
752 case ENTRY_WANTED:
753 // Nothing more to do here, the consumer is responsible to handle
754 // the result of OnCacheEntryCheck it self.
755 // Proceed to callback...
756 break;
758 case RECHECK_AFTER_WRITE_FINISHED:
759 LOG(
760 (" consumer will check on the entry again after write is "
761 "done"));
762 // The consumer wants the entry to complete first.
763 aCallback.mRecheckAfterWrite = true;
764 break;
766 case ENTRY_NEEDS_REVALIDATION:
767 LOG((" will be holding callbacks until entry is revalidated"));
768 // State is READY now and from that state entry cannot transit to
769 // any other state then REVALIDATING for which cocurrency is not an
770 // issue. Potentially no need to lock here.
771 mState = REVALIDATING;
772 break;
774 case ENTRY_NOT_WANTED:
775 LOG((" consumer not interested in the entry"));
776 // Do not give this entry to the consumer, it is not interested in
777 // us.
778 aCallback.mNotWanted = true;
779 break;
785 if (aCallback.mCallback) {
786 if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
787 // If we don't have data and the callback wants a complete entry,
788 // don't invoke now.
789 bool bypass = !mHasData;
790 if (!bypass && NS_SUCCEEDED(mFileStatus)) {
791 int64_t _unused;
792 bypass = !mFile->DataSize(&_unused);
795 if (bypass) {
796 LOG((" bypassing, entry data still being written"));
797 return false;
800 // Entry is complete now, do the check+avail call again
801 aCallback.mRecheckAfterWrite = false;
802 return InvokeCallback(aCallback);
805 mozilla::MutexAutoUnlock unlock(mLock);
806 InvokeAvailableCallback(aCallback);
809 return true;
812 void CacheEntry::InvokeAvailableCallback(Callback const& aCallback) {
813 LOG(
814 ("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, "
815 "n/w=%d]",
816 this, StateString(mState), aCallback.mCallback.get(),
817 aCallback.mReadOnly, aCallback.mNotWanted));
819 nsresult rv;
821 uint32_t state;
823 mozilla::MutexAutoLock lock(mLock);
824 state = mState;
827 // When we are here, the entry must be loaded from disk
828 MOZ_ASSERT(state > LOADING || mIsDoomed);
830 bool onAvailThread;
831 rv = aCallback.OnAvailThread(&onAvailThread);
832 if (NS_FAILED(rv)) {
833 LOG((" target thread dead?"));
834 return;
837 if (!onAvailThread) {
838 // Dispatch to the right thread
839 RefPtr<AvailableCallbackRunnable> event =
840 new AvailableCallbackRunnable(this, aCallback);
842 rv = aCallback.mTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
843 LOG((" redispatched, (rv = 0x%08" PRIx32 ")", static_cast<uint32_t>(rv)));
844 return;
847 if (mIsDoomed || aCallback.mNotWanted) {
848 LOG(
849 (" doomed or not wanted, notifying OCEA with "
850 "NS_ERROR_CACHE_KEY_NOT_FOUND"));
851 aCallback.mCallback->OnCacheEntryAvailable(nullptr, false,
852 NS_ERROR_CACHE_KEY_NOT_FOUND);
853 return;
856 if (state == READY) {
857 LOG((" ready/has-meta, notifying OCEA with entry and NS_OK"));
859 if (!aCallback.mSecret) {
860 mozilla::MutexAutoLock lock(mLock);
861 BackgroundOp(Ops::FRECENCYUPDATE);
864 OnFetched(aCallback);
866 RefPtr<CacheEntryHandle> handle = NewHandle();
867 aCallback.mCallback->OnCacheEntryAvailable(handle, false, NS_OK);
868 return;
871 // R/O callbacks may do revalidation, let them fall through
872 if (aCallback.mReadOnly && !aCallback.mRevalidating) {
873 LOG(
874 (" r/o and not ready, notifying OCEA with "
875 "NS_ERROR_CACHE_KEY_NOT_FOUND"));
876 aCallback.mCallback->OnCacheEntryAvailable(nullptr, false,
877 NS_ERROR_CACHE_KEY_NOT_FOUND);
878 return;
881 // This is a new or potentially non-valid entry and needs to be fetched first.
882 // The CacheEntryHandle blocks other consumers until the channel
883 // either releases the entry or marks metadata as filled or whole entry valid,
884 // i.e. until MetaDataReady() or SetValid() on the entry is called
885 // respectively.
887 // Consumer will be responsible to fill or validate the entry metadata and
888 // data.
890 OnFetched(aCallback);
892 RefPtr<CacheEntryHandle> handle = NewWriteHandle();
893 rv = aCallback.mCallback->OnCacheEntryAvailable(handle, state == WRITING,
894 NS_OK);
896 if (NS_FAILED(rv)) {
897 LOG((" writing/revalidating failed (0x%08" PRIx32 ")",
898 static_cast<uint32_t>(rv)));
900 // Consumer given a new entry failed to take care of the entry.
901 OnHandleClosed(handle);
902 return;
905 LOG((" writing/revalidating"));
908 void CacheEntry::OnFetched(Callback const& aCallback) {
909 if (NS_SUCCEEDED(mFileStatus) && !aCallback.mSecret) {
910 // Let the last-fetched and fetch-count properties be updated.
911 mFile->OnFetched();
915 CacheEntryHandle* CacheEntry::NewHandle() { return new CacheEntryHandle(this); }
917 CacheEntryHandle* CacheEntry::NewWriteHandle() {
918 mozilla::MutexAutoLock lock(mLock);
920 // Ignore the OPEN_SECRETLY flag on purpose here, which should actually be
921 // used only along with OPEN_READONLY, but there is no need to enforce that.
922 BackgroundOp(Ops::FRECENCYUPDATE);
924 return (mWriter = NewHandle());
927 void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle) {
928 LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this,
929 StateString(mState), aHandle));
931 mozilla::MutexAutoLock lock(mLock);
933 if (IsDoomed() && NS_SUCCEEDED(mFileStatus) &&
934 // Note: mHandlesCount is dropped before this method is called
935 (mHandlesCount == 0 ||
936 (mHandlesCount == 1 && mWriter && mWriter != aHandle))) {
937 // This entry is no longer referenced from outside and is doomed.
938 // We can do this also when there is just reference from the writer,
939 // no one else could ever reach the written data.
940 // Tell the file to kill the handle, i.e. bypass any I/O operations
941 // on it except removing the file.
942 mFile->Kill();
945 if (mWriter != aHandle) {
946 LOG((" not the writer"));
947 return;
950 if (mOutputStream) {
951 LOG((" abandoning phantom output stream"));
952 // No one took our internal output stream, so there are no data
953 // and output stream has to be open symultaneously with input stream
954 // on this entry again.
955 mHasData = false;
956 // This asynchronously ends up invoking callbacks on this entry
957 // through OnOutputClosed() call.
958 mOutputStream->Close();
959 mOutputStream = nullptr;
960 } else {
961 // We must always redispatch, otherwise there is a risk of stack
962 // overflow. This code can recurse deeply. It won't execute sooner
963 // than we release mLock.
964 BackgroundOp(Ops::CALLBACKS, true);
967 mWriter = nullptr;
969 if (mState == WRITING) {
970 LOG((" reverting to state EMPTY - write failed"));
971 mState = EMPTY;
972 } else if (mState == REVALIDATING) {
973 LOG((" reverting to state READY - reval failed"));
974 mState = READY;
977 if (mState == READY && !mHasData) {
978 // We may get to this state when following steps happen:
979 // 1. a new entry is given to a consumer
980 // 2. the consumer calls MetaDataReady(), we transit to READY
981 // 3. abandons the entry w/o opening the output stream, mHasData left false
983 // In this case any following consumer will get a ready entry (with
984 // metadata) but in state like the entry data write was still happening (was
985 // in progress) and will indefinitely wait for the entry data or even the
986 // entry itself when RECHECK_AFTER_WRITE is returned from onCacheEntryCheck.
987 LOG(
988 (" we are in READY state, pretend we have data regardless it"
989 " has actully been never touched"));
990 mHasData = true;
994 void CacheEntry::OnOutputClosed() {
995 // Called when the file's output stream is closed. Invoke any callbacks
996 // waiting for complete entry.
998 mozilla::MutexAutoLock lock(mLock);
999 InvokeCallbacks();
1002 bool CacheEntry::IsReferenced() const {
1003 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
1005 // Increasing this counter from 0 to non-null and this check both happen only
1006 // under the service lock.
1007 return mHandlesCount > 0;
1010 bool CacheEntry::IsFileDoomed() {
1011 if (NS_SUCCEEDED(mFileStatus)) {
1012 return mFile->IsDoomed();
1015 return false;
1018 uint32_t CacheEntry::GetMetadataMemoryConsumption() {
1019 NS_ENSURE_SUCCESS(mFileStatus, 0);
1021 uint32_t size;
1022 if (NS_FAILED(mFile->ElementsSize(&size))) return 0;
1024 return size;
1027 // nsICacheEntry
1029 nsresult CacheEntry::GetPersistent(bool* aPersistToDisk) {
1030 // No need to sync when only reading.
1031 // When consumer needs to be consistent with state of the memory storage
1032 // entries table, then let it use GetUseDisk getter that must be called under
1033 // the service lock.
1034 *aPersistToDisk = mUseDisk;
1035 return NS_OK;
1038 nsresult CacheEntry::GetKey(nsACString& aKey) {
1039 aKey.Assign(mURI);
1040 return NS_OK;
1043 nsresult CacheEntry::GetCacheEntryId(uint64_t* aCacheEntryId) {
1044 *aCacheEntryId = mCacheEntryId;
1045 return NS_OK;
1048 nsresult CacheEntry::GetFetchCount(int32_t* aFetchCount) {
1049 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1051 return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount));
1054 nsresult CacheEntry::GetLastFetched(uint32_t* aLastFetched) {
1055 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1057 return mFile->GetLastFetched(aLastFetched);
1060 nsresult CacheEntry::GetLastModified(uint32_t* aLastModified) {
1061 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1063 return mFile->GetLastModified(aLastModified);
1066 nsresult CacheEntry::GetExpirationTime(uint32_t* aExpirationTime) {
1067 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1069 return mFile->GetExpirationTime(aExpirationTime);
1072 nsresult CacheEntry::GetOnStartTime(uint64_t* aTime) {
1073 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1074 return mFile->GetOnStartTime(aTime);
1077 nsresult CacheEntry::GetOnStopTime(uint64_t* aTime) {
1078 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1079 return mFile->GetOnStopTime(aTime);
1082 nsresult CacheEntry::SetNetworkTimes(uint64_t aOnStartTime,
1083 uint64_t aOnStopTime) {
1084 if (NS_SUCCEEDED(mFileStatus)) {
1085 return mFile->SetNetworkTimes(aOnStartTime, aOnStopTime);
1087 return NS_ERROR_NOT_AVAILABLE;
1090 nsresult CacheEntry::SetContentType(uint8_t aContentType) {
1091 NS_ENSURE_ARG_MAX(aContentType, nsICacheEntry::CONTENT_TYPE_LAST - 1);
1093 if (NS_SUCCEEDED(mFileStatus)) {
1094 return mFile->SetContentType(aContentType);
1096 return NS_ERROR_NOT_AVAILABLE;
1099 nsresult CacheEntry::GetIsForcedValid(bool* aIsForcedValid) {
1100 NS_ENSURE_ARG(aIsForcedValid);
1102 MOZ_ASSERT(mState > LOADING);
1105 mozilla::MutexAutoLock lock(mLock);
1106 if (mPinned) {
1107 *aIsForcedValid = true;
1108 return NS_OK;
1112 nsAutoCString key;
1113 nsresult rv = HashingKey(key);
1114 if (NS_FAILED(rv)) {
1115 return rv;
1118 *aIsForcedValid =
1119 CacheStorageService::Self()->IsForcedValidEntry(mStorageID, key);
1120 LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this,
1121 *aIsForcedValid));
1123 return NS_OK;
1126 nsresult CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture) {
1127 LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this,
1128 aSecondsToTheFuture));
1130 nsAutoCString key;
1131 nsresult rv = HashingKey(key);
1132 if (NS_FAILED(rv)) {
1133 return rv;
1136 CacheStorageService::Self()->ForceEntryValidFor(mStorageID, key,
1137 aSecondsToTheFuture);
1139 return NS_OK;
1142 nsresult CacheEntry::SetExpirationTime(uint32_t aExpirationTime) {
1143 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1145 nsresult rv = mFile->SetExpirationTime(aExpirationTime);
1146 NS_ENSURE_SUCCESS(rv, rv);
1148 // Aligned assignment, thus atomic.
1149 mSortingExpirationTime = aExpirationTime;
1150 return NS_OK;
1153 nsresult CacheEntry::OpenInputStream(int64_t offset, nsIInputStream** _retval) {
1154 LOG(("CacheEntry::OpenInputStream [this=%p]", this));
1155 return OpenInputStreamInternal(offset, nullptr, _retval);
1158 nsresult CacheEntry::OpenAlternativeInputStream(const nsACString& type,
1159 nsIInputStream** _retval) {
1160 LOG(("CacheEntry::OpenAlternativeInputStream [this=%p, type=%s]", this,
1161 PromiseFlatCString(type).get()));
1162 return OpenInputStreamInternal(0, PromiseFlatCString(type).get(), _retval);
1165 nsresult CacheEntry::OpenInputStreamInternal(int64_t offset,
1166 const char* aAltDataType,
1167 nsIInputStream** _retval) {
1168 LOG(("CacheEntry::OpenInputStreamInternal [this=%p]", this));
1170 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1172 nsresult rv;
1174 RefPtr<CacheEntryHandle> selfHandle = NewHandle();
1176 nsCOMPtr<nsIInputStream> stream;
1177 if (aAltDataType) {
1178 rv = mFile->OpenAlternativeInputStream(selfHandle, aAltDataType,
1179 getter_AddRefs(stream));
1180 if (NS_FAILED(rv)) {
1181 // Failure of this method may be legal when the alternative data requested
1182 // is not avaialble or of a different type. Console error logs are
1183 // ensured by CacheFile::OpenAlternativeInputStream.
1184 return rv;
1186 } else {
1187 rv = mFile->OpenInputStream(selfHandle, getter_AddRefs(stream));
1188 NS_ENSURE_SUCCESS(rv, rv);
1191 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream, &rv);
1192 NS_ENSURE_SUCCESS(rv, rv);
1194 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1195 NS_ENSURE_SUCCESS(rv, rv);
1197 mozilla::MutexAutoLock lock(mLock);
1199 if (!mHasData) {
1200 // So far output stream on this new entry not opened, do it now.
1201 LOG((" creating phantom output stream"));
1202 rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
1203 NS_ENSURE_SUCCESS(rv, rv);
1206 stream.forget(_retval);
1207 return NS_OK;
1210 nsresult CacheEntry::OpenOutputStream(int64_t offset, int64_t predictedSize,
1211 nsIOutputStream** _retval) {
1212 LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
1214 nsresult rv;
1216 mozilla::MutexAutoLock lock(mLock);
1218 MOZ_ASSERT(mState > EMPTY);
1220 if (mFile->EntryWouldExceedLimit(0, predictedSize, false)) {
1221 LOG((" entry would exceed size limit"));
1222 return NS_ERROR_FILE_TOO_BIG;
1225 if (mOutputStream && !mIsDoomed) {
1226 LOG((" giving phantom output stream"));
1227 mOutputStream.forget(_retval);
1228 } else {
1229 rv = OpenOutputStreamInternal(offset, _retval);
1230 if (NS_FAILED(rv)) return rv;
1233 // Entry considered ready when writer opens output stream.
1234 if (mState < READY) mState = READY;
1236 // Invoke any pending readers now.
1237 InvokeCallbacks();
1239 return NS_OK;
1242 nsresult CacheEntry::OpenAlternativeOutputStream(
1243 const nsACString& type, int64_t predictedSize,
1244 nsIAsyncOutputStream** _retval) {
1245 LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
1246 PromiseFlatCString(type).get()));
1248 nsresult rv;
1250 if (type.IsEmpty()) {
1251 // The empty string is reserved to mean no alt-data available.
1252 return NS_ERROR_INVALID_ARG;
1255 mozilla::MutexAutoLock lock(mLock);
1257 if (!mHasData || mState < READY || mOutputStream || mIsDoomed) {
1258 LOG((" entry not in state to write alt-data"));
1259 return NS_ERROR_NOT_AVAILABLE;
1262 if (mFile->EntryWouldExceedLimit(0, predictedSize, true)) {
1263 LOG((" entry would exceed size limit"));
1264 return NS_ERROR_FILE_TOO_BIG;
1267 nsCOMPtr<nsIAsyncOutputStream> stream;
1268 rv = mFile->OpenAlternativeOutputStream(
1269 nullptr, PromiseFlatCString(type).get(), getter_AddRefs(stream));
1270 NS_ENSURE_SUCCESS(rv, rv);
1272 stream.swap(*_retval);
1273 return NS_OK;
1276 nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset,
1277 nsIOutputStream** _retval) {
1278 LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
1280 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1282 mLock.AssertCurrentThreadOwns();
1284 if (mIsDoomed) {
1285 LOG((" doomed..."));
1286 return NS_ERROR_NOT_AVAILABLE;
1289 MOZ_ASSERT(mState > LOADING);
1291 nsresult rv;
1293 // No need to sync on mUseDisk here, we don't need to be consistent
1294 // with content of the memory storage entries hash table.
1295 if (!mUseDisk) {
1296 rv = mFile->SetMemoryOnly();
1297 NS_ENSURE_SUCCESS(rv, rv);
1300 RefPtr<CacheOutputCloseListener> listener =
1301 new CacheOutputCloseListener(this);
1303 nsCOMPtr<nsIOutputStream> stream;
1304 rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
1305 NS_ENSURE_SUCCESS(rv, rv);
1307 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream, &rv);
1308 NS_ENSURE_SUCCESS(rv, rv);
1310 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1311 NS_ENSURE_SUCCESS(rv, rv);
1313 // Prevent opening output stream again.
1314 mHasData = true;
1316 stream.swap(*_retval);
1317 return NS_OK;
1320 nsresult CacheEntry::GetSecurityInfo(nsISupports** aSecurityInfo) {
1322 mozilla::MutexAutoLock lock(mLock);
1323 if (mSecurityInfoLoaded) {
1324 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1325 return NS_OK;
1329 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1331 nsCString info;
1332 nsCOMPtr<nsISupports> secInfo;
1333 nsresult rv;
1335 rv = mFile->GetElement("security-info", getter_Copies(info));
1336 NS_ENSURE_SUCCESS(rv, rv);
1338 if (!info.IsVoid()) {
1339 rv = NS_DeserializeObject(info, getter_AddRefs(secInfo));
1340 NS_ENSURE_SUCCESS(rv, rv);
1344 mozilla::MutexAutoLock lock(mLock);
1346 mSecurityInfo.swap(secInfo);
1347 mSecurityInfoLoaded = true;
1349 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1352 return NS_OK;
1354 nsresult CacheEntry::SetSecurityInfo(nsISupports* aSecurityInfo) {
1355 nsresult rv;
1357 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1360 mozilla::MutexAutoLock lock(mLock);
1362 mSecurityInfo = aSecurityInfo;
1363 mSecurityInfoLoaded = true;
1366 nsCOMPtr<nsISerializable> serializable = do_QueryInterface(aSecurityInfo);
1367 if (aSecurityInfo && !serializable) return NS_ERROR_UNEXPECTED;
1369 nsCString info;
1370 if (serializable) {
1371 rv = NS_SerializeToString(serializable, info);
1372 NS_ENSURE_SUCCESS(rv, rv);
1375 rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
1376 NS_ENSURE_SUCCESS(rv, rv);
1378 return NS_OK;
1381 nsresult CacheEntry::GetStorageDataSize(uint32_t* aStorageDataSize) {
1382 NS_ENSURE_ARG(aStorageDataSize);
1384 int64_t dataSize;
1385 nsresult rv = GetDataSize(&dataSize);
1386 if (NS_FAILED(rv)) return rv;
1388 *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
1390 return NS_OK;
1393 nsresult CacheEntry::AsyncDoom(nsICacheEntryDoomCallback* aCallback) {
1394 LOG(("CacheEntry::AsyncDoom [this=%p]", this));
1397 mozilla::MutexAutoLock lock(mLock);
1399 if (mIsDoomed || mDoomCallback)
1400 return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
1402 RemoveForcedValidity();
1404 mIsDoomed = true;
1405 mDoomCallback = aCallback;
1408 // This immediately removes the entry from the master hashtable and also
1409 // immediately dooms the file. This way we make sure that any consumer
1410 // after this point asking for the same entry won't get
1411 // a) this entry
1412 // b) a new entry with the same file
1413 PurgeAndDoom();
1415 return NS_OK;
1418 nsresult CacheEntry::GetMetaDataElement(const char* aKey, char** aRetval) {
1419 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1421 return mFile->GetElement(aKey, aRetval);
1424 nsresult CacheEntry::SetMetaDataElement(const char* aKey, const char* aValue) {
1425 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1427 return mFile->SetElement(aKey, aValue);
1430 nsresult CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor* aVisitor) {
1431 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1433 return mFile->VisitMetaData(aVisitor);
1436 nsresult CacheEntry::MetaDataReady() {
1437 mozilla::MutexAutoLock lock(mLock);
1439 LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this,
1440 StateString(mState)));
1442 MOZ_ASSERT(mState > EMPTY);
1444 if (mState == WRITING) mState = READY;
1446 InvokeCallbacks();
1448 return NS_OK;
1451 nsresult CacheEntry::SetValid() {
1452 LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState)));
1454 nsCOMPtr<nsIOutputStream> outputStream;
1457 mozilla::MutexAutoLock lock(mLock);
1459 MOZ_ASSERT(mState > EMPTY);
1461 mState = READY;
1462 mHasData = true;
1464 InvokeCallbacks();
1466 outputStream.swap(mOutputStream);
1469 if (outputStream) {
1470 LOG((" abandoning phantom output stream"));
1471 outputStream->Close();
1474 return NS_OK;
1477 nsresult CacheEntry::Recreate(bool aMemoryOnly, nsICacheEntry** _retval) {
1478 LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
1480 mozilla::MutexAutoLock lock(mLock);
1482 RefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
1483 if (handle) {
1484 handle.forget(_retval);
1485 return NS_OK;
1488 BackgroundOp(Ops::CALLBACKS, true);
1489 return NS_ERROR_NOT_AVAILABLE;
1492 nsresult CacheEntry::GetDataSize(int64_t* aDataSize) {
1493 LOG(("CacheEntry::GetDataSize [this=%p]", this));
1494 *aDataSize = 0;
1497 mozilla::MutexAutoLock lock(mLock);
1499 if (!mHasData) {
1500 LOG((" write in progress (no data)"));
1501 return NS_ERROR_IN_PROGRESS;
1505 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1507 // mayhemer: TODO Problem with compression?
1508 if (!mFile->DataSize(aDataSize)) {
1509 LOG((" write in progress (stream active)"));
1510 return NS_ERROR_IN_PROGRESS;
1513 LOG((" size=%" PRId64, *aDataSize));
1514 return NS_OK;
1517 nsresult CacheEntry::GetAltDataSize(int64_t* aDataSize) {
1518 LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
1519 if (NS_FAILED(mFileStatus)) {
1520 return mFileStatus;
1522 return mFile->GetAltDataSize(aDataSize);
1525 nsresult CacheEntry::GetAltDataType(nsACString& aType) {
1526 LOG(("CacheEntry::GetAltDataType [this=%p]", this));
1527 if (NS_FAILED(mFileStatus)) {
1528 return mFileStatus;
1530 return mFile->GetAltDataType(aType);
1533 nsresult CacheEntry::MarkValid() {
1534 // NOT IMPLEMENTED ACTUALLY
1535 return NS_OK;
1538 nsresult CacheEntry::MaybeMarkValid() {
1539 // NOT IMPLEMENTED ACTUALLY
1540 return NS_OK;
1543 nsresult CacheEntry::HasWriteAccess(bool aWriteAllowed, bool* aWriteAccess) {
1544 *aWriteAccess = aWriteAllowed;
1545 return NS_OK;
1548 nsresult CacheEntry::Close() {
1549 // NOT IMPLEMENTED ACTUALLY
1550 return NS_OK;
1553 nsresult CacheEntry::GetDiskStorageSizeInKB(uint32_t* aDiskStorageSize) {
1554 if (NS_FAILED(mFileStatus)) {
1555 return NS_ERROR_NOT_AVAILABLE;
1558 return mFile->GetDiskStorageSizeInKB(aDiskStorageSize);
1561 nsresult CacheEntry::GetLoadContextInfo(nsILoadContextInfo** aInfo) {
1562 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(mStorageID);
1563 if (!info) {
1564 return NS_ERROR_FAILURE;
1567 info.forget(aInfo);
1569 return NS_OK;
1572 // nsIRunnable
1574 NS_IMETHODIMP CacheEntry::Run() {
1575 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1577 mozilla::MutexAutoLock lock(mLock);
1579 BackgroundOp(mBackgroundOperations.Grab());
1580 return NS_OK;
1583 // Management methods
1585 double CacheEntry::GetFrecency() const {
1586 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1587 return mFrecency;
1590 uint32_t CacheEntry::GetExpirationTime() const {
1591 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1592 return mSortingExpirationTime;
1595 bool CacheEntry::IsRegistered() const {
1596 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1597 return mRegistration == REGISTERED;
1600 bool CacheEntry::CanRegister() const {
1601 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1602 return mRegistration == NEVERREGISTERED;
1605 void CacheEntry::SetRegistered(bool aRegistered) {
1606 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1608 if (aRegistered) {
1609 MOZ_ASSERT(mRegistration == NEVERREGISTERED);
1610 mRegistration = REGISTERED;
1611 } else {
1612 MOZ_ASSERT(mRegistration == REGISTERED);
1613 mRegistration = DEREGISTERED;
1617 bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned) {
1618 LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this));
1620 mozilla::MutexAutoLock lock(mLock);
1622 if (mPinningKnown) {
1623 LOG((" pinned=%d, caller=%d", mPinned, aPinned));
1624 // Bypass when the pin status of this entry doesn't match the pin status
1625 // caller wants to remove
1626 return mPinned != aPinned;
1629 LOG((" pinning unknown, caller=%d", aPinned));
1630 // Oterwise, remember to doom after the status is determined for any
1631 // callback opening the entry after this point...
1632 Callback c(this, aPinned);
1633 RememberCallback(c);
1634 // ...and always bypass
1635 return true;
1638 bool CacheEntry::Purge(uint32_t aWhat) {
1639 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
1641 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1643 switch (aWhat) {
1644 case PURGE_DATA_ONLY_DISK_BACKED:
1645 case PURGE_WHOLE_ONLY_DISK_BACKED:
1646 // This is an in-memory only entry, don't purge it
1647 if (!mUseDisk) {
1648 LOG((" not using disk"));
1649 return false;
1654 mozilla::MutexAutoLock lock(mLock);
1656 if (mState == WRITING || mState == LOADING || mFrecency == 0) {
1657 // In-progress (write or load) entries should (at least for consistency
1658 // and from the logical point of view) stay in memory. Zero-frecency
1659 // entries are those which have never been given to any consumer, those
1660 // are actually very fresh and should not go just because frecency had not
1661 // been set so far.
1662 LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency));
1663 return false;
1667 if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) {
1668 // The file is used when there are open streams or chunks/metadata still
1669 // waiting for write. In this case, this entry cannot be purged,
1670 // otherwise reopenned entry would may not even find the data on disk -
1671 // CacheFile is not shared and cannot be left orphan when its job is not
1672 // done, hence keep the whole entry.
1673 LOG((" file still under use"));
1674 return false;
1677 switch (aWhat) {
1678 case PURGE_WHOLE_ONLY_DISK_BACKED:
1679 case PURGE_WHOLE: {
1680 if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1681 LOG((" not purging, still referenced"));
1682 return false;
1685 CacheStorageService::Self()->UnregisterEntry(this);
1687 // Entry removed it self from control arrays, return true
1688 return true;
1691 case PURGE_DATA_ONLY_DISK_BACKED: {
1692 NS_ENSURE_SUCCESS(mFileStatus, false);
1694 mFile->ThrowMemoryCachedData();
1696 // Entry has been left in control arrays, return false (not purged)
1697 return false;
1701 LOG((" ?"));
1702 return false;
1705 void CacheEntry::PurgeAndDoom() {
1706 LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
1708 CacheStorageService::Self()->RemoveEntry(this);
1709 DoomAlreadyRemoved();
1712 void CacheEntry::DoomAlreadyRemoved() {
1713 LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
1715 mozilla::MutexAutoLock lock(mLock);
1717 RemoveForcedValidity();
1719 mIsDoomed = true;
1721 // Pretend pinning is know. This entry is now doomed for good, so don't
1722 // bother with defering doom because of unknown pinning state any more.
1723 mPinningKnown = true;
1725 // This schedules dooming of the file, dooming is ensured to happen
1726 // sooner than demand to open the same file made after this point
1727 // so that we don't get this file for any newer opened entry(s).
1728 DoomFile();
1730 // Must force post here since may be indirectly called from
1731 // InvokeCallbacks of this entry and we don't want reentrancy here.
1732 BackgroundOp(Ops::CALLBACKS, true);
1733 // Process immediately when on the management thread.
1734 BackgroundOp(Ops::UNREGISTER);
1737 void CacheEntry::DoomFile() {
1738 nsresult rv = NS_ERROR_NOT_AVAILABLE;
1740 if (NS_SUCCEEDED(mFileStatus)) {
1741 if (mHandlesCount == 0 || (mHandlesCount == 1 && mWriter)) {
1742 // We kill the file also when there is just reference from the writer,
1743 // no one else could ever reach the written data. Obvisouly also
1744 // when there is no reference at all (should we ever end up here
1745 // in that case.)
1746 // Tell the file to kill the handle, i.e. bypass any I/O operations
1747 // on it except removing the file.
1748 mFile->Kill();
1751 // Always calls the callback asynchronously.
1752 rv = mFile->Doom(mDoomCallback ? this : nullptr);
1753 if (NS_SUCCEEDED(rv)) {
1754 LOG((" file doomed"));
1755 return;
1758 if (NS_ERROR_FILE_NOT_FOUND == rv) {
1759 // File is set to be just memory-only, notify the callbacks
1760 // and pretend dooming has succeeded. From point of view of
1761 // the entry it actually did - the data is gone and cannot be
1762 // reused.
1763 rv = NS_OK;
1767 // Always posts to the main thread.
1768 OnFileDoomed(rv);
1771 void CacheEntry::RemoveForcedValidity() {
1772 mLock.AssertCurrentThreadOwns();
1774 nsresult rv;
1776 if (mIsDoomed) {
1777 return;
1780 nsAutoCString entryKey;
1781 rv = HashingKey(entryKey);
1782 if (NS_WARN_IF(NS_FAILED(rv))) {
1783 return;
1786 CacheStorageService::Self()->RemoveEntryForceValid(mStorageID, entryKey);
1789 void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync) {
1790 mLock.AssertCurrentThreadOwns();
1792 if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
1793 if (mBackgroundOperations.Set(aOperations))
1794 CacheStorageService::Self()->Dispatch(this);
1796 LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
1797 return;
1801 mozilla::MutexAutoUnlock unlock(mLock);
1803 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1805 if (aOperations & Ops::FRECENCYUPDATE) {
1806 ++mUseCount;
1808 #ifndef M_LN2
1809 # define M_LN2 0.69314718055994530942
1810 #endif
1812 // Half-life is dynamic, in seconds.
1813 static double half_life = CacheObserver::HalfLifeSeconds();
1814 // Must convert from seconds to milliseconds since PR_Now() gives usecs.
1815 static double const decay =
1816 (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
1818 double now_decay = static_cast<double>(PR_Now()) * decay;
1820 if (mFrecency == 0) {
1821 mFrecency = now_decay;
1822 } else {
1823 // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n +
1824 // 1) but more precise.
1825 mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
1827 LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this,
1828 mFrecency));
1830 // Because CacheFile::Set*() are not thread-safe to use (uses
1831 // WeakReference that is not thread-safe) we must post to the main
1832 // thread...
1833 NS_DispatchToMainThread(
1834 NewRunnableMethod<double>("net::CacheEntry::StoreFrecency", this,
1835 &CacheEntry::StoreFrecency, mFrecency));
1838 if (aOperations & Ops::REGISTER) {
1839 LOG(("CacheEntry REGISTER [this=%p]", this));
1841 CacheStorageService::Self()->RegisterEntry(this);
1844 if (aOperations & Ops::UNREGISTER) {
1845 LOG(("CacheEntry UNREGISTER [this=%p]", this));
1847 CacheStorageService::Self()->UnregisterEntry(this);
1849 } // unlock
1851 if (aOperations & Ops::CALLBACKS) {
1852 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
1854 InvokeCallbacks();
1858 void CacheEntry::StoreFrecency(double aFrecency) {
1859 MOZ_ASSERT(NS_IsMainThread());
1861 if (NS_SUCCEEDED(mFileStatus)) {
1862 mFile->SetFrecency(FRECENCY2INT(aFrecency));
1866 // CacheOutputCloseListener
1868 CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
1869 : Runnable("net::CacheOutputCloseListener"), mEntry(aEntry) {}
1871 void CacheOutputCloseListener::OnOutputClosed() {
1872 // We need this class and to redispatch since this callback is invoked
1873 // under the file's lock and to do the job we need to enter the entry's
1874 // lock too. That would lead to potential deadlocks.
1875 NS_DispatchToCurrentThread(this);
1878 NS_IMETHODIMP CacheOutputCloseListener::Run() {
1879 mEntry->OnOutputClosed();
1880 return NS_OK;
1883 // Memory reporting
1885 size_t CacheEntry::SizeOfExcludingThis(
1886 mozilla::MallocSizeOf mallocSizeOf) const {
1887 size_t n = 0;
1889 n += mCallbacks.ShallowSizeOfExcludingThis(mallocSizeOf);
1890 if (mFile) {
1891 n += mFile->SizeOfIncludingThis(mallocSizeOf);
1894 n += mURI.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1895 n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1896 n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1898 // mDoomCallback is an arbitrary class that is probably reported elsewhere.
1899 // mOutputStream is reported in mFile.
1900 // mWriter is one of many handles we create, but (intentionally) not keep
1901 // any reference to, so those unfortunately cannot be reported. Handles are
1902 // small, though.
1903 // mSecurityInfo doesn't impl nsISizeOf.
1905 return n;
1908 size_t CacheEntry::SizeOfIncludingThis(
1909 mozilla::MallocSizeOf mallocSizeOf) const {
1910 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1913 } // namespace net
1914 } // namespace mozilla