Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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"
11 #include "nsIInputStream.h"
12 #include "nsIOutputStream.h"
13 #include "nsISeekableStream.h"
14 #include "nsIURI.h"
15 #include "nsICacheEntryOpenCallback.h"
16 #include "nsICacheStorage.h"
17 #include "nsISerializable.h"
18 #include "nsIStreamTransportService.h"
19 #include "nsISizeOf.h"
21 #include "nsComponentManagerUtils.h"
22 #include "nsServiceManagerUtils.h"
23 #include "nsString.h"
24 #include "nsProxyRelease.h"
25 #include "nsSerializationHelper.h"
26 #include "nsThreadUtils.h"
27 #include "mozilla/Telemetry.h"
28 #include <math.h>
29 #include <algorithm>
31 namespace mozilla {
32 namespace net {
34 static uint32_t const ENTRY_WANTED =
35 nsICacheEntryOpenCallback::ENTRY_WANTED;
36 static uint32_t const RECHECK_AFTER_WRITE_FINISHED =
37 nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
38 static uint32_t const ENTRY_NEEDS_REVALIDATION =
39 nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION;
40 static uint32_t const ENTRY_NOT_WANTED =
41 nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
43 NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry)
45 // CacheEntryHandle
47 CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry)
48 : mEntry(aEntry)
49 {
50 MOZ_COUNT_CTOR(CacheEntryHandle);
52 #ifdef DEBUG
53 if (!mEntry->HandlesCount()) {
54 // CacheEntry.mHandlesCount must go from zero to one only under
55 // the service lock. Can access CacheStorageService::Self() w/o a check
56 // since CacheEntry hrefs it.
57 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
58 }
59 #endif
61 mEntry->AddHandleRef();
63 LOG(("New CacheEntryHandle %p for entry %p", this, aEntry));
64 }
66 CacheEntryHandle::~CacheEntryHandle()
67 {
68 mEntry->ReleaseHandleRef();
69 mEntry->OnHandleClosed(this);
71 MOZ_COUNT_DTOR(CacheEntryHandle);
72 }
74 // CacheEntry::Callback
76 CacheEntry::Callback::Callback(CacheEntry* aEntry,
77 nsICacheEntryOpenCallback *aCallback,
78 bool aReadOnly, bool aCheckOnAnyThread)
79 : mEntry(aEntry)
80 , mCallback(aCallback)
81 , mTargetThread(do_GetCurrentThread())
82 , mReadOnly(aReadOnly)
83 , mCheckOnAnyThread(aCheckOnAnyThread)
84 , mRecheckAfterWrite(false)
85 , mNotWanted(false)
86 {
87 MOZ_COUNT_CTOR(CacheEntry::Callback);
89 // The counter may go from zero to non-null only under the service lock
90 // but here we expect it to be already positive.
91 MOZ_ASSERT(mEntry->HandlesCount());
92 mEntry->AddHandleRef();
93 }
95 CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
96 : mEntry(aThat.mEntry)
97 , mCallback(aThat.mCallback)
98 , mTargetThread(aThat.mTargetThread)
99 , mReadOnly(aThat.mReadOnly)
100 , mCheckOnAnyThread(aThat.mCheckOnAnyThread)
101 , mRecheckAfterWrite(aThat.mRecheckAfterWrite)
102 , mNotWanted(aThat.mNotWanted)
103 {
104 MOZ_COUNT_CTOR(CacheEntry::Callback);
106 // The counter may go from zero to non-null only under the service lock
107 // but here we expect it to be already positive.
108 MOZ_ASSERT(mEntry->HandlesCount());
109 mEntry->AddHandleRef();
110 }
112 CacheEntry::Callback::~Callback()
113 {
114 ProxyRelease(mCallback, mTargetThread);
116 mEntry->ReleaseHandleRef();
117 MOZ_COUNT_DTOR(CacheEntry::Callback);
118 }
120 void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry)
121 {
122 if (mEntry == aEntry)
123 return;
125 // The counter may go from zero to non-null only under the service lock
126 // but here we expect it to be already positive.
127 MOZ_ASSERT(aEntry->HandlesCount());
128 aEntry->AddHandleRef();
129 mEntry->ReleaseHandleRef();
130 mEntry = aEntry;
131 }
133 nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const
134 {
135 if (!mCheckOnAnyThread) {
136 // Check we are on the target
137 return mTargetThread->IsOnCurrentThread(aOnCheckThread);
138 }
140 // We can invoke check anywhere
141 *aOnCheckThread = true;
142 return NS_OK;
143 }
145 nsresult CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread) const
146 {
147 return mTargetThread->IsOnCurrentThread(aOnAvailThread);
148 }
150 // CacheEntry
152 NS_IMPL_ISUPPORTS(CacheEntry,
153 nsICacheEntry,
154 nsIRunnable,
155 CacheFileListener)
157 CacheEntry::CacheEntry(const nsACString& aStorageID,
158 nsIURI* aURI,
159 const nsACString& aEnhanceID,
160 bool aUseDisk)
161 : mFrecency(0)
162 , mSortingExpirationTime(uint32_t(-1))
163 , mLock("CacheEntry")
164 , mFileStatus(NS_ERROR_NOT_INITIALIZED)
165 , mURI(aURI)
166 , mEnhanceID(aEnhanceID)
167 , mStorageID(aStorageID)
168 , mUseDisk(aUseDisk)
169 , mIsDoomed(false)
170 , mSecurityInfoLoaded(false)
171 , mPreventCallbacks(false)
172 , mHasData(false)
173 , mState(NOTLOADED)
174 , mRegistration(NEVERREGISTERED)
175 , mWriter(nullptr)
176 , mPredictedDataSize(0)
177 , mReleaseThread(NS_GetCurrentThread())
178 {
179 MOZ_COUNT_CTOR(CacheEntry);
181 mService = CacheStorageService::Self();
183 CacheStorageService::Self()->RecordMemoryOnlyEntry(
184 this, !aUseDisk, true /* overwrite */);
185 }
187 CacheEntry::~CacheEntry()
188 {
189 ProxyRelease(mURI, mReleaseThread);
191 LOG(("CacheEntry::~CacheEntry [this=%p]", this));
192 MOZ_COUNT_DTOR(CacheEntry);
193 }
195 #ifdef PR_LOG
197 char const * CacheEntry::StateString(uint32_t aState)
198 {
199 switch (aState) {
200 case NOTLOADED: return "NOTLOADED";
201 case LOADING: return "LOADING";
202 case EMPTY: return "EMPTY";
203 case WRITING: return "WRITING";
204 case READY: return "READY";
205 case REVALIDATING: return "REVALIDATING";
206 }
208 return "?";
209 }
211 #endif
213 nsresult CacheEntry::HashingKeyWithStorage(nsACString &aResult)
214 {
215 return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
216 }
218 nsresult CacheEntry::HashingKey(nsACString &aResult)
219 {
220 return HashingKey(EmptyCString(), mEnhanceID, mURI, aResult);
221 }
223 // static
224 nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
225 nsCSubstring const& aEnhanceID,
226 nsIURI* aURI,
227 nsACString &aResult)
228 {
229 nsAutoCString spec;
230 nsresult rv = aURI->GetAsciiSpec(spec);
231 NS_ENSURE_SUCCESS(rv, rv);
233 return HashingKey(aStorageID, aEnhanceID, spec, aResult);
234 }
236 // static
237 nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
238 nsCSubstring const& aEnhanceID,
239 nsCSubstring const& aURISpec,
240 nsACString &aResult)
241 {
242 /**
243 * This key is used to salt hash that is a base for disk file name.
244 * Changing it will cause we will not be able to find files on disk.
245 */
247 aResult.Append(aStorageID);
249 if (!aEnhanceID.IsEmpty()) {
250 CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID);
251 }
253 // Appending directly
254 aResult.Append(':');
255 aResult.Append(aURISpec);
257 return NS_OK;
258 }
260 void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags)
261 {
262 LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]",
263 this, StateString(mState), aFlags, aCallback));
265 bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
266 bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
267 bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
268 bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
270 MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
271 MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry");
273 Callback callback(this, aCallback, readonly, multithread);
275 mozilla::MutexAutoLock lock(mLock);
277 RememberCallback(callback);
279 // Load() opens the lock
280 if (Load(truncate, priority)) {
281 // Loading is in progress...
282 return;
283 }
285 InvokeCallbacks();
286 }
288 bool CacheEntry::Load(bool aTruncate, bool aPriority)
289 {
290 LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
292 mLock.AssertCurrentThreadOwns();
294 if (mState > LOADING) {
295 LOG((" already loaded"));
296 return false;
297 }
299 if (mState == LOADING) {
300 LOG((" already loading"));
301 return true;
302 }
304 mState = LOADING;
306 MOZ_ASSERT(!mFile);
308 bool directLoad = aTruncate || !mUseDisk;
309 if (directLoad)
310 mFileStatus = NS_OK;
311 else
312 mLoadStart = TimeStamp::Now();
314 mFile = new CacheFile();
316 BackgroundOp(Ops::REGISTER);
318 {
319 mozilla::MutexAutoUnlock unlock(mLock);
321 nsresult rv;
323 nsAutoCString fileKey;
324 rv = HashingKeyWithStorage(fileKey);
326 LOG((" performing load, file=%p", mFile.get()));
327 if (NS_SUCCEEDED(rv)) {
328 rv = mFile->Init(fileKey,
329 aTruncate,
330 !mUseDisk,
331 aPriority,
332 directLoad ? nullptr : this);
333 }
335 if (NS_FAILED(rv)) {
336 mFileStatus = rv;
337 AsyncDoom(nullptr);
338 return false;
339 }
340 }
342 if (directLoad) {
343 // Just fake the load has already been done as "new".
344 mState = EMPTY;
345 }
347 return mState == LOADING;
348 }
350 NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
351 {
352 LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08x, new=%d]",
353 this, aResult, aIsNew));
355 MOZ_ASSERT(!mLoadStart.IsNull());
357 if (NS_SUCCEEDED(aResult)) {
358 if (aIsNew) {
359 mozilla::Telemetry::AccumulateTimeDelta(
360 mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS,
361 mLoadStart);
362 }
363 else {
364 mozilla::Telemetry::AccumulateTimeDelta(
365 mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS,
366 mLoadStart);
367 }
368 }
370 // OnFileReady, that is the only code that can transit from LOADING
371 // to any follow-on state, can only be invoked ones on an entry,
372 // thus no need to lock. Until this moment there is no consumer that
373 // could manipulate the entry state.
374 mozilla::MutexAutoLock lock(mLock);
376 MOZ_ASSERT(mState == LOADING);
378 mState = (aIsNew || NS_FAILED(aResult))
379 ? EMPTY
380 : READY;
382 mFileStatus = aResult;
384 if (mState == READY) {
385 mHasData = true;
387 uint32_t frecency;
388 mFile->GetFrecency(&frecency);
389 // mFrecency is held in a double to increase computance precision.
390 // It is ok to persist frecency only as a uint32 with some math involved.
391 mFrecency = INT2FRECENCY(frecency);
392 }
394 InvokeCallbacks();
395 return NS_OK;
396 }
398 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult)
399 {
400 if (mDoomCallback) {
401 nsRefPtr<DoomCallbackRunnable> event =
402 new DoomCallbackRunnable(this, aResult);
403 NS_DispatchToMainThread(event);
404 }
406 return NS_OK;
407 }
409 already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(bool aMemoryOnly,
410 nsICacheEntryOpenCallback* aCallback)
411 {
412 LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
414 mLock.AssertCurrentThreadOwns();
416 // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly
417 mPreventCallbacks = true;
419 nsRefPtr<CacheEntryHandle> handle;
420 nsRefPtr<CacheEntry> newEntry;
421 {
422 mozilla::MutexAutoUnlock unlock(mLock);
424 // The following call dooms this entry (calls DoomAlreadyRemoved on us)
425 nsresult rv = CacheStorageService::Self()->AddStorageEntry(
426 GetStorageID(), GetURI(), GetEnhanceID(),
427 mUseDisk && !aMemoryOnly,
428 true, // always create
429 true, // truncate existing (this one)
430 getter_AddRefs(handle));
432 if (NS_SUCCEEDED(rv)) {
433 newEntry = handle->Entry();
434 LOG((" exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv));
435 newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
436 } else {
437 LOG((" exchanged of entry %p failed, rv=0x%08x", this, rv));
438 AsyncDoom(nullptr);
439 }
440 }
442 mPreventCallbacks = false;
444 if (!newEntry)
445 return nullptr;
447 newEntry->TransferCallbacks(*this);
448 mCallbacks.Clear();
450 return handle.forget();
451 }
453 void CacheEntry::TransferCallbacks(CacheEntry & aFromEntry)
454 {
455 mozilla::MutexAutoLock lock(mLock);
457 LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]",
458 this, &aFromEntry));
460 if (!mCallbacks.Length())
461 mCallbacks.SwapElements(aFromEntry.mCallbacks);
462 else
463 mCallbacks.AppendElements(aFromEntry.mCallbacks);
465 uint32_t callbacksLength = mCallbacks.Length();
466 if (callbacksLength) {
467 // Carry the entry reference (unfortunatelly, needs to be done manually...)
468 for (uint32_t i = 0; i < callbacksLength; ++i)
469 mCallbacks[i].ExchangeEntry(this);
471 BackgroundOp(Ops::CALLBACKS, true);
472 }
473 }
475 void CacheEntry::RememberCallback(Callback const& aCallback)
476 {
477 LOG(("CacheEntry::RememberCallback [this=%p, cb=%p]", this, aCallback.mCallback.get()));
479 mLock.AssertCurrentThreadOwns();
481 mCallbacks.AppendElement(aCallback);
482 }
484 void CacheEntry::InvokeCallbacksLock()
485 {
486 mozilla::MutexAutoLock lock(mLock);
487 InvokeCallbacks();
488 }
490 void CacheEntry::InvokeCallbacks()
491 {
492 mLock.AssertCurrentThreadOwns();
494 LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
496 // Invoke first all r/w callbacks, then all r/o callbacks.
497 if (InvokeCallbacks(false))
498 InvokeCallbacks(true);
500 LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
501 }
503 bool CacheEntry::InvokeCallbacks(bool aReadOnly)
504 {
505 mLock.AssertCurrentThreadOwns();
507 uint32_t i = 0;
508 while (i < mCallbacks.Length()) {
509 if (mPreventCallbacks) {
510 LOG((" callbacks prevented!"));
511 return false;
512 }
514 if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
515 LOG((" entry is being written/revalidated"));
516 return false;
517 }
519 if (mCallbacks[i].mReadOnly != aReadOnly) {
520 // Callback is not r/w or r/o, go to another one in line
521 ++i;
522 continue;
523 }
525 bool onCheckThread;
526 nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
528 if (NS_SUCCEEDED(rv) && !onCheckThread) {
529 // Redispatch to the target thread
530 nsRefPtr<nsRunnableMethod<CacheEntry> > event =
531 NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksLock);
533 rv = mCallbacks[i].mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
534 if (NS_SUCCEEDED(rv)) {
535 LOG((" re-dispatching to target thread"));
536 return false;
537 }
538 }
540 Callback callback = mCallbacks[i];
541 mCallbacks.RemoveElementAt(i);
543 if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
544 // Callback didn't fire, put it back and go to another one in line.
545 // Only reason InvokeCallback returns false is that onCacheEntryCheck
546 // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other
547 // readers or potential writers would be unnecessarily kept from being
548 // invoked.
549 mCallbacks.InsertElementAt(i, callback);
550 ++i;
551 }
552 }
554 return true;
555 }
557 bool CacheEntry::InvokeCallback(Callback & aCallback)
558 {
559 LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]",
560 this, StateString(mState), aCallback.mCallback.get()));
562 mLock.AssertCurrentThreadOwns();
564 // When this entry is doomed we want to notify the callback any time
565 if (!mIsDoomed) {
566 // When we are here, the entry must be loaded from disk
567 MOZ_ASSERT(mState > LOADING);
569 if (mState == WRITING || mState == REVALIDATING) {
570 // Prevent invoking other callbacks since one of them is now writing
571 // or revalidating this entry. No consumers should get this entry
572 // until metadata are filled with values downloaded from the server
573 // or the entry revalidated and output stream has been opened.
574 LOG((" entry is being written/revalidated, callback bypassed"));
575 return false;
576 }
578 // mRecheckAfterWrite flag already set means the callback has already passed
579 // the onCacheEntryCheck call. Until the current write is not finished this
580 // callback will be bypassed.
581 if (!aCallback.mRecheckAfterWrite) {
583 if (!aCallback.mReadOnly) {
584 if (mState == EMPTY) {
585 // Advance to writing state, we expect to invoke the callback and let
586 // it fill content of this entry. Must set and check the state here
587 // to prevent more then one
588 mState = WRITING;
589 LOG((" advancing to WRITING state"));
590 }
592 if (!aCallback.mCallback) {
593 // We can be given no callback only in case of recreate, it is ok
594 // to advance to WRITING state since the caller of recreate is expected
595 // to write this entry now.
596 return true;
597 }
598 }
600 if (mState == READY) {
601 // Metadata present, validate the entry
602 uint32_t checkResult;
603 {
604 // mayhemer: TODO check and solve any potential races of concurent OnCacheEntryCheck
605 mozilla::MutexAutoUnlock unlock(mLock);
607 nsresult rv = aCallback.mCallback->OnCacheEntryCheck(
608 this, nullptr, &checkResult);
609 LOG((" OnCacheEntryCheck: rv=0x%08x, result=%d", rv, checkResult));
611 if (NS_FAILED(rv))
612 checkResult = ENTRY_NOT_WANTED;
613 }
615 switch (checkResult) {
616 case ENTRY_WANTED:
617 // Nothing more to do here, the consumer is responsible to handle
618 // the result of OnCacheEntryCheck it self.
619 // Proceed to callback...
620 break;
622 case RECHECK_AFTER_WRITE_FINISHED:
623 LOG((" consumer will check on the entry again after write is done"));
624 // The consumer wants the entry to complete first.
625 aCallback.mRecheckAfterWrite = true;
626 break;
628 case ENTRY_NEEDS_REVALIDATION:
629 LOG((" will be holding callbacks until entry is revalidated"));
630 // State is READY now and from that state entry cannot transit to any other
631 // state then REVALIDATING for which cocurrency is not an issue. Potentially
632 // no need to lock here.
633 mState = REVALIDATING;
634 break;
636 case ENTRY_NOT_WANTED:
637 LOG((" consumer not interested in the entry"));
638 // Do not give this entry to the consumer, it is not interested in us.
639 aCallback.mNotWanted = true;
640 break;
641 }
642 }
643 }
644 }
646 if (aCallback.mCallback) {
647 if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
648 // If we don't have data and the callback wants a complete entry,
649 // don't invoke now.
650 bool bypass = !mHasData;
651 if (!bypass) {
652 int64_t _unused;
653 bypass = !mFile->DataSize(&_unused);
654 }
656 if (bypass) {
657 LOG((" bypassing, entry data still being written"));
658 return false;
659 }
661 // Entry is complete now, do the check+avail call again
662 aCallback.mRecheckAfterWrite = false;
663 return InvokeCallback(aCallback);
664 }
666 mozilla::MutexAutoUnlock unlock(mLock);
667 InvokeAvailableCallback(aCallback);
668 }
670 return true;
671 }
673 void CacheEntry::InvokeAvailableCallback(Callback const & aCallback)
674 {
675 LOG(("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, n/w=%d]",
676 this, StateString(mState), aCallback.mCallback.get(), aCallback.mReadOnly, aCallback.mNotWanted));
678 nsresult rv;
680 uint32_t const state = mState;
682 // When we are here, the entry must be loaded from disk
683 MOZ_ASSERT(state > LOADING || mIsDoomed);
685 bool onAvailThread;
686 rv = aCallback.OnAvailThread(&onAvailThread);
687 if (NS_FAILED(rv)) {
688 LOG((" target thread dead?"));
689 return;
690 }
692 if (!onAvailThread) {
693 // Dispatch to the right thread
694 nsRefPtr<AvailableCallbackRunnable> event =
695 new AvailableCallbackRunnable(this, aCallback);
697 rv = aCallback.mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
698 LOG((" redispatched, (rv = 0x%08x)", rv));
699 return;
700 }
702 if (mIsDoomed || aCallback.mNotWanted) {
703 LOG((" doomed or not wanted, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
704 aCallback.mCallback->OnCacheEntryAvailable(
705 nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
706 return;
707 }
709 if (state == READY) {
710 LOG((" ready/has-meta, notifying OCEA with entry and NS_OK"));
711 {
712 mozilla::MutexAutoLock lock(mLock);
713 BackgroundOp(Ops::FRECENCYUPDATE);
714 }
716 nsRefPtr<CacheEntryHandle> handle = NewHandle();
717 aCallback.mCallback->OnCacheEntryAvailable(
718 handle, false, nullptr, NS_OK);
719 return;
720 }
722 if (aCallback.mReadOnly) {
723 LOG((" r/o and not ready, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
724 aCallback.mCallback->OnCacheEntryAvailable(
725 nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
726 return;
727 }
729 // This is a new or potentially non-valid entry and needs to be fetched first.
730 // The CacheEntryHandle blocks other consumers until the channel
731 // either releases the entry or marks metadata as filled or whole entry valid,
732 // i.e. until MetaDataReady() or SetValid() on the entry is called respectively.
734 // Consumer will be responsible to fill or validate the entry metadata and data.
736 nsRefPtr<CacheEntryHandle> handle = NewWriteHandle();
737 rv = aCallback.mCallback->OnCacheEntryAvailable(
738 handle, state == WRITING, nullptr, NS_OK);
740 if (NS_FAILED(rv)) {
741 LOG((" writing/revalidating failed (0x%08x)", rv));
743 // Consumer given a new entry failed to take care of the entry.
744 OnHandleClosed(handle);
745 return;
746 }
748 LOG((" writing/revalidating"));
749 }
751 CacheEntryHandle* CacheEntry::NewHandle()
752 {
753 return new CacheEntryHandle(this);
754 }
756 CacheEntryHandle* CacheEntry::NewWriteHandle()
757 {
758 mozilla::MutexAutoLock lock(mLock);
760 BackgroundOp(Ops::FRECENCYUPDATE);
761 return (mWriter = new CacheEntryHandle(this));
762 }
764 void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle)
765 {
766 LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this, StateString(mState), aHandle));
768 nsCOMPtr<nsIOutputStream> outputStream;
770 {
771 mozilla::MutexAutoLock lock(mLock);
773 if (mWriter != aHandle) {
774 LOG((" not the writer"));
775 return;
776 }
778 if (mOutputStream) {
779 // No one took our internal output stream, so there are no data
780 // and output stream has to be open symultaneously with input stream
781 // on this entry again.
782 mHasData = false;
783 }
785 outputStream.swap(mOutputStream);
786 mWriter = nullptr;
788 if (mState == WRITING) {
789 LOG((" reverting to state EMPTY - write failed"));
790 mState = EMPTY;
791 }
792 else if (mState == REVALIDATING) {
793 LOG((" reverting to state READY - reval failed"));
794 mState = READY;
795 }
797 InvokeCallbacks();
798 }
800 if (outputStream) {
801 LOG((" abandoning phantom output stream"));
802 outputStream->Close();
803 }
804 }
806 void CacheEntry::OnOutputClosed()
807 {
808 // Called when the file's output stream is closed. Invoke any callbacks
809 // waiting for complete entry.
811 mozilla::MutexAutoLock lock(mLock);
812 InvokeCallbacks();
813 }
815 bool CacheEntry::IsUsingDiskLocked() const
816 {
817 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
819 return IsUsingDisk();
820 }
822 bool CacheEntry::SetUsingDisk(bool aUsingDisk)
823 {
824 // Called by the service when this entry is reopen to reflect
825 // demanded storage target.
827 if (mState >= READY) {
828 // Don't modify after this entry has been filled.
829 return false;
830 }
832 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
834 bool changed = mUseDisk != aUsingDisk;
835 mUseDisk = aUsingDisk;
836 return changed;
837 }
839 bool CacheEntry::IsReferenced() const
840 {
841 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
843 // Increasing this counter from 0 to non-null and this check both happen only
844 // under the service lock.
845 return mHandlesCount > 0;
846 }
848 bool CacheEntry::IsFileDoomed()
849 {
850 mozilla::MutexAutoLock lock(mLock);
852 if (NS_SUCCEEDED(mFileStatus)) {
853 return mFile->IsDoomed();
854 }
856 return false;
857 }
859 uint32_t CacheEntry::GetMetadataMemoryConsumption()
860 {
861 NS_ENSURE_SUCCESS(mFileStatus, 0);
863 uint32_t size;
864 if (NS_FAILED(mFile->ElementsSize(&size)))
865 return 0;
867 return size;
868 }
870 // nsICacheEntry
872 NS_IMETHODIMP CacheEntry::GetPersistent(bool *aPersistToDisk)
873 {
874 // No need to sync when only reading.
875 // When consumer needs to be consistent with state of the memory storage entries
876 // table, then let it use GetUseDisk getter that must be called under the service lock.
877 *aPersistToDisk = mUseDisk;
878 return NS_OK;
879 }
881 NS_IMETHODIMP CacheEntry::GetKey(nsACString & aKey)
882 {
883 return mURI->GetAsciiSpec(aKey);
884 }
886 NS_IMETHODIMP CacheEntry::GetFetchCount(int32_t *aFetchCount)
887 {
888 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
890 return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount));
891 }
893 NS_IMETHODIMP CacheEntry::GetLastFetched(uint32_t *aLastFetched)
894 {
895 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
897 return mFile->GetLastFetched(aLastFetched);
898 }
900 NS_IMETHODIMP CacheEntry::GetLastModified(uint32_t *aLastModified)
901 {
902 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
904 return mFile->GetLastModified(aLastModified);
905 }
907 NS_IMETHODIMP CacheEntry::GetExpirationTime(uint32_t *aExpirationTime)
908 {
909 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
911 return mFile->GetExpirationTime(aExpirationTime);
912 }
914 NS_IMETHODIMP CacheEntry::SetExpirationTime(uint32_t aExpirationTime)
915 {
916 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
918 nsresult rv = mFile->SetExpirationTime(aExpirationTime);
919 NS_ENSURE_SUCCESS(rv, rv);
921 // Aligned assignment, thus atomic.
922 mSortingExpirationTime = aExpirationTime;
923 return NS_OK;
924 }
926 NS_IMETHODIMP CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval)
927 {
928 LOG(("CacheEntry::OpenInputStream [this=%p]", this));
930 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
932 nsresult rv;
934 nsCOMPtr<nsIInputStream> stream;
935 rv = mFile->OpenInputStream(getter_AddRefs(stream));
936 NS_ENSURE_SUCCESS(rv, rv);
938 nsCOMPtr<nsISeekableStream> seekable =
939 do_QueryInterface(stream, &rv);
940 NS_ENSURE_SUCCESS(rv, rv);
942 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
943 NS_ENSURE_SUCCESS(rv, rv);
945 mozilla::MutexAutoLock lock(mLock);
947 if (!mHasData) {
948 // So far output stream on this new entry not opened, do it now.
949 LOG((" creating phantom output stream"));
950 rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
951 NS_ENSURE_SUCCESS(rv, rv);
952 }
954 stream.forget(_retval);
955 return NS_OK;
956 }
958 NS_IMETHODIMP CacheEntry::OpenOutputStream(int64_t offset, nsIOutputStream * *_retval)
959 {
960 LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
962 nsresult rv;
964 mozilla::MutexAutoLock lock(mLock);
966 MOZ_ASSERT(mState > EMPTY);
968 if (mOutputStream && !mIsDoomed) {
969 LOG((" giving phantom output stream"));
970 mOutputStream.forget(_retval);
971 }
972 else {
973 rv = OpenOutputStreamInternal(offset, _retval);
974 if (NS_FAILED(rv)) return rv;
975 }
977 // Entry considered ready when writer opens output stream.
978 if (mState < READY)
979 mState = READY;
981 // Invoke any pending readers now.
982 InvokeCallbacks();
984 return NS_OK;
985 }
987 nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval)
988 {
989 LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
991 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
993 mLock.AssertCurrentThreadOwns();
995 if (mIsDoomed) {
996 LOG((" doomed..."));
997 return NS_ERROR_NOT_AVAILABLE;
998 }
1000 MOZ_ASSERT(mState > LOADING);
1002 nsresult rv;
1004 // No need to sync on mUseDisk here, we don't need to be consistent
1005 // with content of the memory storage entries hash table.
1006 if (!mUseDisk) {
1007 rv = mFile->SetMemoryOnly();
1008 NS_ENSURE_SUCCESS(rv, rv);
1009 }
1011 nsRefPtr<CacheOutputCloseListener> listener =
1012 new CacheOutputCloseListener(this);
1014 nsCOMPtr<nsIOutputStream> stream;
1015 rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
1016 NS_ENSURE_SUCCESS(rv, rv);
1018 nsCOMPtr<nsISeekableStream> seekable =
1019 do_QueryInterface(stream, &rv);
1020 NS_ENSURE_SUCCESS(rv, rv);
1022 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1023 NS_ENSURE_SUCCESS(rv, rv);
1025 // Prevent opening output stream again.
1026 mHasData = true;
1028 stream.swap(*_retval);
1029 return NS_OK;
1030 }
1032 NS_IMETHODIMP CacheEntry::GetPredictedDataSize(int64_t *aPredictedDataSize)
1033 {
1034 *aPredictedDataSize = mPredictedDataSize;
1035 return NS_OK;
1036 }
1037 NS_IMETHODIMP CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize)
1038 {
1039 mPredictedDataSize = aPredictedDataSize;
1041 if (CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) {
1042 LOG(("CacheEntry::SetPredictedDataSize [this=%p] too big, dooming", this));
1043 AsyncDoom(nullptr);
1045 return NS_ERROR_FILE_TOO_BIG;
1046 }
1048 return NS_OK;
1049 }
1051 NS_IMETHODIMP CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo)
1052 {
1053 {
1054 mozilla::MutexAutoLock lock(mLock);
1055 if (mSecurityInfoLoaded) {
1056 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1057 return NS_OK;
1058 }
1059 }
1061 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1063 nsXPIDLCString info;
1064 nsCOMPtr<nsISupports> secInfo;
1065 nsresult rv;
1067 rv = mFile->GetElement("security-info", getter_Copies(info));
1068 NS_ENSURE_SUCCESS(rv, rv);
1070 if (info) {
1071 rv = NS_DeserializeObject(info, getter_AddRefs(secInfo));
1072 NS_ENSURE_SUCCESS(rv, rv);
1073 }
1075 {
1076 mozilla::MutexAutoLock lock(mLock);
1078 mSecurityInfo.swap(secInfo);
1079 mSecurityInfoLoaded = true;
1081 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1082 }
1084 return NS_OK;
1085 }
1086 NS_IMETHODIMP CacheEntry::SetSecurityInfo(nsISupports *aSecurityInfo)
1087 {
1088 nsresult rv;
1090 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1092 {
1093 mozilla::MutexAutoLock lock(mLock);
1095 mSecurityInfo = aSecurityInfo;
1096 mSecurityInfoLoaded = true;
1097 }
1099 nsCOMPtr<nsISerializable> serializable =
1100 do_QueryInterface(aSecurityInfo);
1101 if (aSecurityInfo && !serializable)
1102 return NS_ERROR_UNEXPECTED;
1104 nsCString info;
1105 if (serializable) {
1106 rv = NS_SerializeToString(serializable, info);
1107 NS_ENSURE_SUCCESS(rv, rv);
1108 }
1110 rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
1111 NS_ENSURE_SUCCESS(rv, rv);
1113 return NS_OK;
1114 }
1116 NS_IMETHODIMP CacheEntry::GetStorageDataSize(uint32_t *aStorageDataSize)
1117 {
1118 NS_ENSURE_ARG(aStorageDataSize);
1120 int64_t dataSize;
1121 nsresult rv = GetDataSize(&dataSize);
1122 if (NS_FAILED(rv))
1123 return rv;
1125 *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
1127 return NS_OK;
1128 }
1130 NS_IMETHODIMP CacheEntry::AsyncDoom(nsICacheEntryDoomCallback *aCallback)
1131 {
1132 LOG(("CacheEntry::AsyncDoom [this=%p]", this));
1134 {
1135 mozilla::MutexAutoLock lock(mLock);
1137 if (mIsDoomed || mDoomCallback)
1138 return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
1140 mIsDoomed = true;
1141 mDoomCallback = aCallback;
1142 }
1144 // This immediately removes the entry from the master hashtable and also
1145 // immediately dooms the file. This way we make sure that any consumer
1146 // after this point asking for the same entry won't get
1147 // a) this entry
1148 // b) a new entry with the same file
1149 PurgeAndDoom();
1151 return NS_OK;
1152 }
1154 NS_IMETHODIMP CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval)
1155 {
1156 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1158 return mFile->GetElement(aKey, aRetval);
1159 }
1161 NS_IMETHODIMP CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue)
1162 {
1163 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1165 return mFile->SetElement(aKey, aValue);
1166 }
1168 NS_IMETHODIMP CacheEntry::MetaDataReady()
1169 {
1170 mozilla::MutexAutoLock lock(mLock);
1172 LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, StateString(mState)));
1174 MOZ_ASSERT(mState > EMPTY);
1176 if (mState == WRITING)
1177 mState = READY;
1179 InvokeCallbacks();
1181 return NS_OK;
1182 }
1184 NS_IMETHODIMP CacheEntry::SetValid()
1185 {
1186 LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState)));
1188 nsCOMPtr<nsIOutputStream> outputStream;
1190 {
1191 mozilla::MutexAutoLock lock(mLock);
1193 MOZ_ASSERT(mState > EMPTY);
1195 mState = READY;
1196 mHasData = true;
1198 InvokeCallbacks();
1200 outputStream.swap(mOutputStream);
1201 }
1203 if (outputStream) {
1204 LOG((" abandoning phantom output stream"));
1205 outputStream->Close();
1206 }
1208 return NS_OK;
1209 }
1211 NS_IMETHODIMP CacheEntry::Recreate(bool aMemoryOnly,
1212 nsICacheEntry **_retval)
1213 {
1214 LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
1216 mozilla::MutexAutoLock lock(mLock);
1218 nsRefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
1219 if (handle) {
1220 handle.forget(_retval);
1221 return NS_OK;
1222 }
1224 BackgroundOp(Ops::CALLBACKS, true);
1225 return NS_OK;
1226 }
1228 NS_IMETHODIMP CacheEntry::GetDataSize(int64_t *aDataSize)
1229 {
1230 LOG(("CacheEntry::GetDataSize [this=%p]", this));
1231 *aDataSize = 0;
1233 {
1234 mozilla::MutexAutoLock lock(mLock);
1236 if (!mHasData) {
1237 LOG((" write in progress (no data)"));
1238 return NS_ERROR_IN_PROGRESS;
1239 }
1240 }
1242 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1244 // mayhemer: TODO Problem with compression?
1245 if (!mFile->DataSize(aDataSize)) {
1246 LOG((" write in progress (stream active)"));
1247 return NS_ERROR_IN_PROGRESS;
1248 }
1250 LOG((" size=%lld", *aDataSize));
1251 return NS_OK;
1252 }
1254 NS_IMETHODIMP CacheEntry::MarkValid()
1255 {
1256 // NOT IMPLEMENTED ACTUALLY
1257 return NS_OK;
1258 }
1260 NS_IMETHODIMP CacheEntry::MaybeMarkValid()
1261 {
1262 // NOT IMPLEMENTED ACTUALLY
1263 return NS_OK;
1264 }
1266 NS_IMETHODIMP CacheEntry::HasWriteAccess(bool aWriteAllowed, bool *aWriteAccess)
1267 {
1268 *aWriteAccess = aWriteAllowed;
1269 return NS_OK;
1270 }
1272 NS_IMETHODIMP CacheEntry::Close()
1273 {
1274 // NOT IMPLEMENTED ACTUALLY
1275 return NS_OK;
1276 }
1278 // nsIRunnable
1280 NS_IMETHODIMP CacheEntry::Run()
1281 {
1282 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1284 mozilla::MutexAutoLock lock(mLock);
1286 BackgroundOp(mBackgroundOperations.Grab());
1287 return NS_OK;
1288 }
1290 // Management methods
1292 double CacheEntry::GetFrecency() const
1293 {
1294 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1295 return mFrecency;
1296 }
1298 uint32_t CacheEntry::GetExpirationTime() const
1299 {
1300 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1301 return mSortingExpirationTime;
1302 }
1304 bool CacheEntry::IsRegistered() const
1305 {
1306 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1307 return mRegistration == REGISTERED;
1308 }
1310 bool CacheEntry::CanRegister() const
1311 {
1312 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1313 return mRegistration == NEVERREGISTERED;
1314 }
1316 void CacheEntry::SetRegistered(bool aRegistered)
1317 {
1318 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1320 if (aRegistered) {
1321 MOZ_ASSERT(mRegistration == NEVERREGISTERED);
1322 mRegistration = REGISTERED;
1323 }
1324 else {
1325 MOZ_ASSERT(mRegistration == REGISTERED);
1326 mRegistration = DEREGISTERED;
1327 }
1328 }
1330 bool CacheEntry::Purge(uint32_t aWhat)
1331 {
1332 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
1334 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1336 switch (aWhat) {
1337 case PURGE_DATA_ONLY_DISK_BACKED:
1338 case PURGE_WHOLE_ONLY_DISK_BACKED:
1339 // This is an in-memory only entry, don't purge it
1340 if (!mUseDisk) {
1341 LOG((" not using disk"));
1342 return false;
1343 }
1344 }
1346 if (mState == WRITING || mState == LOADING || mFrecency == 0) {
1347 // In-progress (write or load) entries should (at least for consistency and from
1348 // the logical point of view) stay in memory.
1349 // Zero-frecency entries are those which have never been given to any consumer, those
1350 // are actually very fresh and should not go just because frecency had not been set
1351 // so far.
1352 LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency));
1353 return false;
1354 }
1356 if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) {
1357 // The file is used when there are open streams or chunks/metadata still waiting for
1358 // write. In this case, this entry cannot be purged, otherwise reopenned entry
1359 // would may not even find the data on disk - CacheFile is not shared and cannot be
1360 // left orphan when its job is not done, hence keep the whole entry.
1361 LOG((" file still under use"));
1362 return false;
1363 }
1365 switch (aWhat) {
1366 case PURGE_WHOLE_ONLY_DISK_BACKED:
1367 case PURGE_WHOLE:
1368 {
1369 if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1370 LOG((" not purging, still referenced"));
1371 return false;
1372 }
1374 CacheStorageService::Self()->UnregisterEntry(this);
1376 // Entry removed it self from control arrays, return true
1377 return true;
1378 }
1380 case PURGE_DATA_ONLY_DISK_BACKED:
1381 {
1382 NS_ENSURE_SUCCESS(mFileStatus, false);
1384 mFile->ThrowMemoryCachedData();
1386 // Entry has been left in control arrays, return false (not purged)
1387 return false;
1388 }
1389 }
1391 LOG((" ?"));
1392 return false;
1393 }
1395 void CacheEntry::PurgeAndDoom()
1396 {
1397 LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
1399 CacheStorageService::Self()->RemoveEntry(this);
1400 DoomAlreadyRemoved();
1401 }
1403 void CacheEntry::DoomAlreadyRemoved()
1404 {
1405 LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
1407 mozilla::MutexAutoLock lock(mLock);
1409 mIsDoomed = true;
1411 // This schedules dooming of the file, dooming is ensured to happen
1412 // sooner than demand to open the same file made after this point
1413 // so that we don't get this file for any newer opened entry(s).
1414 DoomFile();
1416 // Must force post here since may be indirectly called from
1417 // InvokeCallbacks of this entry and we don't want reentrancy here.
1418 BackgroundOp(Ops::CALLBACKS, true);
1419 // Process immediately when on the management thread.
1420 BackgroundOp(Ops::UNREGISTER);
1421 }
1423 void CacheEntry::DoomFile()
1424 {
1425 nsresult rv = NS_ERROR_NOT_AVAILABLE;
1427 if (NS_SUCCEEDED(mFileStatus)) {
1428 // Always calls the callback asynchronously.
1429 rv = mFile->Doom(mDoomCallback ? this : nullptr);
1430 if (NS_SUCCEEDED(rv)) {
1431 LOG((" file doomed"));
1432 return;
1433 }
1435 if (NS_ERROR_FILE_NOT_FOUND == rv) {
1436 // File is set to be just memory-only, notify the callbacks
1437 // and pretend dooming has succeeded. From point of view of
1438 // the entry it actually did - the data is gone and cannot be
1439 // reused.
1440 rv = NS_OK;
1441 }
1442 }
1444 // Always posts to the main thread.
1445 OnFileDoomed(rv);
1446 }
1448 void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync)
1449 {
1450 mLock.AssertCurrentThreadOwns();
1452 if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
1453 if (mBackgroundOperations.Set(aOperations))
1454 CacheStorageService::Self()->Dispatch(this);
1456 LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
1457 return;
1458 }
1460 mozilla::MutexAutoUnlock unlock(mLock);
1462 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1464 if (aOperations & Ops::FRECENCYUPDATE) {
1465 #ifndef M_LN2
1466 #define M_LN2 0.69314718055994530942
1467 #endif
1469 // Half-life is dynamic, in seconds.
1470 static double half_life = CacheObserver::HalfLifeSeconds();
1471 // Must convert from seconds to milliseconds since PR_Now() gives usecs.
1472 static double const decay = (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
1474 double now_decay = static_cast<double>(PR_Now()) * decay;
1476 if (mFrecency == 0) {
1477 mFrecency = now_decay;
1478 }
1479 else {
1480 // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1) but
1481 // more precise.
1482 mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
1483 }
1484 LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this, mFrecency));
1486 // Because CacheFile::Set*() are not thread-safe to use (uses WeakReference that
1487 // is not thread-safe) we must post to the main thread...
1488 nsRefPtr<nsRunnableMethod<CacheEntry> > event =
1489 NS_NewRunnableMethod(this, &CacheEntry::StoreFrecency);
1490 NS_DispatchToMainThread(event);
1491 }
1493 if (aOperations & Ops::REGISTER) {
1494 LOG(("CacheEntry REGISTER [this=%p]", this));
1496 CacheStorageService::Self()->RegisterEntry(this);
1497 }
1499 if (aOperations & Ops::UNREGISTER) {
1500 LOG(("CacheEntry UNREGISTER [this=%p]", this));
1502 CacheStorageService::Self()->UnregisterEntry(this);
1503 }
1505 if (aOperations & Ops::CALLBACKS) {
1506 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
1508 mozilla::MutexAutoLock lock(mLock);
1509 InvokeCallbacks();
1510 }
1511 }
1513 void CacheEntry::StoreFrecency()
1514 {
1515 // No need for thread safety over mFrecency, it will be rewriten
1516 // correctly on following invocation if broken by concurrency.
1517 MOZ_ASSERT(NS_IsMainThread());
1518 mFile->SetFrecency(FRECENCY2INT(mFrecency));
1519 }
1521 // CacheOutputCloseListener
1523 CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
1524 : mEntry(aEntry)
1525 {
1526 MOZ_COUNT_CTOR(CacheOutputCloseListener);
1527 }
1529 CacheOutputCloseListener::~CacheOutputCloseListener()
1530 {
1531 MOZ_COUNT_DTOR(CacheOutputCloseListener);
1532 }
1534 void CacheOutputCloseListener::OnOutputClosed()
1535 {
1536 // We need this class and to redispatch since this callback is invoked
1537 // under the file's lock and to do the job we need to enter the entry's
1538 // lock too. That would lead to potential deadlocks.
1539 NS_DispatchToCurrentThread(this);
1540 }
1542 NS_IMETHODIMP CacheOutputCloseListener::Run()
1543 {
1544 mEntry->OnOutputClosed();
1545 return NS_OK;
1546 }
1548 // Memory reporting
1550 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1551 {
1552 size_t n = 0;
1553 nsCOMPtr<nsISizeOf> sizeOf;
1555 n += mCallbacks.SizeOfExcludingThis(mallocSizeOf);
1556 if (mFile) {
1557 n += mFile->SizeOfIncludingThis(mallocSizeOf);
1558 }
1560 sizeOf = do_QueryInterface(mURI);
1561 if (sizeOf) {
1562 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
1563 }
1565 n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1566 n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1568 // mDoomCallback is an arbitrary class that is probably reported elsewhere.
1569 // mOutputStream is reported in mFile.
1570 // mWriter is one of many handles we create, but (intentionally) not keep
1571 // any reference to, so those unfortunatelly cannot be reported. Handles are
1572 // small, though.
1573 // mSecurityInfo doesn't impl nsISizeOf.
1575 return n;
1576 }
1578 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1579 {
1580 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1581 }
1583 } // net
1584 } // mozilla