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