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