Bumping manifests a=b2g-bump
[gecko.git] / netwerk / cache2 / CacheEntry.cpp
bloba8aec185c035de1d2e7ae9ad81ee2fabd2084f99
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 "nsIInputStream.h"
13 #include "nsIOutputStream.h"
14 #include "nsISeekableStream.h"
15 #include "nsIURI.h"
16 #include "nsICacheEntryOpenCallback.h"
17 #include "nsICacheStorage.h"
18 #include "nsISerializable.h"
19 #include "nsIStreamTransportService.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 <math.h>
30 #include <algorithm>
32 namespace mozilla {
33 namespace net {
35 static uint32_t const ENTRY_WANTED =
36 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)
51 MOZ_COUNT_CTOR(CacheEntryHandle);
53 #ifdef DEBUG
54 if (!mEntry->HandlesCount()) {
55 // CacheEntry.mHandlesCount must go from zero to one only under
56 // the service lock. Can access CacheStorageService::Self() w/o a check
57 // since CacheEntry hrefs it.
58 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
60 #endif
62 mEntry->AddHandleRef();
64 LOG(("New CacheEntryHandle %p for entry %p", this, aEntry));
67 CacheEntryHandle::~CacheEntryHandle()
69 mEntry->ReleaseHandleRef();
70 mEntry->OnHandleClosed(this);
72 MOZ_COUNT_DTOR(CacheEntryHandle);
75 // CacheEntry::Callback
77 CacheEntry::Callback::Callback(CacheEntry* aEntry,
78 nsICacheEntryOpenCallback *aCallback,
79 bool aReadOnly, bool aCheckOnAnyThread,
80 bool aSecret)
81 : mEntry(aEntry)
82 , mCallback(aCallback)
83 , mTargetThread(do_GetCurrentThread())
84 , mReadOnly(aReadOnly)
85 , mCheckOnAnyThread(aCheckOnAnyThread)
86 , mRecheckAfterWrite(false)
87 , mNotWanted(false)
88 , mSecret(aSecret)
90 MOZ_COUNT_CTOR(CacheEntry::Callback);
92 // The counter may go from zero to non-null only under the service lock
93 // but here we expect it to be already positive.
94 MOZ_ASSERT(mEntry->HandlesCount());
95 mEntry->AddHandleRef();
98 CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
99 : mEntry(aThat.mEntry)
100 , mCallback(aThat.mCallback)
101 , mTargetThread(aThat.mTargetThread)
102 , mReadOnly(aThat.mReadOnly)
103 , mCheckOnAnyThread(aThat.mCheckOnAnyThread)
104 , mRecheckAfterWrite(aThat.mRecheckAfterWrite)
105 , mNotWanted(aThat.mNotWanted)
106 , mSecret(aThat.mSecret)
108 MOZ_COUNT_CTOR(CacheEntry::Callback);
110 // The counter may go from zero to non-null only under the service lock
111 // but here we expect it to be already positive.
112 MOZ_ASSERT(mEntry->HandlesCount());
113 mEntry->AddHandleRef();
116 CacheEntry::Callback::~Callback()
118 ProxyRelease(mCallback, mTargetThread);
120 mEntry->ReleaseHandleRef();
121 MOZ_COUNT_DTOR(CacheEntry::Callback);
124 void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry)
126 if (mEntry == aEntry)
127 return;
129 // The counter may go from zero to non-null only under the service lock
130 // but here we expect it to be already positive.
131 MOZ_ASSERT(aEntry->HandlesCount());
132 aEntry->AddHandleRef();
133 mEntry->ReleaseHandleRef();
134 mEntry = aEntry;
137 nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const
139 if (!mCheckOnAnyThread) {
140 // Check we are on the target
141 return mTargetThread->IsOnCurrentThread(aOnCheckThread);
144 // We can invoke check anywhere
145 *aOnCheckThread = true;
146 return NS_OK;
149 nsresult CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread) const
151 return mTargetThread->IsOnCurrentThread(aOnAvailThread);
154 // CacheEntry
156 NS_IMPL_ISUPPORTS(CacheEntry,
157 nsICacheEntry,
158 nsIRunnable,
159 CacheFileListener)
161 CacheEntry::CacheEntry(const nsACString& aStorageID,
162 nsIURI* aURI,
163 const nsACString& aEnhanceID,
164 bool aUseDisk)
165 : mFrecency(0)
166 , mSortingExpirationTime(uint32_t(-1))
167 , mLock("CacheEntry")
168 , mFileStatus(NS_ERROR_NOT_INITIALIZED)
169 , mURI(aURI)
170 , mEnhanceID(aEnhanceID)
171 , mStorageID(aStorageID)
172 , mUseDisk(aUseDisk)
173 , mIsDoomed(false)
174 , mSecurityInfoLoaded(false)
175 , mPreventCallbacks(false)
176 , mHasData(false)
177 , mState(NOTLOADED)
178 , mRegistration(NEVERREGISTERED)
179 , mWriter(nullptr)
180 , mPredictedDataSize(0)
181 , mUseCount(0)
182 , mReleaseThread(NS_GetCurrentThread())
184 MOZ_COUNT_CTOR(CacheEntry);
186 mService = CacheStorageService::Self();
188 CacheStorageService::Self()->RecordMemoryOnlyEntry(
189 this, !aUseDisk, true /* overwrite */);
192 CacheEntry::~CacheEntry()
194 ProxyRelease(mURI, mReleaseThread);
196 LOG(("CacheEntry::~CacheEntry [this=%p]", this));
197 MOZ_COUNT_DTOR(CacheEntry);
200 #ifdef PR_LOG
202 char const * CacheEntry::StateString(uint32_t aState)
204 switch (aState) {
205 case NOTLOADED: return "NOTLOADED";
206 case LOADING: return "LOADING";
207 case EMPTY: return "EMPTY";
208 case WRITING: return "WRITING";
209 case READY: return "READY";
210 case REVALIDATING: return "REVALIDATING";
213 return "?";
216 #endif
218 nsresult CacheEntry::HashingKeyWithStorage(nsACString &aResult) const
220 return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
223 nsresult CacheEntry::HashingKey(nsACString &aResult) const
225 return HashingKey(EmptyCString(), mEnhanceID, mURI, aResult);
228 // static
229 nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
230 nsCSubstring const& aEnhanceID,
231 nsIURI* aURI,
232 nsACString &aResult)
234 nsAutoCString spec;
235 nsresult rv = aURI->GetAsciiSpec(spec);
236 NS_ENSURE_SUCCESS(rv, rv);
238 return HashingKey(aStorageID, aEnhanceID, spec, aResult);
241 // static
242 nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
243 nsCSubstring const& aEnhanceID,
244 nsCSubstring const& aURISpec,
245 nsACString &aResult)
248 * This key is used to salt hash that is a base for disk file name.
249 * Changing it will cause we will not be able to find files on disk.
252 aResult.Append(aStorageID);
254 if (!aEnhanceID.IsEmpty()) {
255 CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID);
258 // Appending directly
259 aResult.Append(':');
260 aResult.Append(aURISpec);
262 return NS_OK;
265 void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags)
267 LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]",
268 this, StateString(mState), aFlags, aCallback));
270 bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
271 bool bypassIfBusy = aFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY;
272 bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
273 bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
274 bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
275 bool secret = aFlags & nsICacheStorage::OPEN_SECRETLY;
277 MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
278 MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry");
280 Callback callback(this, aCallback, readonly, multithread, secret);
282 if (!Open(callback, truncate, priority, bypassIfBusy)) {
283 // We get here when the callback wants to bypass cache when it's busy.
284 LOG((" writing or revalidating, callback wants to bypass cache"));
285 callback.mNotWanted = true;
286 InvokeAvailableCallback(callback);
290 bool CacheEntry::Open(Callback & aCallback, bool aTruncate,
291 bool aPriority, bool aBypassIfBusy)
293 mozilla::MutexAutoLock lock(mLock);
295 // Check state under the lock
296 if (aBypassIfBusy && (mState == WRITING || mState == REVALIDATING)) {
297 return false;
300 RememberCallback(aCallback);
302 // Load() opens the lock
303 if (Load(aTruncate, aPriority)) {
304 // Loading is in progress...
305 return true;
308 InvokeCallbacks();
310 return true;
313 bool CacheEntry::Load(bool aTruncate, bool aPriority)
315 LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
317 mLock.AssertCurrentThreadOwns();
319 if (mState > LOADING) {
320 LOG((" already loaded"));
321 return false;
324 if (mState == LOADING) {
325 LOG((" already loading"));
326 return true;
329 mState = LOADING;
331 MOZ_ASSERT(!mFile);
333 nsresult rv;
335 nsAutoCString fileKey;
336 rv = HashingKeyWithStorage(fileKey);
338 // Check the index under two conditions for two states and take appropriate action:
339 // 1. When this is a disk entry and not told to truncate, check there is a disk file.
340 // If not, set the 'truncate' flag to true so that this entry will open instantly
341 // as a new one.
342 // 2. When this is a memory-only entry, check there is a disk file.
343 // If there is or could be, doom that file.
344 if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) {
345 // Check the index right now to know we have or have not the entry
346 // as soon as possible.
347 CacheIndex::EntryStatus status;
348 if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) {
349 switch (status) {
350 case CacheIndex::DOES_NOT_EXIST:
351 LOG((" entry doesn't exist according information from the index, truncating"));
352 aTruncate = true;
353 break;
354 case CacheIndex::EXISTS:
355 case CacheIndex::DO_NOT_KNOW:
356 if (!mUseDisk) {
357 LOG((" entry open as memory-only, but there is (status=%d) a file, dooming it", status));
358 CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
360 break;
365 mFile = new CacheFile();
367 BackgroundOp(Ops::REGISTER);
369 bool directLoad = aTruncate || !mUseDisk;
370 if (directLoad) {
371 mFileStatus = NS_OK;
372 // mLoadStart will be used to calculate telemetry of life-time of this entry.
373 // Low resulution is then enough.
374 mLoadStart = TimeStamp::NowLoRes();
375 } else {
376 mLoadStart = TimeStamp::Now();
380 mozilla::MutexAutoUnlock unlock(mLock);
382 LOG((" performing load, file=%p", mFile.get()));
383 if (NS_SUCCEEDED(rv)) {
384 rv = mFile->Init(fileKey,
385 aTruncate,
386 !mUseDisk,
387 aPriority,
388 directLoad ? nullptr : this);
391 if (NS_FAILED(rv)) {
392 mFileStatus = rv;
393 AsyncDoom(nullptr);
394 return false;
398 if (directLoad) {
399 // Just fake the load has already been done as "new".
400 mState = EMPTY;
403 return mState == LOADING;
406 NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
408 LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08x, new=%d]",
409 this, aResult, aIsNew));
411 MOZ_ASSERT(!mLoadStart.IsNull());
413 if (NS_SUCCEEDED(aResult)) {
414 if (aIsNew) {
415 mozilla::Telemetry::AccumulateTimeDelta(
416 mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS,
417 mLoadStart);
419 else {
420 mozilla::Telemetry::AccumulateTimeDelta(
421 mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS,
422 mLoadStart);
426 // OnFileReady, that is the only code that can transit from LOADING
427 // to any follow-on state, can only be invoked ones on an entry,
428 // thus no need to lock. Until this moment there is no consumer that
429 // could manipulate the entry state.
430 mozilla::MutexAutoLock lock(mLock);
432 MOZ_ASSERT(mState == LOADING);
434 mState = (aIsNew || NS_FAILED(aResult))
435 ? EMPTY
436 : READY;
438 mFileStatus = aResult;
440 if (mState == READY) {
441 mHasData = true;
443 uint32_t frecency;
444 mFile->GetFrecency(&frecency);
445 // mFrecency is held in a double to increase computance precision.
446 // It is ok to persist frecency only as a uint32 with some math involved.
447 mFrecency = INT2FRECENCY(frecency);
450 InvokeCallbacks();
451 return NS_OK;
454 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult)
456 if (mDoomCallback) {
457 nsRefPtr<DoomCallbackRunnable> event =
458 new DoomCallbackRunnable(this, aResult);
459 NS_DispatchToMainThread(event);
462 return NS_OK;
465 already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(bool aMemoryOnly,
466 nsICacheEntryOpenCallback* aCallback)
468 LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
470 mLock.AssertCurrentThreadOwns();
472 // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly
473 mPreventCallbacks = true;
475 nsRefPtr<CacheEntryHandle> handle;
476 nsRefPtr<CacheEntry> newEntry;
478 mozilla::MutexAutoUnlock unlock(mLock);
480 // The following call dooms this entry (calls DoomAlreadyRemoved on us)
481 nsresult rv = CacheStorageService::Self()->AddStorageEntry(
482 GetStorageID(), GetURI(), GetEnhanceID(),
483 mUseDisk && !aMemoryOnly,
484 true, // always create
485 true, // truncate existing (this one)
486 getter_AddRefs(handle));
488 if (NS_SUCCEEDED(rv)) {
489 newEntry = handle->Entry();
490 LOG((" exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv));
491 newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
492 } else {
493 LOG((" exchanged of entry %p failed, rv=0x%08x", this, rv));
494 AsyncDoom(nullptr);
498 mPreventCallbacks = false;
500 if (!newEntry)
501 return nullptr;
503 newEntry->TransferCallbacks(*this);
504 mCallbacks.Clear();
506 // Must return a new write handle, since the consumer is expected to
507 // write to this newly recreated entry. The |handle| is only a common
508 // reference counter and doesn't revert entry state back when write
509 // fails and also doesn't update the entry frecency. Not updating
510 // frecency causes entries to not be purged from our memory pools.
511 nsRefPtr<CacheEntryHandle> writeHandle =
512 newEntry->NewWriteHandle();
513 return writeHandle.forget();
516 void CacheEntry::TransferCallbacks(CacheEntry & aFromEntry)
518 mozilla::MutexAutoLock lock(mLock);
520 LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]",
521 this, &aFromEntry));
523 if (!mCallbacks.Length())
524 mCallbacks.SwapElements(aFromEntry.mCallbacks);
525 else
526 mCallbacks.AppendElements(aFromEntry.mCallbacks);
528 uint32_t callbacksLength = mCallbacks.Length();
529 if (callbacksLength) {
530 // Carry the entry reference (unfortunatelly, needs to be done manually...)
531 for (uint32_t i = 0; i < callbacksLength; ++i)
532 mCallbacks[i].ExchangeEntry(this);
534 BackgroundOp(Ops::CALLBACKS, true);
538 void CacheEntry::RememberCallback(Callback & aCallback)
540 mLock.AssertCurrentThreadOwns();
542 LOG(("CacheEntry::RememberCallback [this=%p, cb=%p, state=%s]",
543 this, aCallback.mCallback.get(), StateString(mState)));
545 mCallbacks.AppendElement(aCallback);
548 void CacheEntry::InvokeCallbacksLock()
550 mozilla::MutexAutoLock lock(mLock);
551 InvokeCallbacks();
554 void CacheEntry::InvokeCallbacks()
556 mLock.AssertCurrentThreadOwns();
558 LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
560 // Invoke first all r/w callbacks, then all r/o callbacks.
561 if (InvokeCallbacks(false))
562 InvokeCallbacks(true);
564 LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
567 bool CacheEntry::InvokeCallbacks(bool aReadOnly)
569 mLock.AssertCurrentThreadOwns();
571 uint32_t i = 0;
572 while (i < mCallbacks.Length()) {
573 if (mPreventCallbacks) {
574 LOG((" callbacks prevented!"));
575 return false;
578 if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
579 LOG((" entry is being written/revalidated"));
580 return false;
583 if (mCallbacks[i].mReadOnly != aReadOnly) {
584 // Callback is not r/w or r/o, go to another one in line
585 ++i;
586 continue;
589 bool onCheckThread;
590 nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
592 if (NS_SUCCEEDED(rv) && !onCheckThread) {
593 // Redispatch to the target thread
594 nsRefPtr<nsRunnableMethod<CacheEntry> > event =
595 NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksLock);
597 rv = mCallbacks[i].mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
598 if (NS_SUCCEEDED(rv)) {
599 LOG((" re-dispatching to target thread"));
600 return false;
604 Callback callback = mCallbacks[i];
605 mCallbacks.RemoveElementAt(i);
607 if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
608 // Callback didn't fire, put it back and go to another one in line.
609 // Only reason InvokeCallback returns false is that onCacheEntryCheck
610 // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other
611 // readers or potential writers would be unnecessarily kept from being
612 // invoked.
613 mCallbacks.InsertElementAt(i, callback);
614 ++i;
618 return true;
621 bool CacheEntry::InvokeCallback(Callback & aCallback)
623 LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]",
624 this, StateString(mState), aCallback.mCallback.get()));
626 mLock.AssertCurrentThreadOwns();
628 // When this entry is doomed we want to notify the callback any time
629 if (!mIsDoomed) {
630 // When we are here, the entry must be loaded from disk
631 MOZ_ASSERT(mState > LOADING);
633 if (mState == WRITING || mState == REVALIDATING) {
634 // Prevent invoking other callbacks since one of them is now writing
635 // or revalidating this entry. No consumers should get this entry
636 // until metadata are filled with values downloaded from the server
637 // or the entry revalidated and output stream has been opened.
638 LOG((" entry is being written/revalidated, callback bypassed"));
639 return false;
642 // mRecheckAfterWrite flag already set means the callback has already passed
643 // the onCacheEntryCheck call. Until the current write is not finished this
644 // callback will be bypassed.
645 if (!aCallback.mRecheckAfterWrite) {
647 if (!aCallback.mReadOnly) {
648 if (mState == EMPTY) {
649 // Advance to writing state, we expect to invoke the callback and let
650 // it fill content of this entry. Must set and check the state here
651 // to prevent more then one
652 mState = WRITING;
653 LOG((" advancing to WRITING state"));
656 if (!aCallback.mCallback) {
657 // We can be given no callback only in case of recreate, it is ok
658 // to advance to WRITING state since the caller of recreate is expected
659 // to write this entry now.
660 return true;
664 if (mState == READY) {
665 // Metadata present, validate the entry
666 uint32_t checkResult;
668 // mayhemer: TODO check and solve any potential races of concurent OnCacheEntryCheck
669 mozilla::MutexAutoUnlock unlock(mLock);
671 nsresult rv = aCallback.mCallback->OnCacheEntryCheck(
672 this, nullptr, &checkResult);
673 LOG((" OnCacheEntryCheck: rv=0x%08x, result=%d", rv, checkResult));
675 if (NS_FAILED(rv))
676 checkResult = ENTRY_NOT_WANTED;
679 switch (checkResult) {
680 case ENTRY_WANTED:
681 // Nothing more to do here, the consumer is responsible to handle
682 // the result of OnCacheEntryCheck it self.
683 // Proceed to callback...
684 break;
686 case RECHECK_AFTER_WRITE_FINISHED:
687 LOG((" consumer will check on the entry again after write is done"));
688 // The consumer wants the entry to complete first.
689 aCallback.mRecheckAfterWrite = true;
690 break;
692 case ENTRY_NEEDS_REVALIDATION:
693 LOG((" will be holding callbacks until entry is revalidated"));
694 // State is READY now and from that state entry cannot transit to any other
695 // state then REVALIDATING for which cocurrency is not an issue. Potentially
696 // no need to lock here.
697 mState = REVALIDATING;
698 break;
700 case ENTRY_NOT_WANTED:
701 LOG((" consumer not interested in the entry"));
702 // Do not give this entry to the consumer, it is not interested in us.
703 aCallback.mNotWanted = true;
704 break;
710 if (aCallback.mCallback) {
711 if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
712 // If we don't have data and the callback wants a complete entry,
713 // don't invoke now.
714 bool bypass = !mHasData;
715 if (!bypass && NS_SUCCEEDED(mFileStatus)) {
716 int64_t _unused;
717 bypass = !mFile->DataSize(&_unused);
720 if (bypass) {
721 LOG((" bypassing, entry data still being written"));
722 return false;
725 // Entry is complete now, do the check+avail call again
726 aCallback.mRecheckAfterWrite = false;
727 return InvokeCallback(aCallback);
730 mozilla::MutexAutoUnlock unlock(mLock);
731 InvokeAvailableCallback(aCallback);
734 return true;
737 void CacheEntry::InvokeAvailableCallback(Callback const & aCallback)
739 LOG(("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, n/w=%d]",
740 this, StateString(mState), aCallback.mCallback.get(), aCallback.mReadOnly, aCallback.mNotWanted));
742 nsresult rv;
744 uint32_t const state = mState;
746 // When we are here, the entry must be loaded from disk
747 MOZ_ASSERT(state > LOADING || mIsDoomed);
749 bool onAvailThread;
750 rv = aCallback.OnAvailThread(&onAvailThread);
751 if (NS_FAILED(rv)) {
752 LOG((" target thread dead?"));
753 return;
756 if (!onAvailThread) {
757 // Dispatch to the right thread
758 nsRefPtr<AvailableCallbackRunnable> event =
759 new AvailableCallbackRunnable(this, aCallback);
761 rv = aCallback.mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
762 LOG((" redispatched, (rv = 0x%08x)", rv));
763 return;
766 if (NS_SUCCEEDED(mFileStatus) && !aCallback.mSecret) {
767 // Let the last-fetched and fetch-count properties be updated.
768 mFile->OnFetched();
771 if (mIsDoomed || aCallback.mNotWanted) {
772 LOG((" doomed or not wanted, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
773 aCallback.mCallback->OnCacheEntryAvailable(
774 nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
775 return;
778 if (state == READY) {
779 LOG((" ready/has-meta, notifying OCEA with entry and NS_OK"));
781 if (!aCallback.mSecret)
783 mozilla::MutexAutoLock lock(mLock);
784 BackgroundOp(Ops::FRECENCYUPDATE);
787 nsRefPtr<CacheEntryHandle> handle = NewHandle();
788 aCallback.mCallback->OnCacheEntryAvailable(
789 handle, false, nullptr, NS_OK);
790 return;
793 if (aCallback.mReadOnly) {
794 LOG((" r/o and not ready, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
795 aCallback.mCallback->OnCacheEntryAvailable(
796 nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
797 return;
800 // This is a new or potentially non-valid entry and needs to be fetched first.
801 // The CacheEntryHandle blocks other consumers until the channel
802 // either releases the entry or marks metadata as filled or whole entry valid,
803 // i.e. until MetaDataReady() or SetValid() on the entry is called respectively.
805 // Consumer will be responsible to fill or validate the entry metadata and data.
807 nsRefPtr<CacheEntryHandle> handle = NewWriteHandle();
808 rv = aCallback.mCallback->OnCacheEntryAvailable(
809 handle, state == WRITING, nullptr, NS_OK);
811 if (NS_FAILED(rv)) {
812 LOG((" writing/revalidating failed (0x%08x)", rv));
814 // Consumer given a new entry failed to take care of the entry.
815 OnHandleClosed(handle);
816 return;
819 LOG((" writing/revalidating"));
822 CacheEntryHandle* CacheEntry::NewHandle()
824 return new CacheEntryHandle(this);
827 CacheEntryHandle* CacheEntry::NewWriteHandle()
829 mozilla::MutexAutoLock lock(mLock);
831 // Ignore the OPEN_SECRETLY flag on purpose here, which should actually be
832 // used only along with OPEN_READONLY, but there is no need to enforce that.
833 BackgroundOp(Ops::FRECENCYUPDATE);
835 return (mWriter = NewHandle());
838 void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle)
840 LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this, StateString(mState), aHandle));
842 nsCOMPtr<nsIOutputStream> outputStream;
845 mozilla::MutexAutoLock lock(mLock);
847 if (mWriter != aHandle) {
848 LOG((" not the writer"));
849 return;
852 if (mOutputStream) {
853 // No one took our internal output stream, so there are no data
854 // and output stream has to be open symultaneously with input stream
855 // on this entry again.
856 mHasData = false;
859 outputStream.swap(mOutputStream);
860 mWriter = nullptr;
862 if (mState == WRITING) {
863 LOG((" reverting to state EMPTY - write failed"));
864 mState = EMPTY;
866 else if (mState == REVALIDATING) {
867 LOG((" reverting to state READY - reval failed"));
868 mState = READY;
871 if (mState == READY && !mHasData) {
872 // We may get to this state when following steps happen:
873 // 1. a new entry is given to a consumer
874 // 2. the consumer calls MetaDataReady(), we transit to READY
875 // 3. abandons the entry w/o opening the output stream, mHasData left false
877 // In this case any following consumer will get a ready entry (with metadata)
878 // but in state like the entry data write was still happening (was in progress)
879 // and will indefinitely wait for the entry data or even the entry itself when
880 // RECHECK_AFTER_WRITE is returned from onCacheEntryCheck.
881 LOG((" we are in READY state, pretend we have data regardless it"
882 " has actully been never touched"));
883 mHasData = true;
886 InvokeCallbacks();
889 if (outputStream) {
890 LOG((" abandoning phantom output stream"));
891 outputStream->Close();
895 void CacheEntry::OnOutputClosed()
897 // Called when the file's output stream is closed. Invoke any callbacks
898 // waiting for complete entry.
900 mozilla::MutexAutoLock lock(mLock);
901 InvokeCallbacks();
904 bool CacheEntry::IsReferenced() const
906 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
908 // Increasing this counter from 0 to non-null and this check both happen only
909 // under the service lock.
910 return mHandlesCount > 0;
913 bool CacheEntry::IsFileDoomed()
915 if (NS_SUCCEEDED(mFileStatus)) {
916 return mFile->IsDoomed();
919 return false;
922 uint32_t CacheEntry::GetMetadataMemoryConsumption()
924 NS_ENSURE_SUCCESS(mFileStatus, 0);
926 uint32_t size;
927 if (NS_FAILED(mFile->ElementsSize(&size)))
928 return 0;
930 return size;
933 // nsICacheEntry
935 NS_IMETHODIMP CacheEntry::GetPersistent(bool *aPersistToDisk)
937 // No need to sync when only reading.
938 // When consumer needs to be consistent with state of the memory storage entries
939 // table, then let it use GetUseDisk getter that must be called under the service lock.
940 *aPersistToDisk = mUseDisk;
941 return NS_OK;
944 NS_IMETHODIMP CacheEntry::GetKey(nsACString & aKey)
946 return mURI->GetAsciiSpec(aKey);
949 NS_IMETHODIMP CacheEntry::GetFetchCount(int32_t *aFetchCount)
951 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
953 return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount));
956 NS_IMETHODIMP CacheEntry::GetLastFetched(uint32_t *aLastFetched)
958 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
960 return mFile->GetLastFetched(aLastFetched);
963 NS_IMETHODIMP CacheEntry::GetLastModified(uint32_t *aLastModified)
965 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
967 return mFile->GetLastModified(aLastModified);
970 NS_IMETHODIMP CacheEntry::GetExpirationTime(uint32_t *aExpirationTime)
972 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
974 return mFile->GetExpirationTime(aExpirationTime);
977 NS_IMETHODIMP CacheEntry::GetIsForcedValid(bool *aIsForcedValid)
979 NS_ENSURE_ARG(aIsForcedValid);
981 nsAutoCString key;
983 nsresult rv = HashingKeyWithStorage(key);
984 if (NS_FAILED(rv)) {
985 return rv;
988 *aIsForcedValid = CacheStorageService::Self()->IsForcedValidEntry(key);
989 LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this, *aIsForcedValid));
991 return NS_OK;
994 NS_IMETHODIMP CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture)
996 LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this, aSecondsToTheFuture));
998 nsAutoCString key;
999 nsresult rv = HashingKeyWithStorage(key);
1000 if (NS_FAILED(rv)) {
1001 return rv;
1004 CacheStorageService::Self()->ForceEntryValidFor(key, aSecondsToTheFuture);
1006 return NS_OK;
1009 NS_IMETHODIMP CacheEntry::SetExpirationTime(uint32_t aExpirationTime)
1011 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1013 nsresult rv = mFile->SetExpirationTime(aExpirationTime);
1014 NS_ENSURE_SUCCESS(rv, rv);
1016 // Aligned assignment, thus atomic.
1017 mSortingExpirationTime = aExpirationTime;
1018 return NS_OK;
1021 NS_IMETHODIMP CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval)
1023 LOG(("CacheEntry::OpenInputStream [this=%p]", this));
1025 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1027 nsresult rv;
1029 nsCOMPtr<nsIInputStream> stream;
1030 rv = mFile->OpenInputStream(getter_AddRefs(stream));
1031 NS_ENSURE_SUCCESS(rv, rv);
1033 nsCOMPtr<nsISeekableStream> seekable =
1034 do_QueryInterface(stream, &rv);
1035 NS_ENSURE_SUCCESS(rv, rv);
1037 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1038 NS_ENSURE_SUCCESS(rv, rv);
1040 mozilla::MutexAutoLock lock(mLock);
1042 if (!mHasData) {
1043 // So far output stream on this new entry not opened, do it now.
1044 LOG((" creating phantom output stream"));
1045 rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
1046 NS_ENSURE_SUCCESS(rv, rv);
1049 stream.forget(_retval);
1050 return NS_OK;
1053 NS_IMETHODIMP CacheEntry::OpenOutputStream(int64_t offset, nsIOutputStream * *_retval)
1055 LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
1057 nsresult rv;
1059 mozilla::MutexAutoLock lock(mLock);
1061 MOZ_ASSERT(mState > EMPTY);
1063 if (mOutputStream && !mIsDoomed) {
1064 LOG((" giving phantom output stream"));
1065 mOutputStream.forget(_retval);
1067 else {
1068 rv = OpenOutputStreamInternal(offset, _retval);
1069 if (NS_FAILED(rv)) return rv;
1072 // Entry considered ready when writer opens output stream.
1073 if (mState < READY)
1074 mState = READY;
1076 // Invoke any pending readers now.
1077 InvokeCallbacks();
1079 return NS_OK;
1082 nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval)
1084 LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
1086 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1088 mLock.AssertCurrentThreadOwns();
1090 if (mIsDoomed) {
1091 LOG((" doomed..."));
1092 return NS_ERROR_NOT_AVAILABLE;
1095 MOZ_ASSERT(mState > LOADING);
1097 nsresult rv;
1099 // No need to sync on mUseDisk here, we don't need to be consistent
1100 // with content of the memory storage entries hash table.
1101 if (!mUseDisk) {
1102 rv = mFile->SetMemoryOnly();
1103 NS_ENSURE_SUCCESS(rv, rv);
1106 nsRefPtr<CacheOutputCloseListener> listener =
1107 new CacheOutputCloseListener(this);
1109 nsCOMPtr<nsIOutputStream> stream;
1110 rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
1111 NS_ENSURE_SUCCESS(rv, rv);
1113 nsCOMPtr<nsISeekableStream> seekable =
1114 do_QueryInterface(stream, &rv);
1115 NS_ENSURE_SUCCESS(rv, rv);
1117 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1118 NS_ENSURE_SUCCESS(rv, rv);
1120 // Prevent opening output stream again.
1121 mHasData = true;
1123 stream.swap(*_retval);
1124 return NS_OK;
1127 NS_IMETHODIMP CacheEntry::GetPredictedDataSize(int64_t *aPredictedDataSize)
1129 *aPredictedDataSize = mPredictedDataSize;
1130 return NS_OK;
1132 NS_IMETHODIMP CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize)
1134 mPredictedDataSize = aPredictedDataSize;
1136 if (CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) {
1137 LOG(("CacheEntry::SetPredictedDataSize [this=%p] too big, dooming", this));
1138 AsyncDoom(nullptr);
1140 return NS_ERROR_FILE_TOO_BIG;
1143 return NS_OK;
1146 NS_IMETHODIMP CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo)
1149 mozilla::MutexAutoLock lock(mLock);
1150 if (mSecurityInfoLoaded) {
1151 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1152 return NS_OK;
1156 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1158 nsXPIDLCString info;
1159 nsCOMPtr<nsISupports> secInfo;
1160 nsresult rv;
1162 rv = mFile->GetElement("security-info", getter_Copies(info));
1163 NS_ENSURE_SUCCESS(rv, rv);
1165 if (info) {
1166 rv = NS_DeserializeObject(info, getter_AddRefs(secInfo));
1167 NS_ENSURE_SUCCESS(rv, rv);
1171 mozilla::MutexAutoLock lock(mLock);
1173 mSecurityInfo.swap(secInfo);
1174 mSecurityInfoLoaded = true;
1176 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1179 return NS_OK;
1181 NS_IMETHODIMP CacheEntry::SetSecurityInfo(nsISupports *aSecurityInfo)
1183 nsresult rv;
1185 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1188 mozilla::MutexAutoLock lock(mLock);
1190 mSecurityInfo = aSecurityInfo;
1191 mSecurityInfoLoaded = true;
1194 nsCOMPtr<nsISerializable> serializable =
1195 do_QueryInterface(aSecurityInfo);
1196 if (aSecurityInfo && !serializable)
1197 return NS_ERROR_UNEXPECTED;
1199 nsCString info;
1200 if (serializable) {
1201 rv = NS_SerializeToString(serializable, info);
1202 NS_ENSURE_SUCCESS(rv, rv);
1205 rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
1206 NS_ENSURE_SUCCESS(rv, rv);
1208 return NS_OK;
1211 NS_IMETHODIMP CacheEntry::GetStorageDataSize(uint32_t *aStorageDataSize)
1213 NS_ENSURE_ARG(aStorageDataSize);
1215 int64_t dataSize;
1216 nsresult rv = GetDataSize(&dataSize);
1217 if (NS_FAILED(rv))
1218 return rv;
1220 *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
1222 return NS_OK;
1225 NS_IMETHODIMP CacheEntry::AsyncDoom(nsICacheEntryDoomCallback *aCallback)
1227 LOG(("CacheEntry::AsyncDoom [this=%p]", this));
1230 mozilla::MutexAutoLock lock(mLock);
1232 if (mIsDoomed || mDoomCallback)
1233 return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
1235 mIsDoomed = true;
1236 mDoomCallback = aCallback;
1239 // This immediately removes the entry from the master hashtable and also
1240 // immediately dooms the file. This way we make sure that any consumer
1241 // after this point asking for the same entry won't get
1242 // a) this entry
1243 // b) a new entry with the same file
1244 PurgeAndDoom();
1246 return NS_OK;
1249 NS_IMETHODIMP CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval)
1251 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1253 return mFile->GetElement(aKey, aRetval);
1256 NS_IMETHODIMP CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue)
1258 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1260 return mFile->SetElement(aKey, aValue);
1263 NS_IMETHODIMP CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor)
1265 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1267 return mFile->VisitMetaData(aVisitor);
1270 NS_IMETHODIMP CacheEntry::MetaDataReady()
1272 mozilla::MutexAutoLock lock(mLock);
1274 LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, StateString(mState)));
1276 MOZ_ASSERT(mState > EMPTY);
1278 if (mState == WRITING)
1279 mState = READY;
1281 InvokeCallbacks();
1283 return NS_OK;
1286 NS_IMETHODIMP CacheEntry::SetValid()
1288 LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState)));
1290 nsCOMPtr<nsIOutputStream> outputStream;
1293 mozilla::MutexAutoLock lock(mLock);
1295 MOZ_ASSERT(mState > EMPTY);
1297 mState = READY;
1298 mHasData = true;
1300 InvokeCallbacks();
1302 outputStream.swap(mOutputStream);
1305 if (outputStream) {
1306 LOG((" abandoning phantom output stream"));
1307 outputStream->Close();
1310 return NS_OK;
1313 NS_IMETHODIMP CacheEntry::Recreate(bool aMemoryOnly,
1314 nsICacheEntry **_retval)
1316 LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
1318 mozilla::MutexAutoLock lock(mLock);
1320 nsRefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
1321 if (handle) {
1322 handle.forget(_retval);
1323 return NS_OK;
1326 BackgroundOp(Ops::CALLBACKS, true);
1327 return NS_OK;
1330 NS_IMETHODIMP CacheEntry::GetDataSize(int64_t *aDataSize)
1332 LOG(("CacheEntry::GetDataSize [this=%p]", this));
1333 *aDataSize = 0;
1336 mozilla::MutexAutoLock lock(mLock);
1338 if (!mHasData) {
1339 LOG((" write in progress (no data)"));
1340 return NS_ERROR_IN_PROGRESS;
1344 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1346 // mayhemer: TODO Problem with compression?
1347 if (!mFile->DataSize(aDataSize)) {
1348 LOG((" write in progress (stream active)"));
1349 return NS_ERROR_IN_PROGRESS;
1352 LOG((" size=%lld", *aDataSize));
1353 return NS_OK;
1356 NS_IMETHODIMP CacheEntry::MarkValid()
1358 // NOT IMPLEMENTED ACTUALLY
1359 return NS_OK;
1362 NS_IMETHODIMP CacheEntry::MaybeMarkValid()
1364 // NOT IMPLEMENTED ACTUALLY
1365 return NS_OK;
1368 NS_IMETHODIMP CacheEntry::HasWriteAccess(bool aWriteAllowed, bool *aWriteAccess)
1370 *aWriteAccess = aWriteAllowed;
1371 return NS_OK;
1374 NS_IMETHODIMP CacheEntry::Close()
1376 // NOT IMPLEMENTED ACTUALLY
1377 return NS_OK;
1380 // nsIRunnable
1382 NS_IMETHODIMP CacheEntry::Run()
1384 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1386 mozilla::MutexAutoLock lock(mLock);
1388 BackgroundOp(mBackgroundOperations.Grab());
1389 return NS_OK;
1392 // Management methods
1394 double CacheEntry::GetFrecency() const
1396 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1397 return mFrecency;
1400 uint32_t CacheEntry::GetExpirationTime() const
1402 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1403 return mSortingExpirationTime;
1406 bool CacheEntry::IsRegistered() const
1408 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1409 return mRegistration == REGISTERED;
1412 bool CacheEntry::CanRegister() const
1414 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1415 return mRegistration == NEVERREGISTERED;
1418 void CacheEntry::SetRegistered(bool aRegistered)
1420 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1422 if (aRegistered) {
1423 MOZ_ASSERT(mRegistration == NEVERREGISTERED);
1424 mRegistration = REGISTERED;
1426 else {
1427 MOZ_ASSERT(mRegistration == REGISTERED);
1428 mRegistration = DEREGISTERED;
1432 bool CacheEntry::Purge(uint32_t aWhat)
1434 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
1436 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1438 switch (aWhat) {
1439 case PURGE_DATA_ONLY_DISK_BACKED:
1440 case PURGE_WHOLE_ONLY_DISK_BACKED:
1441 // This is an in-memory only entry, don't purge it
1442 if (!mUseDisk) {
1443 LOG((" not using disk"));
1444 return false;
1448 if (mState == WRITING || mState == LOADING || mFrecency == 0) {
1449 // In-progress (write or load) entries should (at least for consistency and from
1450 // the logical point of view) stay in memory.
1451 // Zero-frecency entries are those which have never been given to any consumer, those
1452 // are actually very fresh and should not go just because frecency had not been set
1453 // so far.
1454 LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency));
1455 return false;
1458 if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) {
1459 // The file is used when there are open streams or chunks/metadata still waiting for
1460 // write. In this case, this entry cannot be purged, otherwise reopenned entry
1461 // would may not even find the data on disk - CacheFile is not shared and cannot be
1462 // left orphan when its job is not done, hence keep the whole entry.
1463 LOG((" file still under use"));
1464 return false;
1467 switch (aWhat) {
1468 case PURGE_WHOLE_ONLY_DISK_BACKED:
1469 case PURGE_WHOLE:
1471 if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1472 LOG((" not purging, still referenced"));
1473 return false;
1476 CacheStorageService::Self()->UnregisterEntry(this);
1478 // Entry removed it self from control arrays, return true
1479 return true;
1482 case PURGE_DATA_ONLY_DISK_BACKED:
1484 NS_ENSURE_SUCCESS(mFileStatus, false);
1486 mFile->ThrowMemoryCachedData();
1488 // Entry has been left in control arrays, return false (not purged)
1489 return false;
1493 LOG((" ?"));
1494 return false;
1497 void CacheEntry::PurgeAndDoom()
1499 LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
1501 CacheStorageService::Self()->RemoveEntry(this);
1502 DoomAlreadyRemoved();
1505 void CacheEntry::DoomAlreadyRemoved()
1507 LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
1509 mozilla::MutexAutoLock lock(mLock);
1511 mIsDoomed = true;
1513 // This schedules dooming of the file, dooming is ensured to happen
1514 // sooner than demand to open the same file made after this point
1515 // so that we don't get this file for any newer opened entry(s).
1516 DoomFile();
1518 // Must force post here since may be indirectly called from
1519 // InvokeCallbacks of this entry and we don't want reentrancy here.
1520 BackgroundOp(Ops::CALLBACKS, true);
1521 // Process immediately when on the management thread.
1522 BackgroundOp(Ops::UNREGISTER);
1525 void CacheEntry::DoomFile()
1527 nsresult rv = NS_ERROR_NOT_AVAILABLE;
1529 if (NS_SUCCEEDED(mFileStatus)) {
1530 // Always calls the callback asynchronously.
1531 rv = mFile->Doom(mDoomCallback ? this : nullptr);
1532 if (NS_SUCCEEDED(rv)) {
1533 LOG((" file doomed"));
1534 return;
1537 if (NS_ERROR_FILE_NOT_FOUND == rv) {
1538 // File is set to be just memory-only, notify the callbacks
1539 // and pretend dooming has succeeded. From point of view of
1540 // the entry it actually did - the data is gone and cannot be
1541 // reused.
1542 rv = NS_OK;
1546 // Always posts to the main thread.
1547 OnFileDoomed(rv);
1550 void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync)
1552 mLock.AssertCurrentThreadOwns();
1554 if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
1555 if (mBackgroundOperations.Set(aOperations))
1556 CacheStorageService::Self()->Dispatch(this);
1558 LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
1559 return;
1563 mozilla::MutexAutoUnlock unlock(mLock);
1565 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1567 if (aOperations & Ops::FRECENCYUPDATE) {
1568 ++mUseCount;
1570 #ifndef M_LN2
1571 #define M_LN2 0.69314718055994530942
1572 #endif
1574 // Half-life is dynamic, in seconds.
1575 static double half_life = CacheObserver::HalfLifeSeconds();
1576 // Must convert from seconds to milliseconds since PR_Now() gives usecs.
1577 static double const decay = (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
1579 double now_decay = static_cast<double>(PR_Now()) * decay;
1581 if (mFrecency == 0) {
1582 mFrecency = now_decay;
1584 else {
1585 // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1) but
1586 // more precise.
1587 mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
1589 LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this, mFrecency));
1591 // Because CacheFile::Set*() are not thread-safe to use (uses WeakReference that
1592 // is not thread-safe) we must post to the main thread...
1593 nsRefPtr<nsRunnableMethod<CacheEntry> > event =
1594 NS_NewRunnableMethod(this, &CacheEntry::StoreFrecency);
1595 NS_DispatchToMainThread(event);
1598 if (aOperations & Ops::REGISTER) {
1599 LOG(("CacheEntry REGISTER [this=%p]", this));
1601 CacheStorageService::Self()->RegisterEntry(this);
1604 if (aOperations & Ops::UNREGISTER) {
1605 LOG(("CacheEntry UNREGISTER [this=%p]", this));
1607 CacheStorageService::Self()->UnregisterEntry(this);
1609 } // unlock
1611 if (aOperations & Ops::CALLBACKS) {
1612 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
1614 InvokeCallbacks();
1618 void CacheEntry::StoreFrecency()
1620 // No need for thread safety over mFrecency, it will be rewriten
1621 // correctly on following invocation if broken by concurrency.
1622 MOZ_ASSERT(NS_IsMainThread());
1624 if (NS_SUCCEEDED(mFileStatus)) {
1625 mFile->SetFrecency(FRECENCY2INT(mFrecency));
1629 // CacheOutputCloseListener
1631 CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
1632 : mEntry(aEntry)
1634 MOZ_COUNT_CTOR(CacheOutputCloseListener);
1637 CacheOutputCloseListener::~CacheOutputCloseListener()
1639 MOZ_COUNT_DTOR(CacheOutputCloseListener);
1642 void CacheOutputCloseListener::OnOutputClosed()
1644 // We need this class and to redispatch since this callback is invoked
1645 // under the file's lock and to do the job we need to enter the entry's
1646 // lock too. That would lead to potential deadlocks.
1647 NS_DispatchToCurrentThread(this);
1650 NS_IMETHODIMP CacheOutputCloseListener::Run()
1652 mEntry->OnOutputClosed();
1653 return NS_OK;
1656 // Memory reporting
1658 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1660 size_t n = 0;
1661 nsCOMPtr<nsISizeOf> sizeOf;
1663 n += mCallbacks.SizeOfExcludingThis(mallocSizeOf);
1664 if (mFile) {
1665 n += mFile->SizeOfIncludingThis(mallocSizeOf);
1668 sizeOf = do_QueryInterface(mURI);
1669 if (sizeOf) {
1670 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
1673 n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1674 n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1676 // mDoomCallback is an arbitrary class that is probably reported elsewhere.
1677 // mOutputStream is reported in mFile.
1678 // mWriter is one of many handles we create, but (intentionally) not keep
1679 // any reference to, so those unfortunatelly cannot be reported. Handles are
1680 // small, though.
1681 // mSecurityInfo doesn't impl nsISizeOf.
1683 return n;
1686 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1688 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1691 } // net
1692 } // mozilla