Source code

Revision control

Other Tools

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