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