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