Bumping manifests a=b2g-bump
[gecko.git] / netwerk / cache2 / CacheEntry.cpp
blob9aacd15b1fa5456528b0e1919ff6b27f5b75b950
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 : mEntry(aEntry)
81 , mCallback(aCallback)
82 , mTargetThread(do_GetCurrentThread())
83 , mReadOnly(aReadOnly)
84 , mCheckOnAnyThread(aCheckOnAnyThread)
85 , mRecheckAfterWrite(false)
86 , mNotWanted(false)
88 MOZ_COUNT_CTOR(CacheEntry::Callback);
90 // The counter may go from zero to non-null only under the service lock
91 // but here we expect it to be already positive.
92 MOZ_ASSERT(mEntry->HandlesCount());
93 mEntry->AddHandleRef();
96 CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
97 : mEntry(aThat.mEntry)
98 , mCallback(aThat.mCallback)
99 , mTargetThread(aThat.mTargetThread)
100 , mReadOnly(aThat.mReadOnly)
101 , mCheckOnAnyThread(aThat.mCheckOnAnyThread)
102 , mRecheckAfterWrite(aThat.mRecheckAfterWrite)
103 , mNotWanted(aThat.mNotWanted)
105 MOZ_COUNT_CTOR(CacheEntry::Callback);
107 // The counter may go from zero to non-null only under the service lock
108 // but here we expect it to be already positive.
109 MOZ_ASSERT(mEntry->HandlesCount());
110 mEntry->AddHandleRef();
113 CacheEntry::Callback::~Callback()
115 ProxyRelease(mCallback, mTargetThread);
117 mEntry->ReleaseHandleRef();
118 MOZ_COUNT_DTOR(CacheEntry::Callback);
121 void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry)
123 if (mEntry == aEntry)
124 return;
126 // The counter may go from zero to non-null only under the service lock
127 // but here we expect it to be already positive.
128 MOZ_ASSERT(aEntry->HandlesCount());
129 aEntry->AddHandleRef();
130 mEntry->ReleaseHandleRef();
131 mEntry = aEntry;
134 nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const
136 if (!mCheckOnAnyThread) {
137 // Check we are on the target
138 return mTargetThread->IsOnCurrentThread(aOnCheckThread);
141 // We can invoke check anywhere
142 *aOnCheckThread = true;
143 return NS_OK;
146 nsresult CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread) const
148 return mTargetThread->IsOnCurrentThread(aOnAvailThread);
151 // CacheEntry
153 NS_IMPL_ISUPPORTS(CacheEntry,
154 nsICacheEntry,
155 nsIRunnable,
156 CacheFileListener)
158 CacheEntry::CacheEntry(const nsACString& aStorageID,
159 nsIURI* aURI,
160 const nsACString& aEnhanceID,
161 bool aUseDisk)
162 : mFrecency(0)
163 , mSortingExpirationTime(uint32_t(-1))
164 , mLock("CacheEntry")
165 , mFileStatus(NS_ERROR_NOT_INITIALIZED)
166 , mURI(aURI)
167 , mEnhanceID(aEnhanceID)
168 , mStorageID(aStorageID)
169 , mUseDisk(aUseDisk)
170 , mIsDoomed(false)
171 , mSecurityInfoLoaded(false)
172 , mPreventCallbacks(false)
173 , mHasData(false)
174 , mState(NOTLOADED)
175 , mRegistration(NEVERREGISTERED)
176 , mWriter(nullptr)
177 , mPredictedDataSize(0)
178 , mUseCount(0)
179 , mReleaseThread(NS_GetCurrentThread())
181 MOZ_COUNT_CTOR(CacheEntry);
183 mService = CacheStorageService::Self();
185 CacheStorageService::Self()->RecordMemoryOnlyEntry(
186 this, !aUseDisk, true /* overwrite */);
189 CacheEntry::~CacheEntry()
191 ProxyRelease(mURI, mReleaseThread);
193 LOG(("CacheEntry::~CacheEntry [this=%p]", this));
194 MOZ_COUNT_DTOR(CacheEntry);
197 #ifdef PR_LOG
199 char const * CacheEntry::StateString(uint32_t aState)
201 switch (aState) {
202 case NOTLOADED: return "NOTLOADED";
203 case LOADING: return "LOADING";
204 case EMPTY: return "EMPTY";
205 case WRITING: return "WRITING";
206 case READY: return "READY";
207 case REVALIDATING: return "REVALIDATING";
210 return "?";
213 #endif
215 nsresult CacheEntry::HashingKeyWithStorage(nsACString &aResult) const
217 return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
220 nsresult CacheEntry::HashingKey(nsACString &aResult) const
222 return HashingKey(EmptyCString(), mEnhanceID, mURI, aResult);
225 // static
226 nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
227 nsCSubstring const& aEnhanceID,
228 nsIURI* aURI,
229 nsACString &aResult)
231 nsAutoCString spec;
232 nsresult rv = aURI->GetAsciiSpec(spec);
233 NS_ENSURE_SUCCESS(rv, rv);
235 return HashingKey(aStorageID, aEnhanceID, spec, aResult);
238 // static
239 nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
240 nsCSubstring const& aEnhanceID,
241 nsCSubstring const& aURISpec,
242 nsACString &aResult)
245 * This key is used to salt hash that is a base for disk file name.
246 * Changing it will cause we will not be able to find files on disk.
249 aResult.Append(aStorageID);
251 if (!aEnhanceID.IsEmpty()) {
252 CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID);
255 // Appending directly
256 aResult.Append(':');
257 aResult.Append(aURISpec);
259 return NS_OK;
262 void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags)
264 LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]",
265 this, StateString(mState), aFlags, aCallback));
267 bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
268 bool bypassIfBusy = aFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY;
269 bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
270 bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
271 bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
273 MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
274 MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry");
276 Callback callback(this, aCallback, readonly, multithread);
278 if (!Open(callback, truncate, priority, bypassIfBusy)) {
279 // We get here when the callback wants to bypass cache when it's busy.
280 LOG((" writing or revalidating, callback wants to bypass cache"));
281 callback.mNotWanted = true;
282 InvokeAvailableCallback(callback);
286 bool CacheEntry::Open(Callback & aCallback, bool aTruncate,
287 bool aPriority, bool aBypassIfBusy)
289 mozilla::MutexAutoLock lock(mLock);
291 // Check state under the lock
292 if (aBypassIfBusy && (mState == WRITING || mState == REVALIDATING)) {
293 return false;
296 RememberCallback(aCallback);
298 // Load() opens the lock
299 if (Load(aTruncate, aPriority)) {
300 // Loading is in progress...
301 return true;
304 InvokeCallbacks();
306 return true;
309 bool CacheEntry::Load(bool aTruncate, bool aPriority)
311 LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
313 mLock.AssertCurrentThreadOwns();
315 if (mState > LOADING) {
316 LOG((" already loaded"));
317 return false;
320 if (mState == LOADING) {
321 LOG((" already loading"));
322 return true;
325 mState = LOADING;
327 MOZ_ASSERT(!mFile);
329 nsresult rv;
331 nsAutoCString fileKey;
332 rv = HashingKeyWithStorage(fileKey);
334 // Check the index under two conditions for two states and take appropriate action:
335 // 1. When this is a disk entry and not told to truncate, check there is a disk file.
336 // If not, set the 'truncate' flag to true so that this entry will open instantly
337 // as a new one.
338 // 2. When this is a memory-only entry, check there is a disk file.
339 // If there is or could be, doom that file.
340 if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) {
341 // Check the index right now to know we have or have not the entry
342 // as soon as possible.
343 CacheIndex::EntryStatus status;
344 if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) {
345 switch (status) {
346 case CacheIndex::DOES_NOT_EXIST:
347 LOG((" entry doesn't exist according information from the index, truncating"));
348 aTruncate = true;
349 break;
350 case CacheIndex::EXISTS:
351 case CacheIndex::DO_NOT_KNOW:
352 if (!mUseDisk) {
353 LOG((" entry open as memory-only, but there is (status=%d) a file, dooming it", status));
354 CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
356 break;
361 mFile = new CacheFile();
363 BackgroundOp(Ops::REGISTER);
365 bool directLoad = aTruncate || !mUseDisk;
366 if (directLoad) {
367 mFileStatus = NS_OK;
368 // mLoadStart will be used to calculate telemetry of life-time of this entry.
369 // Low resulution is then enough.
370 mLoadStart = TimeStamp::NowLoRes();
371 } else {
372 mLoadStart = TimeStamp::Now();
376 mozilla::MutexAutoUnlock unlock(mLock);
378 LOG((" performing load, file=%p", mFile.get()));
379 if (NS_SUCCEEDED(rv)) {
380 rv = mFile->Init(fileKey,
381 aTruncate,
382 !mUseDisk,
383 aPriority,
384 directLoad ? nullptr : this);
387 if (NS_FAILED(rv)) {
388 mFileStatus = rv;
389 AsyncDoom(nullptr);
390 return false;
394 if (directLoad) {
395 // Just fake the load has already been done as "new".
396 mState = EMPTY;
399 return mState == LOADING;
402 NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
404 LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08x, new=%d]",
405 this, aResult, aIsNew));
407 MOZ_ASSERT(!mLoadStart.IsNull());
409 if (NS_SUCCEEDED(aResult)) {
410 if (aIsNew) {
411 mozilla::Telemetry::AccumulateTimeDelta(
412 mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS,
413 mLoadStart);
415 else {
416 mozilla::Telemetry::AccumulateTimeDelta(
417 mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS,
418 mLoadStart);
422 // OnFileReady, that is the only code that can transit from LOADING
423 // to any follow-on state, can only be invoked ones on an entry,
424 // thus no need to lock. Until this moment there is no consumer that
425 // could manipulate the entry state.
426 mozilla::MutexAutoLock lock(mLock);
428 MOZ_ASSERT(mState == LOADING);
430 mState = (aIsNew || NS_FAILED(aResult))
431 ? EMPTY
432 : READY;
434 mFileStatus = aResult;
436 if (mState == READY) {
437 mHasData = true;
439 uint32_t frecency;
440 mFile->GetFrecency(&frecency);
441 // mFrecency is held in a double to increase computance precision.
442 // It is ok to persist frecency only as a uint32 with some math involved.
443 mFrecency = INT2FRECENCY(frecency);
446 InvokeCallbacks();
447 return NS_OK;
450 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult)
452 if (mDoomCallback) {
453 nsRefPtr<DoomCallbackRunnable> event =
454 new DoomCallbackRunnable(this, aResult);
455 NS_DispatchToMainThread(event);
458 return NS_OK;
461 already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(bool aMemoryOnly,
462 nsICacheEntryOpenCallback* aCallback)
464 LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
466 mLock.AssertCurrentThreadOwns();
468 // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly
469 mPreventCallbacks = true;
471 nsRefPtr<CacheEntryHandle> handle;
472 nsRefPtr<CacheEntry> newEntry;
474 mozilla::MutexAutoUnlock unlock(mLock);
476 // The following call dooms this entry (calls DoomAlreadyRemoved on us)
477 nsresult rv = CacheStorageService::Self()->AddStorageEntry(
478 GetStorageID(), GetURI(), GetEnhanceID(),
479 mUseDisk && !aMemoryOnly,
480 true, // always create
481 true, // truncate existing (this one)
482 getter_AddRefs(handle));
484 if (NS_SUCCEEDED(rv)) {
485 newEntry = handle->Entry();
486 LOG((" exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv));
487 newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
488 } else {
489 LOG((" exchanged of entry %p failed, rv=0x%08x", this, rv));
490 AsyncDoom(nullptr);
494 mPreventCallbacks = false;
496 if (!newEntry)
497 return nullptr;
499 newEntry->TransferCallbacks(*this);
500 mCallbacks.Clear();
502 // Must return a new write handle, since the consumer is expected to
503 // write to this newly recreated entry. The |handle| is only a common
504 // reference counter and doesn't revert entry state back when write
505 // fails and also doesn't update the entry frecency. Not updating
506 // frecency causes entries to not be purged from our memory pools.
507 nsRefPtr<CacheEntryHandle> writeHandle =
508 newEntry->NewWriteHandle();
509 return writeHandle.forget();
512 void CacheEntry::TransferCallbacks(CacheEntry & aFromEntry)
514 mozilla::MutexAutoLock lock(mLock);
516 LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]",
517 this, &aFromEntry));
519 if (!mCallbacks.Length())
520 mCallbacks.SwapElements(aFromEntry.mCallbacks);
521 else
522 mCallbacks.AppendElements(aFromEntry.mCallbacks);
524 uint32_t callbacksLength = mCallbacks.Length();
525 if (callbacksLength) {
526 // Carry the entry reference (unfortunatelly, needs to be done manually...)
527 for (uint32_t i = 0; i < callbacksLength; ++i)
528 mCallbacks[i].ExchangeEntry(this);
530 BackgroundOp(Ops::CALLBACKS, true);
534 void CacheEntry::RememberCallback(Callback & aCallback)
536 mLock.AssertCurrentThreadOwns();
538 LOG(("CacheEntry::RememberCallback [this=%p, cb=%p, state=%s]",
539 this, aCallback.mCallback.get(), StateString(mState)));
541 mCallbacks.AppendElement(aCallback);
544 void CacheEntry::InvokeCallbacksLock()
546 mozilla::MutexAutoLock lock(mLock);
547 InvokeCallbacks();
550 void CacheEntry::InvokeCallbacks()
552 mLock.AssertCurrentThreadOwns();
554 LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
556 // Invoke first all r/w callbacks, then all r/o callbacks.
557 if (InvokeCallbacks(false))
558 InvokeCallbacks(true);
560 LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
563 bool CacheEntry::InvokeCallbacks(bool aReadOnly)
565 mLock.AssertCurrentThreadOwns();
567 uint32_t i = 0;
568 while (i < mCallbacks.Length()) {
569 if (mPreventCallbacks) {
570 LOG((" callbacks prevented!"));
571 return false;
574 if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
575 LOG((" entry is being written/revalidated"));
576 return false;
579 if (mCallbacks[i].mReadOnly != aReadOnly) {
580 // Callback is not r/w or r/o, go to another one in line
581 ++i;
582 continue;
585 bool onCheckThread;
586 nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
588 if (NS_SUCCEEDED(rv) && !onCheckThread) {
589 // Redispatch to the target thread
590 nsRefPtr<nsRunnableMethod<CacheEntry> > event =
591 NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksLock);
593 rv = mCallbacks[i].mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
594 if (NS_SUCCEEDED(rv)) {
595 LOG((" re-dispatching to target thread"));
596 return false;
600 Callback callback = mCallbacks[i];
601 mCallbacks.RemoveElementAt(i);
603 if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
604 // Callback didn't fire, put it back and go to another one in line.
605 // Only reason InvokeCallback returns false is that onCacheEntryCheck
606 // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other
607 // readers or potential writers would be unnecessarily kept from being
608 // invoked.
609 mCallbacks.InsertElementAt(i, callback);
610 ++i;
614 return true;
617 bool CacheEntry::InvokeCallback(Callback & aCallback)
619 LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]",
620 this, StateString(mState), aCallback.mCallback.get()));
622 mLock.AssertCurrentThreadOwns();
624 // When this entry is doomed we want to notify the callback any time
625 if (!mIsDoomed) {
626 // When we are here, the entry must be loaded from disk
627 MOZ_ASSERT(mState > LOADING);
629 if (mState == WRITING || mState == REVALIDATING) {
630 // Prevent invoking other callbacks since one of them is now writing
631 // or revalidating this entry. No consumers should get this entry
632 // until metadata are filled with values downloaded from the server
633 // or the entry revalidated and output stream has been opened.
634 LOG((" entry is being written/revalidated, callback bypassed"));
635 return false;
638 // mRecheckAfterWrite flag already set means the callback has already passed
639 // the onCacheEntryCheck call. Until the current write is not finished this
640 // callback will be bypassed.
641 if (!aCallback.mRecheckAfterWrite) {
643 if (!aCallback.mReadOnly) {
644 if (mState == EMPTY) {
645 // Advance to writing state, we expect to invoke the callback and let
646 // it fill content of this entry. Must set and check the state here
647 // to prevent more then one
648 mState = WRITING;
649 LOG((" advancing to WRITING state"));
652 if (!aCallback.mCallback) {
653 // We can be given no callback only in case of recreate, it is ok
654 // to advance to WRITING state since the caller of recreate is expected
655 // to write this entry now.
656 return true;
660 if (mState == READY) {
661 // Metadata present, validate the entry
662 uint32_t checkResult;
664 // mayhemer: TODO check and solve any potential races of concurent OnCacheEntryCheck
665 mozilla::MutexAutoUnlock unlock(mLock);
667 nsresult rv = aCallback.mCallback->OnCacheEntryCheck(
668 this, nullptr, &checkResult);
669 LOG((" OnCacheEntryCheck: rv=0x%08x, result=%d", rv, checkResult));
671 if (NS_FAILED(rv))
672 checkResult = ENTRY_NOT_WANTED;
675 switch (checkResult) {
676 case ENTRY_WANTED:
677 // Nothing more to do here, the consumer is responsible to handle
678 // the result of OnCacheEntryCheck it self.
679 // Proceed to callback...
680 break;
682 case RECHECK_AFTER_WRITE_FINISHED:
683 LOG((" consumer will check on the entry again after write is done"));
684 // The consumer wants the entry to complete first.
685 aCallback.mRecheckAfterWrite = true;
686 break;
688 case ENTRY_NEEDS_REVALIDATION:
689 LOG((" will be holding callbacks until entry is revalidated"));
690 // State is READY now and from that state entry cannot transit to any other
691 // state then REVALIDATING for which cocurrency is not an issue. Potentially
692 // no need to lock here.
693 mState = REVALIDATING;
694 break;
696 case ENTRY_NOT_WANTED:
697 LOG((" consumer not interested in the entry"));
698 // Do not give this entry to the consumer, it is not interested in us.
699 aCallback.mNotWanted = true;
700 break;
706 if (aCallback.mCallback) {
707 if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
708 // If we don't have data and the callback wants a complete entry,
709 // don't invoke now.
710 bool bypass = !mHasData;
711 if (!bypass && NS_SUCCEEDED(mFileStatus)) {
712 int64_t _unused;
713 bypass = !mFile->DataSize(&_unused);
716 if (bypass) {
717 LOG((" bypassing, entry data still being written"));
718 return false;
721 // Entry is complete now, do the check+avail call again
722 aCallback.mRecheckAfterWrite = false;
723 return InvokeCallback(aCallback);
726 mozilla::MutexAutoUnlock unlock(mLock);
727 InvokeAvailableCallback(aCallback);
730 return true;
733 void CacheEntry::InvokeAvailableCallback(Callback const & aCallback)
735 LOG(("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, n/w=%d]",
736 this, StateString(mState), aCallback.mCallback.get(), aCallback.mReadOnly, aCallback.mNotWanted));
738 nsresult rv;
740 uint32_t const state = mState;
742 // When we are here, the entry must be loaded from disk
743 MOZ_ASSERT(state > LOADING || mIsDoomed);
745 bool onAvailThread;
746 rv = aCallback.OnAvailThread(&onAvailThread);
747 if (NS_FAILED(rv)) {
748 LOG((" target thread dead?"));
749 return;
752 if (!onAvailThread) {
753 // Dispatch to the right thread
754 nsRefPtr<AvailableCallbackRunnable> event =
755 new AvailableCallbackRunnable(this, aCallback);
757 rv = aCallback.mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
758 LOG((" redispatched, (rv = 0x%08x)", rv));
759 return;
762 if (NS_SUCCEEDED(mFileStatus)) {
763 // Let the last-fetched and fetch-count properties be updated.
764 mFile->OnFetched();
767 if (mIsDoomed || aCallback.mNotWanted) {
768 LOG((" doomed or not wanted, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
769 aCallback.mCallback->OnCacheEntryAvailable(
770 nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
771 return;
774 if (state == READY) {
775 LOG((" ready/has-meta, notifying OCEA with entry and NS_OK"));
777 mozilla::MutexAutoLock lock(mLock);
778 BackgroundOp(Ops::FRECENCYUPDATE);
781 nsRefPtr<CacheEntryHandle> handle = NewHandle();
782 aCallback.mCallback->OnCacheEntryAvailable(
783 handle, false, nullptr, NS_OK);
784 return;
787 if (aCallback.mReadOnly) {
788 LOG((" r/o and not ready, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
789 aCallback.mCallback->OnCacheEntryAvailable(
790 nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
791 return;
794 // This is a new or potentially non-valid entry and needs to be fetched first.
795 // The CacheEntryHandle blocks other consumers until the channel
796 // either releases the entry or marks metadata as filled or whole entry valid,
797 // i.e. until MetaDataReady() or SetValid() on the entry is called respectively.
799 // Consumer will be responsible to fill or validate the entry metadata and data.
801 nsRefPtr<CacheEntryHandle> handle = NewWriteHandle();
802 rv = aCallback.mCallback->OnCacheEntryAvailable(
803 handle, state == WRITING, nullptr, NS_OK);
805 if (NS_FAILED(rv)) {
806 LOG((" writing/revalidating failed (0x%08x)", rv));
808 // Consumer given a new entry failed to take care of the entry.
809 OnHandleClosed(handle);
810 return;
813 LOG((" writing/revalidating"));
816 CacheEntryHandle* CacheEntry::NewHandle()
818 return new CacheEntryHandle(this);
821 CacheEntryHandle* CacheEntry::NewWriteHandle()
823 mozilla::MutexAutoLock lock(mLock);
825 BackgroundOp(Ops::FRECENCYUPDATE);
826 return (mWriter = NewHandle());
829 void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle)
831 LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this, StateString(mState), aHandle));
833 nsCOMPtr<nsIOutputStream> outputStream;
836 mozilla::MutexAutoLock lock(mLock);
838 if (mWriter != aHandle) {
839 LOG((" not the writer"));
840 return;
843 if (mOutputStream) {
844 // No one took our internal output stream, so there are no data
845 // and output stream has to be open symultaneously with input stream
846 // on this entry again.
847 mHasData = false;
850 outputStream.swap(mOutputStream);
851 mWriter = nullptr;
853 if (mState == WRITING) {
854 LOG((" reverting to state EMPTY - write failed"));
855 mState = EMPTY;
857 else if (mState == REVALIDATING) {
858 LOG((" reverting to state READY - reval failed"));
859 mState = READY;
862 if (mState == READY && !mHasData) {
863 // We may get to this state when following steps happen:
864 // 1. a new entry is given to a consumer
865 // 2. the consumer calls MetaDataReady(), we transit to READY
866 // 3. abandons the entry w/o opening the output stream, mHasData left false
868 // In this case any following consumer will get a ready entry (with metadata)
869 // but in state like the entry data write was still happening (was in progress)
870 // and will indefinitely wait for the entry data or even the entry itself when
871 // RECHECK_AFTER_WRITE is returned from onCacheEntryCheck.
872 LOG((" we are in READY state, pretend we have data regardless it"
873 " has actully been never touched"));
874 mHasData = true;
877 InvokeCallbacks();
880 if (outputStream) {
881 LOG((" abandoning phantom output stream"));
882 outputStream->Close();
886 void CacheEntry::OnOutputClosed()
888 // Called when the file's output stream is closed. Invoke any callbacks
889 // waiting for complete entry.
891 mozilla::MutexAutoLock lock(mLock);
892 InvokeCallbacks();
895 bool CacheEntry::IsReferenced() const
897 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
899 // Increasing this counter from 0 to non-null and this check both happen only
900 // under the service lock.
901 return mHandlesCount > 0;
904 bool CacheEntry::IsFileDoomed()
906 if (NS_SUCCEEDED(mFileStatus)) {
907 return mFile->IsDoomed();
910 return false;
913 uint32_t CacheEntry::GetMetadataMemoryConsumption()
915 NS_ENSURE_SUCCESS(mFileStatus, 0);
917 uint32_t size;
918 if (NS_FAILED(mFile->ElementsSize(&size)))
919 return 0;
921 return size;
924 // nsICacheEntry
926 NS_IMETHODIMP CacheEntry::GetPersistent(bool *aPersistToDisk)
928 // No need to sync when only reading.
929 // When consumer needs to be consistent with state of the memory storage entries
930 // table, then let it use GetUseDisk getter that must be called under the service lock.
931 *aPersistToDisk = mUseDisk;
932 return NS_OK;
935 NS_IMETHODIMP CacheEntry::GetKey(nsACString & aKey)
937 return mURI->GetAsciiSpec(aKey);
940 NS_IMETHODIMP CacheEntry::GetFetchCount(int32_t *aFetchCount)
942 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
944 return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount));
947 NS_IMETHODIMP CacheEntry::GetLastFetched(uint32_t *aLastFetched)
949 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
951 return mFile->GetLastFetched(aLastFetched);
954 NS_IMETHODIMP CacheEntry::GetLastModified(uint32_t *aLastModified)
956 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
958 return mFile->GetLastModified(aLastModified);
961 NS_IMETHODIMP CacheEntry::GetExpirationTime(uint32_t *aExpirationTime)
963 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
965 return mFile->GetExpirationTime(aExpirationTime);
968 NS_IMETHODIMP CacheEntry::GetIsForcedValid(bool *aIsForcedValid)
970 NS_ENSURE_ARG(aIsForcedValid);
972 nsAutoCString key;
974 nsresult rv = HashingKeyWithStorage(key);
975 if (NS_FAILED(rv)) {
976 return rv;
979 *aIsForcedValid = CacheStorageService::Self()->IsForcedValidEntry(key);
980 LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this, *aIsForcedValid));
982 return NS_OK;
985 NS_IMETHODIMP CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture)
987 LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this, aSecondsToTheFuture));
989 nsAutoCString key;
990 nsresult rv = HashingKeyWithStorage(key);
991 if (NS_FAILED(rv)) {
992 return rv;
995 CacheStorageService::Self()->ForceEntryValidFor(key, aSecondsToTheFuture);
997 return NS_OK;
1000 NS_IMETHODIMP CacheEntry::SetExpirationTime(uint32_t aExpirationTime)
1002 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1004 nsresult rv = mFile->SetExpirationTime(aExpirationTime);
1005 NS_ENSURE_SUCCESS(rv, rv);
1007 // Aligned assignment, thus atomic.
1008 mSortingExpirationTime = aExpirationTime;
1009 return NS_OK;
1012 NS_IMETHODIMP CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval)
1014 LOG(("CacheEntry::OpenInputStream [this=%p]", this));
1016 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1018 nsresult rv;
1020 nsCOMPtr<nsIInputStream> stream;
1021 rv = mFile->OpenInputStream(getter_AddRefs(stream));
1022 NS_ENSURE_SUCCESS(rv, rv);
1024 nsCOMPtr<nsISeekableStream> seekable =
1025 do_QueryInterface(stream, &rv);
1026 NS_ENSURE_SUCCESS(rv, rv);
1028 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1029 NS_ENSURE_SUCCESS(rv, rv);
1031 mozilla::MutexAutoLock lock(mLock);
1033 if (!mHasData) {
1034 // So far output stream on this new entry not opened, do it now.
1035 LOG((" creating phantom output stream"));
1036 rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
1037 NS_ENSURE_SUCCESS(rv, rv);
1040 stream.forget(_retval);
1041 return NS_OK;
1044 NS_IMETHODIMP CacheEntry::OpenOutputStream(int64_t offset, nsIOutputStream * *_retval)
1046 LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
1048 nsresult rv;
1050 mozilla::MutexAutoLock lock(mLock);
1052 MOZ_ASSERT(mState > EMPTY);
1054 if (mOutputStream && !mIsDoomed) {
1055 LOG((" giving phantom output stream"));
1056 mOutputStream.forget(_retval);
1058 else {
1059 rv = OpenOutputStreamInternal(offset, _retval);
1060 if (NS_FAILED(rv)) return rv;
1063 // Entry considered ready when writer opens output stream.
1064 if (mState < READY)
1065 mState = READY;
1067 // Invoke any pending readers now.
1068 InvokeCallbacks();
1070 return NS_OK;
1073 nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval)
1075 LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
1077 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1079 mLock.AssertCurrentThreadOwns();
1081 if (mIsDoomed) {
1082 LOG((" doomed..."));
1083 return NS_ERROR_NOT_AVAILABLE;
1086 MOZ_ASSERT(mState > LOADING);
1088 nsresult rv;
1090 // No need to sync on mUseDisk here, we don't need to be consistent
1091 // with content of the memory storage entries hash table.
1092 if (!mUseDisk) {
1093 rv = mFile->SetMemoryOnly();
1094 NS_ENSURE_SUCCESS(rv, rv);
1097 nsRefPtr<CacheOutputCloseListener> listener =
1098 new CacheOutputCloseListener(this);
1100 nsCOMPtr<nsIOutputStream> stream;
1101 rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
1102 NS_ENSURE_SUCCESS(rv, rv);
1104 nsCOMPtr<nsISeekableStream> seekable =
1105 do_QueryInterface(stream, &rv);
1106 NS_ENSURE_SUCCESS(rv, rv);
1108 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1109 NS_ENSURE_SUCCESS(rv, rv);
1111 // Prevent opening output stream again.
1112 mHasData = true;
1114 stream.swap(*_retval);
1115 return NS_OK;
1118 NS_IMETHODIMP CacheEntry::GetPredictedDataSize(int64_t *aPredictedDataSize)
1120 *aPredictedDataSize = mPredictedDataSize;
1121 return NS_OK;
1123 NS_IMETHODIMP CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize)
1125 mPredictedDataSize = aPredictedDataSize;
1127 if (CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) {
1128 LOG(("CacheEntry::SetPredictedDataSize [this=%p] too big, dooming", this));
1129 AsyncDoom(nullptr);
1131 return NS_ERROR_FILE_TOO_BIG;
1134 return NS_OK;
1137 NS_IMETHODIMP CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo)
1140 mozilla::MutexAutoLock lock(mLock);
1141 if (mSecurityInfoLoaded) {
1142 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1143 return NS_OK;
1147 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1149 nsXPIDLCString info;
1150 nsCOMPtr<nsISupports> secInfo;
1151 nsresult rv;
1153 rv = mFile->GetElement("security-info", getter_Copies(info));
1154 NS_ENSURE_SUCCESS(rv, rv);
1156 if (info) {
1157 rv = NS_DeserializeObject(info, getter_AddRefs(secInfo));
1158 NS_ENSURE_SUCCESS(rv, rv);
1162 mozilla::MutexAutoLock lock(mLock);
1164 mSecurityInfo.swap(secInfo);
1165 mSecurityInfoLoaded = true;
1167 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1170 return NS_OK;
1172 NS_IMETHODIMP CacheEntry::SetSecurityInfo(nsISupports *aSecurityInfo)
1174 nsresult rv;
1176 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1179 mozilla::MutexAutoLock lock(mLock);
1181 mSecurityInfo = aSecurityInfo;
1182 mSecurityInfoLoaded = true;
1185 nsCOMPtr<nsISerializable> serializable =
1186 do_QueryInterface(aSecurityInfo);
1187 if (aSecurityInfo && !serializable)
1188 return NS_ERROR_UNEXPECTED;
1190 nsCString info;
1191 if (serializable) {
1192 rv = NS_SerializeToString(serializable, info);
1193 NS_ENSURE_SUCCESS(rv, rv);
1196 rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
1197 NS_ENSURE_SUCCESS(rv, rv);
1199 return NS_OK;
1202 NS_IMETHODIMP CacheEntry::GetStorageDataSize(uint32_t *aStorageDataSize)
1204 NS_ENSURE_ARG(aStorageDataSize);
1206 int64_t dataSize;
1207 nsresult rv = GetDataSize(&dataSize);
1208 if (NS_FAILED(rv))
1209 return rv;
1211 *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
1213 return NS_OK;
1216 NS_IMETHODIMP CacheEntry::AsyncDoom(nsICacheEntryDoomCallback *aCallback)
1218 LOG(("CacheEntry::AsyncDoom [this=%p]", this));
1221 mozilla::MutexAutoLock lock(mLock);
1223 if (mIsDoomed || mDoomCallback)
1224 return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
1226 mIsDoomed = true;
1227 mDoomCallback = aCallback;
1230 // This immediately removes the entry from the master hashtable and also
1231 // immediately dooms the file. This way we make sure that any consumer
1232 // after this point asking for the same entry won't get
1233 // a) this entry
1234 // b) a new entry with the same file
1235 PurgeAndDoom();
1237 return NS_OK;
1240 NS_IMETHODIMP CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval)
1242 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1244 return mFile->GetElement(aKey, aRetval);
1247 NS_IMETHODIMP CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue)
1249 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1251 return mFile->SetElement(aKey, aValue);
1254 NS_IMETHODIMP CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor)
1256 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1258 return mFile->VisitMetaData(aVisitor);
1261 NS_IMETHODIMP CacheEntry::MetaDataReady()
1263 mozilla::MutexAutoLock lock(mLock);
1265 LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, StateString(mState)));
1267 MOZ_ASSERT(mState > EMPTY);
1269 if (mState == WRITING)
1270 mState = READY;
1272 InvokeCallbacks();
1274 return NS_OK;
1277 NS_IMETHODIMP CacheEntry::SetValid()
1279 LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState)));
1281 nsCOMPtr<nsIOutputStream> outputStream;
1284 mozilla::MutexAutoLock lock(mLock);
1286 MOZ_ASSERT(mState > EMPTY);
1288 mState = READY;
1289 mHasData = true;
1291 InvokeCallbacks();
1293 outputStream.swap(mOutputStream);
1296 if (outputStream) {
1297 LOG((" abandoning phantom output stream"));
1298 outputStream->Close();
1301 return NS_OK;
1304 NS_IMETHODIMP CacheEntry::Recreate(bool aMemoryOnly,
1305 nsICacheEntry **_retval)
1307 LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
1309 mozilla::MutexAutoLock lock(mLock);
1311 nsRefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
1312 if (handle) {
1313 handle.forget(_retval);
1314 return NS_OK;
1317 BackgroundOp(Ops::CALLBACKS, true);
1318 return NS_OK;
1321 NS_IMETHODIMP CacheEntry::GetDataSize(int64_t *aDataSize)
1323 LOG(("CacheEntry::GetDataSize [this=%p]", this));
1324 *aDataSize = 0;
1327 mozilla::MutexAutoLock lock(mLock);
1329 if (!mHasData) {
1330 LOG((" write in progress (no data)"));
1331 return NS_ERROR_IN_PROGRESS;
1335 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1337 // mayhemer: TODO Problem with compression?
1338 if (!mFile->DataSize(aDataSize)) {
1339 LOG((" write in progress (stream active)"));
1340 return NS_ERROR_IN_PROGRESS;
1343 LOG((" size=%lld", *aDataSize));
1344 return NS_OK;
1347 NS_IMETHODIMP CacheEntry::MarkValid()
1349 // NOT IMPLEMENTED ACTUALLY
1350 return NS_OK;
1353 NS_IMETHODIMP CacheEntry::MaybeMarkValid()
1355 // NOT IMPLEMENTED ACTUALLY
1356 return NS_OK;
1359 NS_IMETHODIMP CacheEntry::HasWriteAccess(bool aWriteAllowed, bool *aWriteAccess)
1361 *aWriteAccess = aWriteAllowed;
1362 return NS_OK;
1365 NS_IMETHODIMP CacheEntry::Close()
1367 // NOT IMPLEMENTED ACTUALLY
1368 return NS_OK;
1371 // nsIRunnable
1373 NS_IMETHODIMP CacheEntry::Run()
1375 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1377 mozilla::MutexAutoLock lock(mLock);
1379 BackgroundOp(mBackgroundOperations.Grab());
1380 return NS_OK;
1383 // Management methods
1385 double CacheEntry::GetFrecency() const
1387 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1388 return mFrecency;
1391 uint32_t CacheEntry::GetExpirationTime() const
1393 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1394 return mSortingExpirationTime;
1397 bool CacheEntry::IsRegistered() const
1399 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1400 return mRegistration == REGISTERED;
1403 bool CacheEntry::CanRegister() const
1405 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1406 return mRegistration == NEVERREGISTERED;
1409 void CacheEntry::SetRegistered(bool aRegistered)
1411 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1413 if (aRegistered) {
1414 MOZ_ASSERT(mRegistration == NEVERREGISTERED);
1415 mRegistration = REGISTERED;
1417 else {
1418 MOZ_ASSERT(mRegistration == REGISTERED);
1419 mRegistration = DEREGISTERED;
1423 bool CacheEntry::Purge(uint32_t aWhat)
1425 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
1427 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1429 switch (aWhat) {
1430 case PURGE_DATA_ONLY_DISK_BACKED:
1431 case PURGE_WHOLE_ONLY_DISK_BACKED:
1432 // This is an in-memory only entry, don't purge it
1433 if (!mUseDisk) {
1434 LOG((" not using disk"));
1435 return false;
1439 if (mState == WRITING || mState == LOADING || mFrecency == 0) {
1440 // In-progress (write or load) entries should (at least for consistency and from
1441 // the logical point of view) stay in memory.
1442 // Zero-frecency entries are those which have never been given to any consumer, those
1443 // are actually very fresh and should not go just because frecency had not been set
1444 // so far.
1445 LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency));
1446 return false;
1449 if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) {
1450 // The file is used when there are open streams or chunks/metadata still waiting for
1451 // write. In this case, this entry cannot be purged, otherwise reopenned entry
1452 // would may not even find the data on disk - CacheFile is not shared and cannot be
1453 // left orphan when its job is not done, hence keep the whole entry.
1454 LOG((" file still under use"));
1455 return false;
1458 switch (aWhat) {
1459 case PURGE_WHOLE_ONLY_DISK_BACKED:
1460 case PURGE_WHOLE:
1462 if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1463 LOG((" not purging, still referenced"));
1464 return false;
1467 CacheStorageService::Self()->UnregisterEntry(this);
1469 // Entry removed it self from control arrays, return true
1470 return true;
1473 case PURGE_DATA_ONLY_DISK_BACKED:
1475 NS_ENSURE_SUCCESS(mFileStatus, false);
1477 mFile->ThrowMemoryCachedData();
1479 // Entry has been left in control arrays, return false (not purged)
1480 return false;
1484 LOG((" ?"));
1485 return false;
1488 void CacheEntry::PurgeAndDoom()
1490 LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
1492 CacheStorageService::Self()->RemoveEntry(this);
1493 DoomAlreadyRemoved();
1496 void CacheEntry::DoomAlreadyRemoved()
1498 LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
1500 mozilla::MutexAutoLock lock(mLock);
1502 mIsDoomed = true;
1504 // This schedules dooming of the file, dooming is ensured to happen
1505 // sooner than demand to open the same file made after this point
1506 // so that we don't get this file for any newer opened entry(s).
1507 DoomFile();
1509 // Must force post here since may be indirectly called from
1510 // InvokeCallbacks of this entry and we don't want reentrancy here.
1511 BackgroundOp(Ops::CALLBACKS, true);
1512 // Process immediately when on the management thread.
1513 BackgroundOp(Ops::UNREGISTER);
1516 void CacheEntry::DoomFile()
1518 nsresult rv = NS_ERROR_NOT_AVAILABLE;
1520 if (NS_SUCCEEDED(mFileStatus)) {
1521 // Always calls the callback asynchronously.
1522 rv = mFile->Doom(mDoomCallback ? this : nullptr);
1523 if (NS_SUCCEEDED(rv)) {
1524 LOG((" file doomed"));
1525 return;
1528 if (NS_ERROR_FILE_NOT_FOUND == rv) {
1529 // File is set to be just memory-only, notify the callbacks
1530 // and pretend dooming has succeeded. From point of view of
1531 // the entry it actually did - the data is gone and cannot be
1532 // reused.
1533 rv = NS_OK;
1537 // Always posts to the main thread.
1538 OnFileDoomed(rv);
1541 void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync)
1543 mLock.AssertCurrentThreadOwns();
1545 if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
1546 if (mBackgroundOperations.Set(aOperations))
1547 CacheStorageService::Self()->Dispatch(this);
1549 LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
1550 return;
1554 mozilla::MutexAutoUnlock unlock(mLock);
1556 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1558 if (aOperations & Ops::FRECENCYUPDATE) {
1559 ++mUseCount;
1561 #ifndef M_LN2
1562 #define M_LN2 0.69314718055994530942
1563 #endif
1565 // Half-life is dynamic, in seconds.
1566 static double half_life = CacheObserver::HalfLifeSeconds();
1567 // Must convert from seconds to milliseconds since PR_Now() gives usecs.
1568 static double const decay = (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
1570 double now_decay = static_cast<double>(PR_Now()) * decay;
1572 if (mFrecency == 0) {
1573 mFrecency = now_decay;
1575 else {
1576 // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1) but
1577 // more precise.
1578 mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
1580 LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this, mFrecency));
1582 // Because CacheFile::Set*() are not thread-safe to use (uses WeakReference that
1583 // is not thread-safe) we must post to the main thread...
1584 nsRefPtr<nsRunnableMethod<CacheEntry> > event =
1585 NS_NewRunnableMethod(this, &CacheEntry::StoreFrecency);
1586 NS_DispatchToMainThread(event);
1589 if (aOperations & Ops::REGISTER) {
1590 LOG(("CacheEntry REGISTER [this=%p]", this));
1592 CacheStorageService::Self()->RegisterEntry(this);
1595 if (aOperations & Ops::UNREGISTER) {
1596 LOG(("CacheEntry UNREGISTER [this=%p]", this));
1598 CacheStorageService::Self()->UnregisterEntry(this);
1600 } // unlock
1602 if (aOperations & Ops::CALLBACKS) {
1603 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
1605 InvokeCallbacks();
1609 void CacheEntry::StoreFrecency()
1611 // No need for thread safety over mFrecency, it will be rewriten
1612 // correctly on following invocation if broken by concurrency.
1613 MOZ_ASSERT(NS_IsMainThread());
1615 if (NS_SUCCEEDED(mFileStatus)) {
1616 mFile->SetFrecency(FRECENCY2INT(mFrecency));
1620 // CacheOutputCloseListener
1622 CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
1623 : mEntry(aEntry)
1625 MOZ_COUNT_CTOR(CacheOutputCloseListener);
1628 CacheOutputCloseListener::~CacheOutputCloseListener()
1630 MOZ_COUNT_DTOR(CacheOutputCloseListener);
1633 void CacheOutputCloseListener::OnOutputClosed()
1635 // We need this class and to redispatch since this callback is invoked
1636 // under the file's lock and to do the job we need to enter the entry's
1637 // lock too. That would lead to potential deadlocks.
1638 NS_DispatchToCurrentThread(this);
1641 NS_IMETHODIMP CacheOutputCloseListener::Run()
1643 mEntry->OnOutputClosed();
1644 return NS_OK;
1647 // Memory reporting
1649 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1651 size_t n = 0;
1652 nsCOMPtr<nsISizeOf> sizeOf;
1654 n += mCallbacks.SizeOfExcludingThis(mallocSizeOf);
1655 if (mFile) {
1656 n += mFile->SizeOfIncludingThis(mallocSizeOf);
1659 sizeOf = do_QueryInterface(mURI);
1660 if (sizeOf) {
1661 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
1664 n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1665 n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1667 // mDoomCallback is an arbitrary class that is probably reported elsewhere.
1668 // mOutputStream is reported in mFile.
1669 // mWriter is one of many handles we create, but (intentionally) not keep
1670 // any reference to, so those unfortunatelly cannot be reported. Handles are
1671 // small, though.
1672 // mSecurityInfo doesn't impl nsISizeOf.
1674 return n;
1677 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1679 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1682 } // net
1683 } // mozilla