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