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