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/. */
4 
5 #include "CacheLog.h"
6 #include "CacheEntry.h"
7 #include "CacheStorageService.h"
8 #include "CacheObserver.h"
9 #include "CacheFileUtils.h"
10 #include "CacheIndex.h"
11 
12 #include "nsIAsyncOutputStream.h"
13 #include "nsIInputStream.h"
14 #include "nsIOutputStream.h"
15 #include "nsISeekableStream.h"
16 #include "nsIURI.h"
17 #include "nsICacheEntryOpenCallback.h"
18 #include "nsICacheStorage.h"
19 #include "nsISerializable.h"
20 #include "nsISizeOf.h"
21 
22 #include "nsComponentManagerUtils.h"
23 #include "nsServiceManagerUtils.h"
24 #include "nsString.h"
25 #include "nsProxyRelease.h"
26 #include "nsSerializationHelper.h"
27 #include "nsThreadUtils.h"
28 #include "mozilla/Telemetry.h"
29 #include "mozilla/IntegerPrintfMacros.h"
30 #include <math.h>
31 #include <algorithm>
32 
33 namespace mozilla::net {
34 
35 static uint32_t const ENTRY_WANTED = nsICacheEntryOpenCallback::ENTRY_WANTED;
36 static uint32_t const RECHECK_AFTER_WRITE_FINISHED =
37     nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
38 static uint32_t const ENTRY_NEEDS_REVALIDATION =
39     nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION;
40 static uint32_t const ENTRY_NOT_WANTED =
41     nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
42 
NS_IMPL_ISUPPORTS(CacheEntryHandle,nsICacheEntry)43 NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry)
44 
45 // CacheEntryHandle
46 
47 CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry) : mEntry(aEntry) {
48 #ifdef DEBUG
49   if (!mEntry->HandlesCount()) {
50     // CacheEntry.mHandlesCount must go from zero to one only under
51     // the service lock. Can access CacheStorageService::Self() w/o a check
52     // since CacheEntry hrefs it.
53     CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
54   }
55 #endif
56 
57   mEntry->AddHandleRef();
58 
59   LOG(("New CacheEntryHandle %p for entry %p", this, aEntry));
60 }
61 
Dismiss()62 NS_IMETHODIMP CacheEntryHandle::Dismiss() {
63   LOG(("CacheEntryHandle::Dismiss %p", this));
64 
65   if (mClosed.compareExchange(false, true)) {
66     mEntry->OnHandleClosed(this);
67     return NS_OK;
68   }
69 
70   LOG(("  already dropped"));
71   return NS_ERROR_UNEXPECTED;
72 }
73 
~CacheEntryHandle()74 CacheEntryHandle::~CacheEntryHandle() {
75   mEntry->ReleaseHandleRef();
76   Dismiss();
77 
78   LOG(("CacheEntryHandle::~CacheEntryHandle %p", this));
79 }
80 
81 // CacheEntry::Callback
82 
Callback(CacheEntry * aEntry,nsICacheEntryOpenCallback * aCallback,bool aReadOnly,bool aCheckOnAnyThread,bool aSecret)83 CacheEntry::Callback::Callback(CacheEntry* aEntry,
84                                nsICacheEntryOpenCallback* aCallback,
85                                bool aReadOnly, bool aCheckOnAnyThread,
86                                bool aSecret)
87     : mEntry(aEntry),
88       mCallback(aCallback),
89       mTarget(GetCurrentEventTarget()),
90       mReadOnly(aReadOnly),
91       mRevalidating(false),
92       mCheckOnAnyThread(aCheckOnAnyThread),
93       mRecheckAfterWrite(false),
94       mNotWanted(false),
95       mSecret(aSecret),
96       mDoomWhenFoundPinned(false),
97       mDoomWhenFoundNonPinned(false) {
98   MOZ_COUNT_CTOR(CacheEntry::Callback);
99 
100   // The counter may go from zero to non-null only under the service lock
101   // but here we expect it to be already positive.
102   MOZ_ASSERT(mEntry->HandlesCount());
103   mEntry->AddHandleRef();
104 }
105 
Callback(CacheEntry * aEntry,bool aDoomWhenFoundInPinStatus)106 CacheEntry::Callback::Callback(CacheEntry* aEntry,
107                                bool aDoomWhenFoundInPinStatus)
108     : mEntry(aEntry),
109       mReadOnly(false),
110       mRevalidating(false),
111       mCheckOnAnyThread(true),
112       mRecheckAfterWrite(false),
113       mNotWanted(false),
114       mSecret(false),
115       mDoomWhenFoundPinned(aDoomWhenFoundInPinStatus),
116       mDoomWhenFoundNonPinned(!aDoomWhenFoundInPinStatus) {
117   MOZ_COUNT_CTOR(CacheEntry::Callback);
118   MOZ_ASSERT(mEntry->HandlesCount());
119   mEntry->AddHandleRef();
120 }
121 
Callback(CacheEntry::Callback const & aThat)122 CacheEntry::Callback::Callback(CacheEntry::Callback const& aThat)
123     : mEntry(aThat.mEntry),
124       mCallback(aThat.mCallback),
125       mTarget(aThat.mTarget),
126       mReadOnly(aThat.mReadOnly),
127       mRevalidating(aThat.mRevalidating),
128       mCheckOnAnyThread(aThat.mCheckOnAnyThread),
129       mRecheckAfterWrite(aThat.mRecheckAfterWrite),
130       mNotWanted(aThat.mNotWanted),
131       mSecret(aThat.mSecret),
132       mDoomWhenFoundPinned(aThat.mDoomWhenFoundPinned),
133       mDoomWhenFoundNonPinned(aThat.mDoomWhenFoundNonPinned) {
134   MOZ_COUNT_CTOR(CacheEntry::Callback);
135 
136   // The counter may go from zero to non-null only under the service lock
137   // but here we expect it to be already positive.
138   MOZ_ASSERT(mEntry->HandlesCount());
139   mEntry->AddHandleRef();
140 }
141 
~Callback()142 CacheEntry::Callback::~Callback() {
143   ProxyRelease("CacheEntry::Callback::mCallback", mCallback, mTarget);
144 
145   mEntry->ReleaseHandleRef();
146   MOZ_COUNT_DTOR(CacheEntry::Callback);
147 }
148 
ExchangeEntry(CacheEntry * aEntry)149 void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry) {
150   if (mEntry == aEntry) return;
151 
152   // The counter may go from zero to non-null only under the service lock
153   // but here we expect it to be already positive.
154   MOZ_ASSERT(aEntry->HandlesCount());
155   aEntry->AddHandleRef();
156   mEntry->ReleaseHandleRef();
157   mEntry = aEntry;
158 }
159 
DeferDoom(bool * aDoom) const160 bool CacheEntry::Callback::DeferDoom(bool* aDoom) const {
161   MOZ_ASSERT(mEntry->mPinningKnown);
162 
163   if (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) ||
164       MOZ_UNLIKELY(mDoomWhenFoundPinned)) {
165     *aDoom =
166         (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) &&
167          MOZ_LIKELY(!mEntry->mPinned)) ||
168         (MOZ_UNLIKELY(mDoomWhenFoundPinned) && MOZ_UNLIKELY(mEntry->mPinned));
169 
170     return true;
171   }
172 
173   return false;
174 }
175 
OnCheckThread(bool * aOnCheckThread) const176 nsresult CacheEntry::Callback::OnCheckThread(bool* aOnCheckThread) const {
177   if (!mCheckOnAnyThread) {
178     // Check we are on the target
179     return mTarget->IsOnCurrentThread(aOnCheckThread);
180   }
181 
182   // We can invoke check anywhere
183   *aOnCheckThread = true;
184   return NS_OK;
185 }
186 
OnAvailThread(bool * aOnAvailThread) const187 nsresult CacheEntry::Callback::OnAvailThread(bool* aOnAvailThread) const {
188   return mTarget->IsOnCurrentThread(aOnAvailThread);
189 }
190 
191 // CacheEntry
192 
NS_IMPL_ISUPPORTS(CacheEntry,nsIRunnable,CacheFileListener)193 NS_IMPL_ISUPPORTS(CacheEntry, nsIRunnable, CacheFileListener)
194 
195 /* static */
196 uint64_t CacheEntry::GetNextId() {
197   static Atomic<uint64_t, Relaxed> id(0);
198   return ++id;
199 }
200 
CacheEntry(const nsACString & aStorageID,const nsACString & aURI,const nsACString & aEnhanceID,bool aUseDisk,bool aSkipSizeCheck,bool aPin)201 CacheEntry::CacheEntry(const nsACString& aStorageID, const nsACString& aURI,
202                        const nsACString& aEnhanceID, bool aUseDisk,
203                        bool aSkipSizeCheck, bool aPin)
204     : mURI(aURI),
205       mEnhanceID(aEnhanceID),
206       mStorageID(aStorageID),
207       mUseDisk(aUseDisk),
208       mSkipSizeCheck(aSkipSizeCheck),
209       mSecurityInfoLoaded(false),
210       mPreventCallbacks(false),
211       mHasData(false),
212       mPinned(aPin),
213       mPinningKnown(false),
214       mCacheEntryId(GetNextId()) {
215   LOG(("CacheEntry::CacheEntry [this=%p]", this));
216 
217   mService = CacheStorageService::Self();
218 
219   CacheStorageService::Self()->RecordMemoryOnlyEntry(this, !aUseDisk,
220                                                      true /* overwrite */);
221 }
222 
~CacheEntry()223 CacheEntry::~CacheEntry() { LOG(("CacheEntry::~CacheEntry [this=%p]", this)); }
224 
StateString(uint32_t aState)225 char const* CacheEntry::StateString(uint32_t aState) {
226   switch (aState) {
227     case NOTLOADED:
228       return "NOTLOADED";
229     case LOADING:
230       return "LOADING";
231     case EMPTY:
232       return "EMPTY";
233     case WRITING:
234       return "WRITING";
235     case READY:
236       return "READY";
237     case REVALIDATING:
238       return "REVALIDATING";
239   }
240 
241   return "?";
242 }
243 
HashingKeyWithStorage(nsACString & aResult) const244 nsresult CacheEntry::HashingKeyWithStorage(nsACString& aResult) const {
245   return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
246 }
247 
HashingKey(nsACString & aResult) const248 nsresult CacheEntry::HashingKey(nsACString& aResult) const {
249   return HashingKey(""_ns, mEnhanceID, mURI, aResult);
250 }
251 
252 // static
HashingKey(const nsACString & aStorageID,const nsACString & aEnhanceID,nsIURI * aURI,nsACString & aResult)253 nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
254                                 const nsACString& aEnhanceID, nsIURI* aURI,
255                                 nsACString& aResult) {
256   nsAutoCString spec;
257   nsresult rv = aURI->GetAsciiSpec(spec);
258   NS_ENSURE_SUCCESS(rv, rv);
259 
260   return HashingKey(aStorageID, aEnhanceID, spec, aResult);
261 }
262 
263 // static
HashingKey(const nsACString & aStorageID,const nsACString & aEnhanceID,const nsACString & aURISpec,nsACString & aResult)264 nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
265                                 const nsACString& aEnhanceID,
266                                 const nsACString& aURISpec,
267                                 nsACString& aResult) {
268   /**
269    * This key is used to salt hash that is a base for disk file name.
270    * Changing it will cause we will not be able to find files on disk.
271    */
272 
273   aResult.Assign(aStorageID);
274 
275   if (!aEnhanceID.IsEmpty()) {
276     CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID);
277   }
278 
279   // Appending directly
280   aResult.Append(':');
281   aResult.Append(aURISpec);
282 
283   return NS_OK;
284 }
285 
AsyncOpen(nsICacheEntryOpenCallback * aCallback,uint32_t aFlags)286 void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback,
287                            uint32_t aFlags) {
288   LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]", this,
289        StateString(mState), aFlags, aCallback));
290 
291   bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
292   bool bypassIfBusy = aFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY;
293   bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
294   bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
295   bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
296   bool secret = aFlags & nsICacheStorage::OPEN_SECRETLY;
297 
298   MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
299   MOZ_ASSERT(!(truncate && mState > LOADING),
300              "Must not call truncate on already loaded entry");
301 
302   Callback callback(this, aCallback, readonly, multithread, secret);
303 
304   if (!Open(callback, truncate, priority, bypassIfBusy)) {
305     // We get here when the callback wants to bypass cache when it's busy.
306     LOG(("  writing or revalidating, callback wants to bypass cache"));
307     callback.mNotWanted = true;
308     InvokeAvailableCallback(callback);
309   }
310 }
311 
Open(Callback & aCallback,bool aTruncate,bool aPriority,bool aBypassIfBusy)312 bool CacheEntry::Open(Callback& aCallback, bool aTruncate, bool aPriority,
313                       bool aBypassIfBusy) {
314   mozilla::MutexAutoLock lock(mLock);
315 
316   // Check state under the lock
317   if (aBypassIfBusy && (mState == WRITING || mState == REVALIDATING)) {
318     return false;
319   }
320 
321   RememberCallback(aCallback);
322 
323   // Load() opens the lock
324   if (Load(aTruncate, aPriority)) {
325     // Loading is in progress...
326     return true;
327   }
328 
329   InvokeCallbacks();
330 
331   return true;
332 }
333 
Load(bool aTruncate,bool aPriority)334 bool CacheEntry::Load(bool aTruncate, bool aPriority) {
335   LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
336 
337   mLock.AssertCurrentThreadOwns();
338 
339   if (mState > LOADING) {
340     LOG(("  already loaded"));
341     return false;
342   }
343 
344   if (mState == LOADING) {
345     LOG(("  already loading"));
346     return true;
347   }
348 
349   mState = LOADING;
350 
351   MOZ_ASSERT(!mFile);
352 
353   nsresult rv;
354 
355   nsAutoCString fileKey;
356   rv = HashingKeyWithStorage(fileKey);
357 
358   bool reportMiss = false;
359 
360   // Check the index under two conditions for two states and take appropriate
361   // action:
362   // 1. When this is a disk entry and not told to truncate, check there is a
363   //    disk file.
364   //    If not, set the 'truncate' flag to true so that this entry will open
365   //    instantly as a new one.
366   // 2. When this is a memory-only entry, check there is a disk file.
367   //    If there is or could be, doom that file.
368   if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) {
369     // Check the index right now to know we have or have not the entry
370     // as soon as possible.
371     CacheIndex::EntryStatus status;
372     if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) {
373       switch (status) {
374         case CacheIndex::DOES_NOT_EXIST:
375           // Doesn't apply to memory-only entries, Load() is called only once
376           // for them and never again for their session lifetime.
377           if (!aTruncate && mUseDisk) {
378             LOG(
379                 ("  entry doesn't exist according information from the index, "
380                  "truncating"));
381             reportMiss = true;
382             aTruncate = true;
383           }
384           break;
385         case CacheIndex::EXISTS:
386         case CacheIndex::DO_NOT_KNOW:
387           if (!mUseDisk) {
388             LOG(
389                 ("  entry open as memory-only, but there is a file, status=%d, "
390                  "dooming it",
391                  status));
392             CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
393           }
394           break;
395       }
396     }
397   }
398 
399   mFile = new CacheFile();
400 
401   BackgroundOp(Ops::REGISTER);
402 
403   bool directLoad = aTruncate || !mUseDisk;
404   if (directLoad) {
405     // mLoadStart will be used to calculate telemetry of life-time of this
406     // entry. Low resulution is then enough.
407     mLoadStart = TimeStamp::NowLoRes();
408     mPinningKnown = true;
409   } else {
410     mLoadStart = TimeStamp::Now();
411   }
412 
413   {
414     mozilla::MutexAutoUnlock unlock(mLock);
415 
416     if (reportMiss) {
417       CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
418           CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
419     }
420 
421     LOG(("  performing load, file=%p", mFile.get()));
422     if (NS_SUCCEEDED(rv)) {
423       rv = mFile->Init(fileKey, aTruncate, !mUseDisk, mSkipSizeCheck, aPriority,
424                        mPinned, directLoad ? nullptr : this);
425     }
426 
427     if (NS_FAILED(rv)) {
428       mFileStatus = rv;
429       AsyncDoom(nullptr);
430       return false;
431     }
432   }
433 
434   if (directLoad) {
435     // Just fake the load has already been done as "new".
436     mFileStatus = NS_OK;
437     mState = EMPTY;
438   }
439 
440   return mState == LOADING;
441 }
442 
OnFileReady(nsresult aResult,bool aIsNew)443 NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew) {
444   LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08" PRIx32 ", new=%d]", this,
445        static_cast<uint32_t>(aResult), aIsNew));
446 
447   MOZ_ASSERT(!mLoadStart.IsNull());
448 
449   if (NS_SUCCEEDED(aResult)) {
450     if (aIsNew) {
451       CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
452           CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
453     } else {
454       CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
455           CacheFileUtils::DetailedCacheHitTelemetry::HIT, mLoadStart);
456     }
457   }
458 
459   // OnFileReady, that is the only code that can transit from LOADING
460   // to any follow-on state and can only be invoked ones on an entry.
461   // Until this moment there is no consumer that could manipulate
462   // the entry state.
463 
464   mozilla::MutexAutoLock lock(mLock);
465 
466   MOZ_ASSERT(mState == LOADING);
467 
468   mState = (aIsNew || NS_FAILED(aResult)) ? EMPTY : READY;
469 
470   mFileStatus = aResult;
471 
472   mPinned = mFile->IsPinned();
473   ;
474   mPinningKnown = true;
475   LOG(("  pinning=%d", mPinned));
476 
477   if (mState == READY) {
478     mHasData = true;
479 
480     uint32_t frecency;
481     mFile->GetFrecency(&frecency);
482     // mFrecency is held in a double to increase computance precision.
483     // It is ok to persist frecency only as a uint32 with some math involved.
484     mFrecency = INT2FRECENCY(frecency);
485   }
486 
487   InvokeCallbacks();
488 
489   return NS_OK;
490 }
491 
OnFileDoomed(nsresult aResult)492 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult) {
493   if (mDoomCallback) {
494     RefPtr<DoomCallbackRunnable> event =
495         new DoomCallbackRunnable(this, aResult);
496     NS_DispatchToMainThread(event);
497   }
498 
499   return NS_OK;
500 }
501 
ReopenTruncated(bool aMemoryOnly,nsICacheEntryOpenCallback * aCallback)502 already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(
503     bool aMemoryOnly, nsICacheEntryOpenCallback* aCallback) {
504   LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
505 
506   mLock.AssertCurrentThreadOwns();
507 
508   // Hold callbacks invocation, AddStorageEntry would invoke from doom
509   // prematurly
510   mPreventCallbacks = true;
511 
512   RefPtr<CacheEntryHandle> handle;
513   RefPtr<CacheEntry> newEntry;
514   {
515     if (mPinned) {
516       MOZ_ASSERT(mUseDisk);
517       // We want to pin even no-store entries (the case we recreate a disk entry
518       // as a memory-only entry.)
519       aMemoryOnly = false;
520     }
521 
522     mozilla::MutexAutoUnlock unlock(mLock);
523 
524     // The following call dooms this entry (calls DoomAlreadyRemoved on us)
525     nsresult rv = CacheStorageService::Self()->AddStorageEntry(
526         GetStorageID(), GetURI(), GetEnhanceID(), mUseDisk && !aMemoryOnly,
527         mSkipSizeCheck, mPinned,
528         true,  // truncate existing (this one)
529         getter_AddRefs(handle));
530 
531     if (NS_SUCCEEDED(rv)) {
532       newEntry = handle->Entry();
533       LOG(("  exchanged entry %p by entry %p, rv=0x%08" PRIx32, this,
534            newEntry.get(), static_cast<uint32_t>(rv)));
535       newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
536     } else {
537       LOG(("  exchanged of entry %p failed, rv=0x%08" PRIx32, this,
538            static_cast<uint32_t>(rv)));
539       AsyncDoom(nullptr);
540     }
541   }
542 
543   mPreventCallbacks = false;
544 
545   if (!newEntry) return nullptr;
546 
547   newEntry->TransferCallbacks(*this);
548   mCallbacks.Clear();
549 
550   // Must return a new write handle, since the consumer is expected to
551   // write to this newly recreated entry.  The |handle| is only a common
552   // reference counter and doesn't revert entry state back when write
553   // fails and also doesn't update the entry frecency.  Not updating
554   // frecency causes entries to not be purged from our memory pools.
555   RefPtr<CacheEntryHandle> writeHandle = newEntry->NewWriteHandle();
556   return writeHandle.forget();
557 }
558 
TransferCallbacks(CacheEntry & aFromEntry)559 void CacheEntry::TransferCallbacks(CacheEntry& aFromEntry) {
560   mozilla::MutexAutoLock lock(mLock);
561 
562   LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]", this, &aFromEntry));
563 
564   if (!mCallbacks.Length()) {
565     mCallbacks.SwapElements(aFromEntry.mCallbacks);
566   } else {
567     mCallbacks.AppendElements(aFromEntry.mCallbacks);
568   }
569 
570   uint32_t callbacksLength = mCallbacks.Length();
571   if (callbacksLength) {
572     // Carry the entry reference (unfortunately, needs to be done manually...)
573     for (uint32_t i = 0; i < callbacksLength; ++i) {
574       mCallbacks[i].ExchangeEntry(this);
575     }
576 
577     BackgroundOp(Ops::CALLBACKS, true);
578   }
579 }
580 
RememberCallback(Callback & aCallback)581 void CacheEntry::RememberCallback(Callback& aCallback) {
582   mLock.AssertCurrentThreadOwns();
583 
584   LOG(("CacheEntry::RememberCallback [this=%p, cb=%p, state=%s]", this,
585        aCallback.mCallback.get(), StateString(mState)));
586 
587   mCallbacks.AppendElement(aCallback);
588 }
589 
InvokeCallbacksLock()590 void CacheEntry::InvokeCallbacksLock() {
591   mozilla::MutexAutoLock lock(mLock);
592   InvokeCallbacks();
593 }
594 
InvokeCallbacks()595 void CacheEntry::InvokeCallbacks() {
596   mLock.AssertCurrentThreadOwns();
597 
598   LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
599 
600   // Invoke first all r/w callbacks, then all r/o callbacks.
601   if (InvokeCallbacks(false)) InvokeCallbacks(true);
602 
603   LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
604 }
605 
InvokeCallbacks(bool aReadOnly)606 bool CacheEntry::InvokeCallbacks(bool aReadOnly) {
607   mLock.AssertCurrentThreadOwns();
608 
609   RefPtr<CacheEntryHandle> recreatedHandle;
610 
611   uint32_t i = 0;
612   while (i < mCallbacks.Length()) {
613     if (mPreventCallbacks) {
614       LOG(("  callbacks prevented!"));
615       return false;
616     }
617 
618     if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
619       LOG(("  entry is being written/revalidated"));
620       return false;
621     }
622 
623     bool recreate;
624     if (mCallbacks[i].DeferDoom(&recreate)) {
625       mCallbacks.RemoveElementAt(i);
626       if (!recreate) {
627         continue;
628       }
629 
630       LOG(("  defer doom marker callback hit positive, recreating"));
631       recreatedHandle = ReopenTruncated(!mUseDisk, nullptr);
632       break;
633     }
634 
635     if (mCallbacks[i].mReadOnly != aReadOnly) {
636       // Callback is not r/w or r/o, go to another one in line
637       ++i;
638       continue;
639     }
640 
641     bool onCheckThread;
642     nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
643 
644     if (NS_SUCCEEDED(rv) && !onCheckThread) {
645       // Redispatch to the target thread
646       rv = mCallbacks[i].mTarget->Dispatch(
647           NewRunnableMethod("net::CacheEntry::InvokeCallbacksLock", this,
648                             &CacheEntry::InvokeCallbacksLock),
649           nsIEventTarget::DISPATCH_NORMAL);
650       if (NS_SUCCEEDED(rv)) {
651         LOG(("  re-dispatching to target thread"));
652         return false;
653       }
654     }
655 
656     Callback callback = mCallbacks[i];
657     mCallbacks.RemoveElementAt(i);
658 
659     if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
660       // Callback didn't fire, put it back and go to another one in line.
661       // Only reason InvokeCallback returns false is that onCacheEntryCheck
662       // returns RECHECK_AFTER_WRITE_FINISHED.  If we would stop the loop, other
663       // readers or potential writers would be unnecessarily kept from being
664       // invoked.
665       size_t pos = std::min(mCallbacks.Length(), static_cast<size_t>(i));
666       mCallbacks.InsertElementAt(pos, callback);
667       ++i;
668     }
669   }
670 
671   if (recreatedHandle) {
672     // Must be released outside of the lock, enters InvokeCallback on the new
673     // entry
674     mozilla::MutexAutoUnlock unlock(mLock);
675     recreatedHandle = nullptr;
676   }
677 
678   return true;
679 }
680 
InvokeCallback(Callback & aCallback)681 bool CacheEntry::InvokeCallback(Callback& aCallback) {
682   LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", this,
683        StateString(mState), aCallback.mCallback.get()));
684 
685   mLock.AssertCurrentThreadOwns();
686 
687   // When this entry is doomed we want to notify the callback any time
688   if (!mIsDoomed) {
689     // When we are here, the entry must be loaded from disk
690     MOZ_ASSERT(mState > LOADING);
691 
692     if (mState == WRITING || mState == REVALIDATING) {
693       // Prevent invoking other callbacks since one of them is now writing
694       // or revalidating this entry.  No consumers should get this entry
695       // until metadata are filled with values downloaded from the server
696       // or the entry revalidated and output stream has been opened.
697       LOG(("  entry is being written/revalidated, callback bypassed"));
698       return false;
699     }
700 
701     // mRecheckAfterWrite flag already set means the callback has already passed
702     // the onCacheEntryCheck call. Until the current write is not finished this
703     // callback will be bypassed.
704     if (!aCallback.mRecheckAfterWrite) {
705       if (!aCallback.mReadOnly) {
706         if (mState == EMPTY) {
707           // Advance to writing state, we expect to invoke the callback and let
708           // it fill content of this entry.  Must set and check the state here
709           // to prevent more then one
710           mState = WRITING;
711           LOG(("  advancing to WRITING state"));
712         }
713 
714         if (!aCallback.mCallback) {
715           // We can be given no callback only in case of recreate, it is ok
716           // to advance to WRITING state since the caller of recreate is
717           // expected to write this entry now.
718           return true;
719         }
720       }
721 
722       if (mState == READY) {
723         // Metadata present, validate the entry
724         uint32_t checkResult;
725         {
726           // mayhemer: TODO check and solve any potential races of concurent
727           // OnCacheEntryCheck
728           mozilla::MutexAutoUnlock unlock(mLock);
729 
730           RefPtr<CacheEntryHandle> handle = NewHandle();
731 
732           nsresult rv =
733               aCallback.mCallback->OnCacheEntryCheck(handle, &checkResult);
734           LOG(("  OnCacheEntryCheck: rv=0x%08" PRIx32 ", result=%" PRId32,
735                static_cast<uint32_t>(rv), static_cast<uint32_t>(checkResult)));
736 
737           if (NS_FAILED(rv)) checkResult = ENTRY_NOT_WANTED;
738         }
739 
740         aCallback.mRevalidating = checkResult == ENTRY_NEEDS_REVALIDATION;
741 
742         switch (checkResult) {
743           case ENTRY_WANTED:
744             // Nothing more to do here, the consumer is responsible to handle
745             // the result of OnCacheEntryCheck it self.
746             // Proceed to callback...
747             break;
748 
749           case RECHECK_AFTER_WRITE_FINISHED:
750             LOG(
751                 ("  consumer will check on the entry again after write is "
752                  "done"));
753             // The consumer wants the entry to complete first.
754             aCallback.mRecheckAfterWrite = true;
755             break;
756 
757           case ENTRY_NEEDS_REVALIDATION:
758             LOG(("  will be holding callbacks until entry is revalidated"));
759             // State is READY now and from that state entry cannot transit to
760             // any other state then REVALIDATING for which cocurrency is not an
761             // issue.  Potentially no need to lock here.
762             mState = REVALIDATING;
763             break;
764 
765           case ENTRY_NOT_WANTED:
766             LOG(("  consumer not interested in the entry"));
767             // Do not give this entry to the consumer, it is not interested in
768             // us.
769             aCallback.mNotWanted = true;
770             break;
771         }
772       }
773     }
774   }
775 
776   if (aCallback.mCallback) {
777     if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
778       // If we don't have data and the callback wants a complete entry,
779       // don't invoke now.
780       bool bypass = !mHasData;
781       if (!bypass && NS_SUCCEEDED(mFileStatus)) {
782         int64_t _unused;
783         bypass = !mFile->DataSize(&_unused);
784       }
785 
786       if (bypass) {
787         LOG(("  bypassing, entry data still being written"));
788         return false;
789       }
790 
791       // Entry is complete now, do the check+avail call again
792       aCallback.mRecheckAfterWrite = false;
793       return InvokeCallback(aCallback);
794     }
795 
796     mozilla::MutexAutoUnlock unlock(mLock);
797     InvokeAvailableCallback(aCallback);
798   }
799 
800   return true;
801 }
802 
InvokeAvailableCallback(Callback const & aCallback)803 void CacheEntry::InvokeAvailableCallback(Callback const& aCallback) {
804   LOG(
805       ("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, "
806        "n/w=%d]",
807        this, StateString(mState), aCallback.mCallback.get(),
808        aCallback.mReadOnly, aCallback.mNotWanted));
809 
810   nsresult rv;
811 
812   uint32_t state;
813   {
814     mozilla::MutexAutoLock lock(mLock);
815     state = mState;
816   }
817 
818   // When we are here, the entry must be loaded from disk
819   MOZ_ASSERT(state > LOADING || mIsDoomed);
820 
821   bool onAvailThread;
822   rv = aCallback.OnAvailThread(&onAvailThread);
823   if (NS_FAILED(rv)) {
824     LOG(("  target thread dead?"));
825     return;
826   }
827 
828   if (!onAvailThread) {
829     // Dispatch to the right thread
830     RefPtr<AvailableCallbackRunnable> event =
831         new AvailableCallbackRunnable(this, aCallback);
832 
833     rv = aCallback.mTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
834     LOG(("  redispatched, (rv = 0x%08" PRIx32 ")", static_cast<uint32_t>(rv)));
835     return;
836   }
837 
838   if (mIsDoomed || aCallback.mNotWanted) {
839     LOG(
840         ("  doomed or not wanted, notifying OCEA with "
841          "NS_ERROR_CACHE_KEY_NOT_FOUND"));
842     aCallback.mCallback->OnCacheEntryAvailable(nullptr, false,
843                                                NS_ERROR_CACHE_KEY_NOT_FOUND);
844     return;
845   }
846 
847   if (state == READY) {
848     LOG(("  ready/has-meta, notifying OCEA with entry and NS_OK"));
849 
850     if (!aCallback.mSecret) {
851       mozilla::MutexAutoLock lock(mLock);
852       BackgroundOp(Ops::FRECENCYUPDATE);
853     }
854 
855     OnFetched(aCallback);
856 
857     RefPtr<CacheEntryHandle> handle = NewHandle();
858     aCallback.mCallback->OnCacheEntryAvailable(handle, false, NS_OK);
859     return;
860   }
861 
862   // R/O callbacks may do revalidation, let them fall through
863   if (aCallback.mReadOnly && !aCallback.mRevalidating) {
864     LOG(
865         ("  r/o and not ready, notifying OCEA with "
866          "NS_ERROR_CACHE_KEY_NOT_FOUND"));
867     aCallback.mCallback->OnCacheEntryAvailable(nullptr, false,
868                                                NS_ERROR_CACHE_KEY_NOT_FOUND);
869     return;
870   }
871 
872   // This is a new or potentially non-valid entry and needs to be fetched first.
873   // The CacheEntryHandle blocks other consumers until the channel
874   // either releases the entry or marks metadata as filled or whole entry valid,
875   // i.e. until MetaDataReady() or SetValid() on the entry is called
876   // respectively.
877 
878   // Consumer will be responsible to fill or validate the entry metadata and
879   // data.
880 
881   OnFetched(aCallback);
882 
883   RefPtr<CacheEntryHandle> handle = NewWriteHandle();
884   rv = aCallback.mCallback->OnCacheEntryAvailable(handle, state == WRITING,
885                                                   NS_OK);
886 
887   if (NS_FAILED(rv)) {
888     LOG(("  writing/revalidating failed (0x%08" PRIx32 ")",
889          static_cast<uint32_t>(rv)));
890 
891     // Consumer given a new entry failed to take care of the entry.
892     OnHandleClosed(handle);
893     return;
894   }
895 
896   LOG(("  writing/revalidating"));
897 }
898 
OnFetched(Callback const & aCallback)899 void CacheEntry::OnFetched(Callback const& aCallback) {
900   if (NS_SUCCEEDED(mFileStatus) && !aCallback.mSecret) {
901     // Let the last-fetched and fetch-count properties be updated.
902     mFile->OnFetched();
903   }
904 }
905 
NewHandle()906 CacheEntryHandle* CacheEntry::NewHandle() { return new CacheEntryHandle(this); }
907 
NewWriteHandle()908 CacheEntryHandle* CacheEntry::NewWriteHandle() {
909   mozilla::MutexAutoLock lock(mLock);
910 
911   // Ignore the OPEN_SECRETLY flag on purpose here, which should actually be
912   // used only along with OPEN_READONLY, but there is no need to enforce that.
913   BackgroundOp(Ops::FRECENCYUPDATE);
914 
915   return (mWriter = NewHandle());
916 }
917 
OnHandleClosed(CacheEntryHandle const * aHandle)918 void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle) {
919   LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this,
920        StateString(mState), aHandle));
921 
922   mozilla::MutexAutoLock lock(mLock);
923 
924   if (IsDoomed() && NS_SUCCEEDED(mFileStatus) &&
925       // Note: mHandlesCount is dropped before this method is called
926       (mHandlesCount == 0 ||
927        (mHandlesCount == 1 && mWriter && mWriter != aHandle))) {
928     // This entry is no longer referenced from outside and is doomed.
929     // We can do this also when there is just reference from the writer,
930     // no one else could ever reach the written data.
931     // Tell the file to kill the handle, i.e. bypass any I/O operations
932     // on it except removing the file.
933     mFile->Kill();
934   }
935 
936   if (mWriter != aHandle) {
937     LOG(("  not the writer"));
938     return;
939   }
940 
941   if (mOutputStream) {
942     LOG(("  abandoning phantom output stream"));
943     // No one took our internal output stream, so there are no data
944     // and output stream has to be open symultaneously with input stream
945     // on this entry again.
946     mHasData = false;
947     // This asynchronously ends up invoking callbacks on this entry
948     // through OnOutputClosed() call.
949     mOutputStream->Close();
950     mOutputStream = nullptr;
951   } else {
952     // We must always redispatch, otherwise there is a risk of stack
953     // overflow.  This code can recurse deeply.  It won't execute sooner
954     // than we release mLock.
955     BackgroundOp(Ops::CALLBACKS, true);
956   }
957 
958   mWriter = nullptr;
959 
960   if (mState == WRITING) {
961     LOG(("  reverting to state EMPTY - write failed"));
962     mState = EMPTY;
963   } else if (mState == REVALIDATING) {
964     LOG(("  reverting to state READY - reval failed"));
965     mState = READY;
966   }
967 
968   if (mState == READY && !mHasData) {
969     // We may get to this state when following steps happen:
970     // 1. a new entry is given to a consumer
971     // 2. the consumer calls MetaDataReady(), we transit to READY
972     // 3. abandons the entry w/o opening the output stream, mHasData left false
973     //
974     // In this case any following consumer will get a ready entry (with
975     // metadata) but in state like the entry data write was still happening (was
976     // in progress) and will indefinitely wait for the entry data or even the
977     // entry itself when RECHECK_AFTER_WRITE is returned from onCacheEntryCheck.
978     LOG(
979         ("  we are in READY state, pretend we have data regardless it"
980          " has actully been never touched"));
981     mHasData = true;
982   }
983 }
984 
OnOutputClosed()985 void CacheEntry::OnOutputClosed() {
986   // Called when the file's output stream is closed.  Invoke any callbacks
987   // waiting for complete entry.
988 
989   mozilla::MutexAutoLock lock(mLock);
990   InvokeCallbacks();
991 }
992 
IsReferenced() const993 bool CacheEntry::IsReferenced() const {
994   CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
995 
996   // Increasing this counter from 0 to non-null and this check both happen only
997   // under the service lock.
998   return mHandlesCount > 0;
999 }
1000 
IsFileDoomed()1001 bool CacheEntry::IsFileDoomed() {
1002   if (NS_SUCCEEDED(mFileStatus)) {
1003     return mFile->IsDoomed();
1004   }
1005 
1006   return false;
1007 }
1008 
GetMetadataMemoryConsumption()1009 uint32_t CacheEntry::GetMetadataMemoryConsumption() {
1010   NS_ENSURE_SUCCESS(mFileStatus, 0);
1011 
1012   uint32_t size;
1013   if (NS_FAILED(mFile->ElementsSize(&size))) return 0;
1014 
1015   return size;
1016 }
1017 
1018 // nsICacheEntry
1019 
GetPersistent(bool * aPersistToDisk)1020 nsresult CacheEntry::GetPersistent(bool* aPersistToDisk) {
1021   // No need to sync when only reading.
1022   // When consumer needs to be consistent with state of the memory storage
1023   // entries table, then let it use GetUseDisk getter that must be called under
1024   // the service lock.
1025   *aPersistToDisk = mUseDisk;
1026   return NS_OK;
1027 }
1028 
GetKey(nsACString & aKey)1029 nsresult CacheEntry::GetKey(nsACString& aKey) {
1030   aKey.Assign(mURI);
1031   return NS_OK;
1032 }
1033 
GetCacheEntryId(uint64_t * aCacheEntryId)1034 nsresult CacheEntry::GetCacheEntryId(uint64_t* aCacheEntryId) {
1035   *aCacheEntryId = mCacheEntryId;
1036   return NS_OK;
1037 }
1038 
GetFetchCount(int32_t * aFetchCount)1039 nsresult CacheEntry::GetFetchCount(int32_t* aFetchCount) {
1040   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1041 
1042   return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount));
1043 }
1044 
GetLastFetched(uint32_t * aLastFetched)1045 nsresult CacheEntry::GetLastFetched(uint32_t* aLastFetched) {
1046   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1047 
1048   return mFile->GetLastFetched(aLastFetched);
1049 }
1050 
GetLastModified(uint32_t * aLastModified)1051 nsresult CacheEntry::GetLastModified(uint32_t* aLastModified) {
1052   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1053 
1054   return mFile->GetLastModified(aLastModified);
1055 }
1056 
GetExpirationTime(uint32_t * aExpirationTime)1057 nsresult CacheEntry::GetExpirationTime(uint32_t* aExpirationTime) {
1058   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1059 
1060   return mFile->GetExpirationTime(aExpirationTime);
1061 }
1062 
GetOnStartTime(uint64_t * aTime)1063 nsresult CacheEntry::GetOnStartTime(uint64_t* aTime) {
1064   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1065   return mFile->GetOnStartTime(aTime);
1066 }
1067 
GetOnStopTime(uint64_t * aTime)1068 nsresult CacheEntry::GetOnStopTime(uint64_t* aTime) {
1069   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1070   return mFile->GetOnStopTime(aTime);
1071 }
1072 
SetNetworkTimes(uint64_t aOnStartTime,uint64_t aOnStopTime)1073 nsresult CacheEntry::SetNetworkTimes(uint64_t aOnStartTime,
1074                                      uint64_t aOnStopTime) {
1075   if (NS_SUCCEEDED(mFileStatus)) {
1076     return mFile->SetNetworkTimes(aOnStartTime, aOnStopTime);
1077   }
1078   return NS_ERROR_NOT_AVAILABLE;
1079 }
1080 
SetContentType(uint8_t aContentType)1081 nsresult CacheEntry::SetContentType(uint8_t aContentType) {
1082   NS_ENSURE_ARG_MAX(aContentType, nsICacheEntry::CONTENT_TYPE_LAST - 1);
1083 
1084   if (NS_SUCCEEDED(mFileStatus)) {
1085     return mFile->SetContentType(aContentType);
1086   }
1087   return NS_ERROR_NOT_AVAILABLE;
1088 }
1089 
GetIsForcedValid(bool * aIsForcedValid)1090 nsresult CacheEntry::GetIsForcedValid(bool* aIsForcedValid) {
1091   NS_ENSURE_ARG(aIsForcedValid);
1092 
1093   MOZ_ASSERT(mState > LOADING);
1094 
1095   {
1096     mozilla::MutexAutoLock lock(mLock);
1097     if (mPinned) {
1098       *aIsForcedValid = true;
1099       return NS_OK;
1100     }
1101   }
1102 
1103   nsAutoCString key;
1104   nsresult rv = HashingKey(key);
1105   if (NS_FAILED(rv)) {
1106     return rv;
1107   }
1108 
1109   *aIsForcedValid =
1110       CacheStorageService::Self()->IsForcedValidEntry(mStorageID, key);
1111   LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this,
1112        *aIsForcedValid));
1113 
1114   return NS_OK;
1115 }
1116 
ForceValidFor(uint32_t aSecondsToTheFuture)1117 nsresult CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture) {
1118   LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this,
1119        aSecondsToTheFuture));
1120 
1121   nsAutoCString key;
1122   nsresult rv = HashingKey(key);
1123   if (NS_FAILED(rv)) {
1124     return rv;
1125   }
1126 
1127   CacheStorageService::Self()->ForceEntryValidFor(mStorageID, key,
1128                                                   aSecondsToTheFuture);
1129 
1130   return NS_OK;
1131 }
1132 
SetExpirationTime(uint32_t aExpirationTime)1133 nsresult CacheEntry::SetExpirationTime(uint32_t aExpirationTime) {
1134   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1135 
1136   nsresult rv = mFile->SetExpirationTime(aExpirationTime);
1137   NS_ENSURE_SUCCESS(rv, rv);
1138 
1139   // Aligned assignment, thus atomic.
1140   mSortingExpirationTime = aExpirationTime;
1141   return NS_OK;
1142 }
1143 
OpenInputStream(int64_t offset,nsIInputStream ** _retval)1144 nsresult CacheEntry::OpenInputStream(int64_t offset, nsIInputStream** _retval) {
1145   LOG(("CacheEntry::OpenInputStream [this=%p]", this));
1146   return OpenInputStreamInternal(offset, nullptr, _retval);
1147 }
1148 
OpenAlternativeInputStream(const nsACString & type,nsIInputStream ** _retval)1149 nsresult CacheEntry::OpenAlternativeInputStream(const nsACString& type,
1150                                                 nsIInputStream** _retval) {
1151   LOG(("CacheEntry::OpenAlternativeInputStream [this=%p, type=%s]", this,
1152        PromiseFlatCString(type).get()));
1153   return OpenInputStreamInternal(0, PromiseFlatCString(type).get(), _retval);
1154 }
1155 
OpenInputStreamInternal(int64_t offset,const char * aAltDataType,nsIInputStream ** _retval)1156 nsresult CacheEntry::OpenInputStreamInternal(int64_t offset,
1157                                              const char* aAltDataType,
1158                                              nsIInputStream** _retval) {
1159   LOG(("CacheEntry::OpenInputStreamInternal [this=%p]", this));
1160 
1161   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1162 
1163   nsresult rv;
1164 
1165   RefPtr<CacheEntryHandle> selfHandle = NewHandle();
1166 
1167   nsCOMPtr<nsIInputStream> stream;
1168   if (aAltDataType) {
1169     rv = mFile->OpenAlternativeInputStream(selfHandle, aAltDataType,
1170                                            getter_AddRefs(stream));
1171     if (NS_FAILED(rv)) {
1172       // Failure of this method may be legal when the alternative data requested
1173       // is not avaialble or of a different type.  Console error logs are
1174       // ensured by CacheFile::OpenAlternativeInputStream.
1175       return rv;
1176     }
1177   } else {
1178     rv = mFile->OpenInputStream(selfHandle, getter_AddRefs(stream));
1179     NS_ENSURE_SUCCESS(rv, rv);
1180   }
1181 
1182   nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream, &rv);
1183   NS_ENSURE_SUCCESS(rv, rv);
1184 
1185   rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1186   NS_ENSURE_SUCCESS(rv, rv);
1187 
1188   mozilla::MutexAutoLock lock(mLock);
1189 
1190   if (!mHasData) {
1191     // So far output stream on this new entry not opened, do it now.
1192     LOG(("  creating phantom output stream"));
1193     rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
1194     NS_ENSURE_SUCCESS(rv, rv);
1195   }
1196 
1197   stream.forget(_retval);
1198   return NS_OK;
1199 }
1200 
OpenOutputStream(int64_t offset,int64_t predictedSize,nsIOutputStream ** _retval)1201 nsresult CacheEntry::OpenOutputStream(int64_t offset, int64_t predictedSize,
1202                                       nsIOutputStream** _retval) {
1203   LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
1204 
1205   nsresult rv;
1206 
1207   mozilla::MutexAutoLock lock(mLock);
1208 
1209   MOZ_ASSERT(mState > EMPTY);
1210 
1211   if (mFile->EntryWouldExceedLimit(0, predictedSize, false)) {
1212     LOG(("  entry would exceed size limit"));
1213     return NS_ERROR_FILE_TOO_BIG;
1214   }
1215 
1216   if (mOutputStream && !mIsDoomed) {
1217     LOG(("  giving phantom output stream"));
1218     mOutputStream.forget(_retval);
1219   } else {
1220     rv = OpenOutputStreamInternal(offset, _retval);
1221     if (NS_FAILED(rv)) return rv;
1222   }
1223 
1224   // Entry considered ready when writer opens output stream.
1225   if (mState < READY) mState = READY;
1226 
1227   // Invoke any pending readers now.
1228   InvokeCallbacks();
1229 
1230   return NS_OK;
1231 }
1232 
OpenAlternativeOutputStream(const nsACString & type,int64_t predictedSize,nsIAsyncOutputStream ** _retval)1233 nsresult CacheEntry::OpenAlternativeOutputStream(
1234     const nsACString& type, int64_t predictedSize,
1235     nsIAsyncOutputStream** _retval) {
1236   LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
1237        PromiseFlatCString(type).get()));
1238 
1239   nsresult rv;
1240 
1241   if (type.IsEmpty()) {
1242     // The empty string is reserved to mean no alt-data available.
1243     return NS_ERROR_INVALID_ARG;
1244   }
1245 
1246   mozilla::MutexAutoLock lock(mLock);
1247 
1248   if (!mHasData || mState < READY || mOutputStream || mIsDoomed) {
1249     LOG(("  entry not in state to write alt-data"));
1250     return NS_ERROR_NOT_AVAILABLE;
1251   }
1252 
1253   if (mFile->EntryWouldExceedLimit(0, predictedSize, true)) {
1254     LOG(("  entry would exceed size limit"));
1255     return NS_ERROR_FILE_TOO_BIG;
1256   }
1257 
1258   nsCOMPtr<nsIAsyncOutputStream> stream;
1259   rv = mFile->OpenAlternativeOutputStream(
1260       nullptr, PromiseFlatCString(type).get(), getter_AddRefs(stream));
1261   NS_ENSURE_SUCCESS(rv, rv);
1262 
1263   stream.swap(*_retval);
1264   return NS_OK;
1265 }
1266 
OpenOutputStreamInternal(int64_t offset,nsIOutputStream ** _retval)1267 nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset,
1268                                               nsIOutputStream** _retval) {
1269   LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
1270 
1271   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1272 
1273   mLock.AssertCurrentThreadOwns();
1274 
1275   if (mIsDoomed) {
1276     LOG(("  doomed..."));
1277     return NS_ERROR_NOT_AVAILABLE;
1278   }
1279 
1280   MOZ_ASSERT(mState > LOADING);
1281 
1282   nsresult rv;
1283 
1284   // No need to sync on mUseDisk here, we don't need to be consistent
1285   // with content of the memory storage entries hash table.
1286   if (!mUseDisk) {
1287     rv = mFile->SetMemoryOnly();
1288     NS_ENSURE_SUCCESS(rv, rv);
1289   }
1290 
1291   RefPtr<CacheOutputCloseListener> listener =
1292       new CacheOutputCloseListener(this);
1293 
1294   nsCOMPtr<nsIOutputStream> stream;
1295   rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
1296   NS_ENSURE_SUCCESS(rv, rv);
1297 
1298   nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream, &rv);
1299   NS_ENSURE_SUCCESS(rv, rv);
1300 
1301   rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1302   NS_ENSURE_SUCCESS(rv, rv);
1303 
1304   // Prevent opening output stream again.
1305   mHasData = true;
1306 
1307   stream.swap(*_retval);
1308   return NS_OK;
1309 }
1310 
GetSecurityInfo(nsISupports ** aSecurityInfo)1311 nsresult CacheEntry::GetSecurityInfo(nsISupports** aSecurityInfo) {
1312   {
1313     mozilla::MutexAutoLock lock(mLock);
1314     if (mSecurityInfoLoaded) {
1315       NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1316       return NS_OK;
1317     }
1318   }
1319 
1320   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1321 
1322   nsCString info;
1323   nsCOMPtr<nsISupports> secInfo;
1324   nsresult rv;
1325 
1326   rv = mFile->GetElement("security-info", getter_Copies(info));
1327   NS_ENSURE_SUCCESS(rv, rv);
1328 
1329   if (!info.IsVoid()) {
1330     rv = NS_DeserializeObject(info, getter_AddRefs(secInfo));
1331     NS_ENSURE_SUCCESS(rv, rv);
1332   }
1333 
1334   {
1335     mozilla::MutexAutoLock lock(mLock);
1336 
1337     mSecurityInfo.swap(secInfo);
1338     mSecurityInfoLoaded = true;
1339 
1340     NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1341   }
1342 
1343   return NS_OK;
1344 }
SetSecurityInfo(nsISupports * aSecurityInfo)1345 nsresult CacheEntry::SetSecurityInfo(nsISupports* aSecurityInfo) {
1346   nsresult rv;
1347 
1348   NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1349 
1350   {
1351     mozilla::MutexAutoLock lock(mLock);
1352 
1353     mSecurityInfo = aSecurityInfo;
1354     mSecurityInfoLoaded = true;
1355   }
1356 
1357   nsCOMPtr<nsISerializable> serializable = do_QueryInterface(aSecurityInfo);
1358   if (aSecurityInfo && !serializable) return NS_ERROR_UNEXPECTED;
1359 
1360   nsCString info;
1361   if (serializable) {
1362     rv = NS_SerializeToString(serializable, info);
1363     NS_ENSURE_SUCCESS(rv, rv);
1364   }
1365 
1366   rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
1367   NS_ENSURE_SUCCESS(rv, rv);
1368 
1369   return NS_OK;
1370 }
1371 
GetStorageDataSize(uint32_t * aStorageDataSize)1372 nsresult CacheEntry::GetStorageDataSize(uint32_t* aStorageDataSize) {
1373   NS_ENSURE_ARG(aStorageDataSize);
1374 
1375   int64_t dataSize;
1376   nsresult rv = GetDataSize(&dataSize);
1377   if (NS_FAILED(rv)) return rv;
1378 
1379   *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
1380 
1381   return NS_OK;
1382 }
1383 
AsyncDoom(nsICacheEntryDoomCallback * aCallback)1384 nsresult CacheEntry::AsyncDoom(nsICacheEntryDoomCallback* aCallback) {
1385   LOG(("CacheEntry::AsyncDoom [this=%p]", this));
1386 
1387   {
1388     mozilla::MutexAutoLock lock(mLock);
1389 
1390     if (mIsDoomed || mDoomCallback) {
1391       return NS_ERROR_IN_PROGRESS;  // to aggregate have DOOMING state
1392     }
1393 
1394     RemoveForcedValidity();
1395 
1396     mIsDoomed = true;
1397     mDoomCallback = aCallback;
1398   }
1399 
1400   // This immediately removes the entry from the master hashtable and also
1401   // immediately dooms the file.  This way we make sure that any consumer
1402   // after this point asking for the same entry won't get
1403   //   a) this entry
1404   //   b) a new entry with the same file
1405   PurgeAndDoom();
1406 
1407   return NS_OK;
1408 }
1409 
GetMetaDataElement(const char * aKey,char ** aRetval)1410 nsresult CacheEntry::GetMetaDataElement(const char* aKey, char** aRetval) {
1411   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1412 
1413   return mFile->GetElement(aKey, aRetval);
1414 }
1415 
SetMetaDataElement(const char * aKey,const char * aValue)1416 nsresult CacheEntry::SetMetaDataElement(const char* aKey, const char* aValue) {
1417   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1418 
1419   return mFile->SetElement(aKey, aValue);
1420 }
1421 
VisitMetaData(nsICacheEntryMetaDataVisitor * aVisitor)1422 nsresult CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor* aVisitor) {
1423   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1424 
1425   return mFile->VisitMetaData(aVisitor);
1426 }
1427 
MetaDataReady()1428 nsresult CacheEntry::MetaDataReady() {
1429   mozilla::MutexAutoLock lock(mLock);
1430 
1431   LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this,
1432        StateString(mState)));
1433 
1434   MOZ_ASSERT(mState > EMPTY);
1435 
1436   if (mState == WRITING) mState = READY;
1437 
1438   InvokeCallbacks();
1439 
1440   return NS_OK;
1441 }
1442 
SetValid()1443 nsresult CacheEntry::SetValid() {
1444   LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState)));
1445 
1446   nsCOMPtr<nsIOutputStream> outputStream;
1447 
1448   {
1449     mozilla::MutexAutoLock lock(mLock);
1450 
1451     MOZ_ASSERT(mState > EMPTY);
1452 
1453     mState = READY;
1454     mHasData = true;
1455 
1456     InvokeCallbacks();
1457 
1458     outputStream.swap(mOutputStream);
1459   }
1460 
1461   if (outputStream) {
1462     LOG(("  abandoning phantom output stream"));
1463     outputStream->Close();
1464   }
1465 
1466   return NS_OK;
1467 }
1468 
Recreate(bool aMemoryOnly,nsICacheEntry ** _retval)1469 nsresult CacheEntry::Recreate(bool aMemoryOnly, nsICacheEntry** _retval) {
1470   LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
1471 
1472   mozilla::MutexAutoLock lock(mLock);
1473 
1474   RefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
1475   if (handle) {
1476     handle.forget(_retval);
1477     return NS_OK;
1478   }
1479 
1480   BackgroundOp(Ops::CALLBACKS, true);
1481   return NS_ERROR_NOT_AVAILABLE;
1482 }
1483 
GetDataSize(int64_t * aDataSize)1484 nsresult CacheEntry::GetDataSize(int64_t* aDataSize) {
1485   LOG(("CacheEntry::GetDataSize [this=%p]", this));
1486   *aDataSize = 0;
1487 
1488   {
1489     mozilla::MutexAutoLock lock(mLock);
1490 
1491     if (!mHasData) {
1492       LOG(("  write in progress (no data)"));
1493       return NS_ERROR_IN_PROGRESS;
1494     }
1495   }
1496 
1497   NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1498 
1499   // mayhemer: TODO Problem with compression?
1500   if (!mFile->DataSize(aDataSize)) {
1501     LOG(("  write in progress (stream active)"));
1502     return NS_ERROR_IN_PROGRESS;
1503   }
1504 
1505   LOG(("  size=%" PRId64, *aDataSize));
1506   return NS_OK;
1507 }
1508 
GetAltDataSize(int64_t * aDataSize)1509 nsresult CacheEntry::GetAltDataSize(int64_t* aDataSize) {
1510   LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
1511   if (NS_FAILED(mFileStatus)) {
1512     return mFileStatus;
1513   }
1514   return mFile->GetAltDataSize(aDataSize);
1515 }
1516 
GetAltDataType(nsACString & aType)1517 nsresult CacheEntry::GetAltDataType(nsACString& aType) {
1518   LOG(("CacheEntry::GetAltDataType [this=%p]", this));
1519   if (NS_FAILED(mFileStatus)) {
1520     return mFileStatus;
1521   }
1522   return mFile->GetAltDataType(aType);
1523 }
1524 
MarkValid()1525 nsresult CacheEntry::MarkValid() {
1526   // NOT IMPLEMENTED ACTUALLY
1527   return NS_OK;
1528 }
1529 
MaybeMarkValid()1530 nsresult CacheEntry::MaybeMarkValid() {
1531   // NOT IMPLEMENTED ACTUALLY
1532   return NS_OK;
1533 }
1534 
HasWriteAccess(bool aWriteAllowed,bool * aWriteAccess)1535 nsresult CacheEntry::HasWriteAccess(bool aWriteAllowed, bool* aWriteAccess) {
1536   *aWriteAccess = aWriteAllowed;
1537   return NS_OK;
1538 }
1539 
Close()1540 nsresult CacheEntry::Close() {
1541   // NOT IMPLEMENTED ACTUALLY
1542   return NS_OK;
1543 }
1544 
GetDiskStorageSizeInKB(uint32_t * aDiskStorageSize)1545 nsresult CacheEntry::GetDiskStorageSizeInKB(uint32_t* aDiskStorageSize) {
1546   if (NS_FAILED(mFileStatus)) {
1547     return NS_ERROR_NOT_AVAILABLE;
1548   }
1549 
1550   return mFile->GetDiskStorageSizeInKB(aDiskStorageSize);
1551 }
1552 
GetLoadContextInfo(nsILoadContextInfo ** aInfo)1553 nsresult CacheEntry::GetLoadContextInfo(nsILoadContextInfo** aInfo) {
1554   nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(mStorageID);
1555   if (!info) {
1556     return NS_ERROR_FAILURE;
1557   }
1558 
1559   info.forget(aInfo);
1560 
1561   return NS_OK;
1562 }
1563 
1564 // nsIRunnable
1565 
Run()1566 NS_IMETHODIMP CacheEntry::Run() {
1567   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1568 
1569   mozilla::MutexAutoLock lock(mLock);
1570 
1571   BackgroundOp(mBackgroundOperations.Grab());
1572   return NS_OK;
1573 }
1574 
1575 // Management methods
1576 
GetFrecency() const1577 double CacheEntry::GetFrecency() const {
1578   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1579   return mFrecency;
1580 }
1581 
GetExpirationTime() const1582 uint32_t CacheEntry::GetExpirationTime() const {
1583   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1584   return mSortingExpirationTime;
1585 }
1586 
IsRegistered() const1587 bool CacheEntry::IsRegistered() const {
1588   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1589   return mRegistration == REGISTERED;
1590 }
1591 
CanRegister() const1592 bool CacheEntry::CanRegister() const {
1593   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1594   return mRegistration == NEVERREGISTERED;
1595 }
1596 
SetRegistered(bool aRegistered)1597 void CacheEntry::SetRegistered(bool aRegistered) {
1598   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1599 
1600   if (aRegistered) {
1601     MOZ_ASSERT(mRegistration == NEVERREGISTERED);
1602     mRegistration = REGISTERED;
1603   } else {
1604     MOZ_ASSERT(mRegistration == REGISTERED);
1605     mRegistration = DEREGISTERED;
1606   }
1607 }
1608 
DeferOrBypassRemovalOnPinStatus(bool aPinned)1609 bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned) {
1610   LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this));
1611 
1612   mozilla::MutexAutoLock lock(mLock);
1613 
1614   if (mPinningKnown) {
1615     LOG(("  pinned=%d, caller=%d", mPinned, aPinned));
1616     // Bypass when the pin status of this entry doesn't match the pin status
1617     // caller wants to remove
1618     return mPinned != aPinned;
1619   }
1620 
1621   LOG(("  pinning unknown, caller=%d", aPinned));
1622   // Oterwise, remember to doom after the status is determined for any
1623   // callback opening the entry after this point...
1624   Callback c(this, aPinned);
1625   RememberCallback(c);
1626   // ...and always bypass
1627   return true;
1628 }
1629 
Purge(uint32_t aWhat)1630 bool CacheEntry::Purge(uint32_t aWhat) {
1631   LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
1632 
1633   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1634 
1635   switch (aWhat) {
1636     case PURGE_DATA_ONLY_DISK_BACKED:
1637     case PURGE_WHOLE_ONLY_DISK_BACKED:
1638       // This is an in-memory only entry, don't purge it
1639       if (!mUseDisk) {
1640         LOG(("  not using disk"));
1641         return false;
1642       }
1643   }
1644 
1645   {
1646     mozilla::MutexAutoLock lock(mLock);
1647 
1648     if (mState == WRITING || mState == LOADING || mFrecency == 0) {
1649       // In-progress (write or load) entries should (at least for consistency
1650       // and from the logical point of view) stay in memory. Zero-frecency
1651       // entries are those which have never been given to any consumer, those
1652       // are actually very fresh and should not go just because frecency had not
1653       // been set so far.
1654       LOG(("  state=%s, frecency=%1.10f", StateString(mState), mFrecency));
1655       return false;
1656     }
1657   }
1658 
1659   if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) {
1660     // The file is used when there are open streams or chunks/metadata still
1661     // waiting for write.  In this case, this entry cannot be purged,
1662     // otherwise reopenned entry would may not even find the data on disk -
1663     // CacheFile is not shared and cannot be left orphan when its job is not
1664     // done, hence keep the whole entry.
1665     LOG(("  file still under use"));
1666     return false;
1667   }
1668 
1669   switch (aWhat) {
1670     case PURGE_WHOLE_ONLY_DISK_BACKED:
1671     case PURGE_WHOLE: {
1672       if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1673         LOG(("  not purging, still referenced"));
1674         return false;
1675       }
1676 
1677       CacheStorageService::Self()->UnregisterEntry(this);
1678 
1679       // Entry removed it self from control arrays, return true
1680       return true;
1681     }
1682 
1683     case PURGE_DATA_ONLY_DISK_BACKED: {
1684       NS_ENSURE_SUCCESS(mFileStatus, false);
1685 
1686       mFile->ThrowMemoryCachedData();
1687 
1688       // Entry has been left in control arrays, return false (not purged)
1689       return false;
1690     }
1691   }
1692 
1693   LOG(("  ?"));
1694   return false;
1695 }
1696 
PurgeAndDoom()1697 void CacheEntry::PurgeAndDoom() {
1698   LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
1699 
1700   CacheStorageService::Self()->RemoveEntry(this);
1701   DoomAlreadyRemoved();
1702 }
1703 
DoomAlreadyRemoved()1704 void CacheEntry::DoomAlreadyRemoved() {
1705   LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
1706 
1707   mozilla::MutexAutoLock lock(mLock);
1708 
1709   RemoveForcedValidity();
1710 
1711   mIsDoomed = true;
1712 
1713   // Pretend pinning is know.  This entry is now doomed for good, so don't
1714   // bother with defering doom because of unknown pinning state any more.
1715   mPinningKnown = true;
1716 
1717   // This schedules dooming of the file, dooming is ensured to happen
1718   // sooner than demand to open the same file made after this point
1719   // so that we don't get this file for any newer opened entry(s).
1720   DoomFile();
1721 
1722   // Must force post here since may be indirectly called from
1723   // InvokeCallbacks of this entry and we don't want reentrancy here.
1724   BackgroundOp(Ops::CALLBACKS, true);
1725   // Process immediately when on the management thread.
1726   BackgroundOp(Ops::UNREGISTER);
1727 }
1728 
DoomFile()1729 void CacheEntry::DoomFile() {
1730   nsresult rv = NS_ERROR_NOT_AVAILABLE;
1731 
1732   if (NS_SUCCEEDED(mFileStatus)) {
1733     if (mHandlesCount == 0 || (mHandlesCount == 1 && mWriter)) {
1734       // We kill the file also when there is just reference from the writer,
1735       // no one else could ever reach the written data.  Obvisouly also
1736       // when there is no reference at all (should we ever end up here
1737       // in that case.)
1738       // Tell the file to kill the handle, i.e. bypass any I/O operations
1739       // on it except removing the file.
1740       mFile->Kill();
1741     }
1742 
1743     // Always calls the callback asynchronously.
1744     rv = mFile->Doom(mDoomCallback ? this : nullptr);
1745     if (NS_SUCCEEDED(rv)) {
1746       LOG(("  file doomed"));
1747       return;
1748     }
1749 
1750     if (NS_ERROR_FILE_NOT_FOUND == rv) {
1751       // File is set to be just memory-only, notify the callbacks
1752       // and pretend dooming has succeeded.  From point of view of
1753       // the entry it actually did - the data is gone and cannot be
1754       // reused.
1755       rv = NS_OK;
1756     }
1757   }
1758 
1759   // Always posts to the main thread.
1760   OnFileDoomed(rv);
1761 }
1762 
RemoveForcedValidity()1763 void CacheEntry::RemoveForcedValidity() {
1764   mLock.AssertCurrentThreadOwns();
1765 
1766   nsresult rv;
1767 
1768   if (mIsDoomed) {
1769     return;
1770   }
1771 
1772   nsAutoCString entryKey;
1773   rv = HashingKey(entryKey);
1774   if (NS_WARN_IF(NS_FAILED(rv))) {
1775     return;
1776   }
1777 
1778   CacheStorageService::Self()->RemoveEntryForceValid(mStorageID, entryKey);
1779 }
1780 
BackgroundOp(uint32_t aOperations,bool aForceAsync)1781 void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync) {
1782   mLock.AssertCurrentThreadOwns();
1783 
1784   if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
1785     if (mBackgroundOperations.Set(aOperations)) {
1786       CacheStorageService::Self()->Dispatch(this);
1787     }
1788 
1789     LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
1790     return;
1791   }
1792 
1793   {
1794     mozilla::MutexAutoUnlock unlock(mLock);
1795 
1796     MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1797 
1798     if (aOperations & Ops::FRECENCYUPDATE) {
1799       ++mUseCount;
1800 
1801 #ifndef M_LN2
1802 #  define M_LN2 0.69314718055994530942
1803 #endif
1804 
1805       // Half-life is dynamic, in seconds.
1806       static double half_life = CacheObserver::HalfLifeSeconds();
1807       // Must convert from seconds to milliseconds since PR_Now() gives usecs.
1808       static double const decay =
1809           (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
1810 
1811       double now_decay = static_cast<double>(PR_Now()) * decay;
1812 
1813       if (mFrecency == 0) {
1814         mFrecency = now_decay;
1815       } else {
1816         // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n +
1817         // 1) but more precise.
1818         mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
1819       }
1820       LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this,
1821            mFrecency));
1822 
1823       // Because CacheFile::Set*() are not thread-safe to use (uses
1824       // WeakReference that is not thread-safe) we must post to the main
1825       // thread...
1826       NS_DispatchToMainThread(
1827           NewRunnableMethod<double>("net::CacheEntry::StoreFrecency", this,
1828                                     &CacheEntry::StoreFrecency, mFrecency));
1829     }
1830 
1831     if (aOperations & Ops::REGISTER) {
1832       LOG(("CacheEntry REGISTER [this=%p]", this));
1833 
1834       CacheStorageService::Self()->RegisterEntry(this);
1835     }
1836 
1837     if (aOperations & Ops::UNREGISTER) {
1838       LOG(("CacheEntry UNREGISTER [this=%p]", this));
1839 
1840       CacheStorageService::Self()->UnregisterEntry(this);
1841     }
1842   }  // unlock
1843 
1844   if (aOperations & Ops::CALLBACKS) {
1845     LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
1846 
1847     InvokeCallbacks();
1848   }
1849 }
1850 
StoreFrecency(double aFrecency)1851 void CacheEntry::StoreFrecency(double aFrecency) {
1852   MOZ_ASSERT(NS_IsMainThread());
1853 
1854   if (NS_SUCCEEDED(mFileStatus)) {
1855     mFile->SetFrecency(FRECENCY2INT(aFrecency));
1856   }
1857 }
1858 
1859 // CacheOutputCloseListener
1860 
CacheOutputCloseListener(CacheEntry * aEntry)1861 CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
1862     : Runnable("net::CacheOutputCloseListener"), mEntry(aEntry) {}
1863 
OnOutputClosed()1864 void CacheOutputCloseListener::OnOutputClosed() {
1865   // We need this class and to redispatch since this callback is invoked
1866   // under the file's lock and to do the job we need to enter the entry's
1867   // lock too.  That would lead to potential deadlocks.
1868   NS_DispatchToCurrentThread(this);
1869 }
1870 
Run()1871 NS_IMETHODIMP CacheOutputCloseListener::Run() {
1872   mEntry->OnOutputClosed();
1873   return NS_OK;
1874 }
1875 
1876 // Memory reporting
1877 
SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const1878 size_t CacheEntry::SizeOfExcludingThis(
1879     mozilla::MallocSizeOf mallocSizeOf) const {
1880   size_t n = 0;
1881 
1882   n += mCallbacks.ShallowSizeOfExcludingThis(mallocSizeOf);
1883   if (mFile) {
1884     n += mFile->SizeOfIncludingThis(mallocSizeOf);
1885   }
1886 
1887   n += mURI.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1888   n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1889   n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1890 
1891   // mDoomCallback is an arbitrary class that is probably reported elsewhere.
1892   // mOutputStream is reported in mFile.
1893   // mWriter is one of many handles we create, but (intentionally) not keep
1894   // any reference to, so those unfortunately cannot be reported.  Handles are
1895   // small, though.
1896   // mSecurityInfo doesn't impl nsISizeOf.
1897 
1898   return n;
1899 }
1900 
SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const1901 size_t CacheEntry::SizeOfIncludingThis(
1902     mozilla::MallocSizeOf mallocSizeOf) const {
1903   return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1904 }
1905 
1906 }  // namespace mozilla::net
1907