Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "CacheLog.h"
8
#include "CacheStorageService.h"
9
#include "CacheFileIOManager.h"
10
#include "CacheObserver.h"
11
#include "CacheIndex.h"
12
#include "CacheIndexIterator.h"
13
#include "CacheStorage.h"
14
#include "AppCacheStorage.h"
15
#include "CacheEntry.h"
16
#include "CacheFileUtils.h"
17
18
#include "OldWrappers.h"
19
#include "nsCacheService.h"
20
#include "nsDeleteDir.h"
21
22
#include "nsICacheStorageVisitor.h"
23
#include "nsIObserverService.h"
24
#include "nsIFile.h"
25
#include "nsIURI.h"
26
#include "nsCOMPtr.h"
27
#include "nsContentUtils.h"
28
#include "nsAutoPtr.h"
29
#include "nsNetCID.h"
30
#include "nsNetUtil.h"
31
#include "nsServiceManagerUtils.h"
32
#include "nsXULAppAPI.h"
33
#include "mozilla/TimeStamp.h"
34
#include "mozilla/DebugOnly.h"
35
#include "mozilla/Services.h"
36
#include "mozilla/IntegerPrintfMacros.h"
37
38
namespace mozilla {
39
namespace net {
40
41
namespace {
42
43
void AppendMemoryStorageTag(nsAutoCString& key) {
44
// Using DEL as the very last ascii-7 character we can use in the list of
45
// attributes
46
key.Append('\x7f');
47
key.Append(',');
48
}
49
50
} // namespace
51
52
// Not defining as static or class member of CacheStorageService since
53
// it would otherwise need to include CacheEntry.h and that then would
54
// need to be exported to make nsNetModule.cpp compilable.
55
typedef nsClassHashtable<nsCStringHashKey, CacheEntryTable> GlobalEntryTables;
56
57
/**
58
* Keeps tables of entries. There is one entries table for each distinct load
59
* context type. The distinction is based on following load context info
60
* states: <isPrivate|isAnon|inIsolatedMozBrowser> which builds a mapping
61
* key.
62
*
63
* Thread-safe to access, protected by the service mutex.
64
*/
65
static GlobalEntryTables* sGlobalEntryTables;
66
67
CacheMemoryConsumer::CacheMemoryConsumer(uint32_t aFlags)
68
: mReportedMemoryConsumption(0), mFlags(aFlags) {}
69
70
void CacheMemoryConsumer::DoMemoryReport(uint32_t aCurrentSize) {
71
if (!(mFlags & DONT_REPORT) && CacheStorageService::Self()) {
72
CacheStorageService::Self()->OnMemoryConsumptionChange(this, aCurrentSize);
73
}
74
}
75
76
CacheStorageService::MemoryPool::MemoryPool(EType aType)
77
: mType(aType), mMemorySize(0) {}
78
79
CacheStorageService::MemoryPool::~MemoryPool() {
80
if (mMemorySize != 0) {
81
NS_ERROR(
82
"Network cache reported memory consumption is not at 0, probably "
83
"leaking?");
84
}
85
}
86
87
uint32_t CacheStorageService::MemoryPool::Limit() const {
88
uint32_t limit = 0;
89
90
switch (mType) {
91
case DISK:
92
limit = CacheObserver::MetadataMemoryLimit();
93
break;
94
case MEMORY:
95
limit = CacheObserver::MemoryCacheCapacity();
96
break;
97
default:
98
MOZ_CRASH("Bad pool type");
99
}
100
101
static const uint32_t kMaxLimit = 0x3FFFFF;
102
if (limit > kMaxLimit) {
103
LOG((" a memory limit (%u) is unexpectedly high, clipping to %u", limit,
104
kMaxLimit));
105
limit = kMaxLimit;
106
}
107
108
return limit << 10;
109
}
110
111
NS_IMPL_ISUPPORTS(CacheStorageService, nsICacheStorageService,
112
nsIMemoryReporter, nsITimerCallback, nsICacheTesting,
113
nsINamed)
114
115
CacheStorageService* CacheStorageService::sSelf = nullptr;
116
117
CacheStorageService::CacheStorageService()
118
: mLock("CacheStorageService.mLock"),
119
mForcedValidEntriesLock("CacheStorageService.mForcedValidEntriesLock"),
120
mShutdown(false),
121
mDiskPool(MemoryPool::DISK),
122
mMemoryPool(MemoryPool::MEMORY) {
123
CacheFileIOManager::Init();
124
125
MOZ_ASSERT(XRE_IsParentProcess());
126
MOZ_ASSERT(!sSelf);
127
128
sSelf = this;
129
sGlobalEntryTables = new GlobalEntryTables();
130
131
RegisterStrongMemoryReporter(this);
132
}
133
134
CacheStorageService::~CacheStorageService() {
135
LOG(("CacheStorageService::~CacheStorageService"));
136
sSelf = nullptr;
137
}
138
139
void CacheStorageService::Shutdown() {
140
mozilla::MutexAutoLock lock(mLock);
141
142
if (mShutdown) return;
143
144
LOG(("CacheStorageService::Shutdown - start"));
145
146
mShutdown = true;
147
148
nsCOMPtr<nsIRunnable> event =
149
NewRunnableMethod("net::CacheStorageService::ShutdownBackground", this,
150
&CacheStorageService::ShutdownBackground);
151
Dispatch(event);
152
153
#ifdef NS_FREE_PERMANENT_DATA
154
sGlobalEntryTables->Clear();
155
delete sGlobalEntryTables;
156
#endif
157
sGlobalEntryTables = nullptr;
158
159
LOG(("CacheStorageService::Shutdown - done"));
160
}
161
162
void CacheStorageService::ShutdownBackground() {
163
LOG(("CacheStorageService::ShutdownBackground - start"));
164
165
MOZ_ASSERT(IsOnManagementThread());
166
167
{
168
mozilla::MutexAutoLock lock(mLock);
169
170
// Cancel purge timer to avoid leaking.
171
if (mPurgeTimer) {
172
LOG((" freeing the timer"));
173
mPurgeTimer->Cancel();
174
}
175
}
176
177
#ifdef NS_FREE_PERMANENT_DATA
178
Pool(false).mFrecencyArray.Clear();
179
Pool(false).mExpirationArray.Clear();
180
Pool(true).mFrecencyArray.Clear();
181
Pool(true).mExpirationArray.Clear();
182
#endif
183
184
LOG(("CacheStorageService::ShutdownBackground - done"));
185
}
186
187
// Internal management methods
188
189
namespace {
190
191
// WalkCacheRunnable
192
// Base class for particular storage entries visiting
193
class WalkCacheRunnable : public Runnable,
194
public CacheStorageService::EntryInfoCallback {
195
protected:
196
WalkCacheRunnable(nsICacheStorageVisitor* aVisitor, bool aVisitEntries)
197
: Runnable("net::WalkCacheRunnable"),
198
mService(CacheStorageService::Self()),
199
mCallback(aVisitor),
200
mSize(0),
201
mNotifyStorage(true),
202
mVisitEntries(aVisitEntries),
203
mCancel(false) {
204
MOZ_ASSERT(NS_IsMainThread());
205
}
206
207
virtual ~WalkCacheRunnable() {
208
if (mCallback) {
209
ProxyReleaseMainThread("WalkCacheRunnable::mCallback", mCallback);
210
}
211
}
212
213
RefPtr<CacheStorageService> mService;
214
nsCOMPtr<nsICacheStorageVisitor> mCallback;
215
216
uint64_t mSize;
217
218
bool mNotifyStorage : 1;
219
bool mVisitEntries : 1;
220
221
Atomic<bool> mCancel;
222
};
223
224
// WalkMemoryCacheRunnable
225
// Responsible to visit memory storage and walk
226
// all entries on it asynchronously.
227
class WalkMemoryCacheRunnable : public WalkCacheRunnable {
228
public:
229
WalkMemoryCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries,
230
nsICacheStorageVisitor* aVisitor)
231
: WalkCacheRunnable(aVisitor, aVisitEntries) {
232
CacheFileUtils::AppendKeyPrefix(aLoadInfo, mContextKey);
233
MOZ_ASSERT(NS_IsMainThread());
234
}
235
236
nsresult Walk() { return mService->Dispatch(this); }
237
238
private:
239
NS_IMETHOD Run() override {
240
if (CacheStorageService::IsOnManagementThread()) {
241
LOG(("WalkMemoryCacheRunnable::Run - collecting [this=%p]", this));
242
// First, walk, count and grab all entries from the storage
243
244
mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
245
246
if (!CacheStorageService::IsRunning()) return NS_ERROR_NOT_INITIALIZED;
247
248
CacheEntryTable* entries;
249
if (sGlobalEntryTables->Get(mContextKey, &entries)) {
250
for (auto iter = entries->Iter(); !iter.Done(); iter.Next()) {
251
CacheEntry* entry = iter.UserData();
252
253
// Ignore disk entries
254
if (entry->IsUsingDisk()) {
255
continue;
256
}
257
258
mSize += entry->GetMetadataMemoryConsumption();
259
260
int64_t size;
261
if (NS_SUCCEEDED(entry->GetDataSize(&size))) {
262
mSize += size;
263
}
264
mEntryArray.AppendElement(entry);
265
}
266
}
267
268
// Next, we dispatch to the main thread
269
} else if (NS_IsMainThread()) {
270
LOG(("WalkMemoryCacheRunnable::Run - notifying [this=%p]", this));
271
272
if (mNotifyStorage) {
273
LOG((" storage"));
274
275
uint64_t capacity = CacheObserver::MemoryCacheCapacity();
276
capacity <<= 10; // kilobytes to bytes
277
278
// Second, notify overall storage info
279
mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize, capacity,
280
nullptr);
281
if (!mVisitEntries) return NS_OK; // done
282
283
mNotifyStorage = false;
284
285
} else {
286
LOG((" entry [left=%zu, canceled=%d]", mEntryArray.Length(),
287
(bool)mCancel));
288
289
// Third, notify each entry until depleted or canceled
290
if (!mEntryArray.Length() || mCancel) {
291
mCallback->OnCacheEntryVisitCompleted();
292
return NS_OK; // done
293
}
294
295
// Grab the next entry
296
RefPtr<CacheEntry> entry = mEntryArray[0];
297
mEntryArray.RemoveElementAt(0);
298
299
// Invokes this->OnEntryInfo, that calls the callback with all
300
// information of the entry.
301
CacheStorageService::GetCacheEntryInfo(entry, this);
302
}
303
} else {
304
MOZ_CRASH("Bad thread");
305
return NS_ERROR_FAILURE;
306
}
307
308
NS_DispatchToMainThread(this);
309
return NS_OK;
310
}
311
312
virtual ~WalkMemoryCacheRunnable() {
313
if (mCallback)
314
ProxyReleaseMainThread("WalkMemoryCacheRunnable::mCallback", mCallback);
315
}
316
317
virtual void OnEntryInfo(const nsACString& aURISpec,
318
const nsACString& aIdEnhance, int64_t aDataSize,
319
int32_t aFetchCount, uint32_t aLastModifiedTime,
320
uint32_t aExpirationTime, bool aPinned,
321
nsILoadContextInfo* aInfo) override {
322
nsresult rv;
323
324
nsCOMPtr<nsIURI> uri;
325
rv = NS_NewURI(getter_AddRefs(uri), aURISpec);
326
if (NS_FAILED(rv)) {
327
return;
328
}
329
330
rv = mCallback->OnCacheEntryInfo(uri, aIdEnhance, aDataSize, aFetchCount,
331
aLastModifiedTime, aExpirationTime,
332
aPinned, aInfo);
333
if (NS_FAILED(rv)) {
334
LOG((" callback failed, canceling the walk"));
335
mCancel = true;
336
}
337
}
338
339
private:
340
nsCString mContextKey;
341
nsTArray<RefPtr<CacheEntry>> mEntryArray;
342
};
343
344
// WalkDiskCacheRunnable
345
// Using the cache index information to get the list of files per context.
346
class WalkDiskCacheRunnable : public WalkCacheRunnable {
347
public:
348
WalkDiskCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries,
349
nsICacheStorageVisitor* aVisitor)
350
: WalkCacheRunnable(aVisitor, aVisitEntries),
351
mLoadInfo(aLoadInfo),
352
mPass(COLLECT_STATS),
353
mCount(0) {}
354
355
nsresult Walk() {
356
// TODO, bug 998693
357
// Initial index build should be forced here so that about:cache soon
358
// after startup gives some meaningfull results.
359
360
// Dispatch to the INDEX level in hope that very recent cache entries
361
// information gets to the index list before we grab the index iterator
362
// for the first time. This tries to avoid miss of entries that has
363
// been created right before the visit is required.
364
RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
365
NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED);
366
367
return thread->Dispatch(this, CacheIOThread::INDEX);
368
}
369
370
private:
371
// Invokes OnCacheEntryInfo callback for each single found entry.
372
// There is one instance of this class per one entry.
373
class OnCacheEntryInfoRunnable : public Runnable {
374
public:
375
explicit OnCacheEntryInfoRunnable(WalkDiskCacheRunnable* aWalker)
376
: Runnable("net::WalkDiskCacheRunnable::OnCacheEntryInfoRunnable"),
377
mWalker(aWalker),
378
mDataSize(0),
379
mFetchCount(0),
380
mLastModifiedTime(0),
381
mExpirationTime(0),
382
mPinned(false) {}
383
384
NS_IMETHOD Run() override {
385
MOZ_ASSERT(NS_IsMainThread());
386
387
nsresult rv;
388
389
nsCOMPtr<nsIURI> uri;
390
rv = NS_NewURI(getter_AddRefs(uri), mURISpec);
391
if (NS_FAILED(rv)) {
392
return NS_OK;
393
}
394
395
rv = mWalker->mCallback->OnCacheEntryInfo(
396
uri, mIdEnhance, mDataSize, mFetchCount, mLastModifiedTime,
397
mExpirationTime, mPinned, mInfo);
398
if (NS_FAILED(rv)) {
399
mWalker->mCancel = true;
400
}
401
402
return NS_OK;
403
}
404
405
RefPtr<WalkDiskCacheRunnable> mWalker;
406
407
nsCString mURISpec;
408
nsCString mIdEnhance;
409
int64_t mDataSize;
410
int32_t mFetchCount;
411
uint32_t mLastModifiedTime;
412
uint32_t mExpirationTime;
413
bool mPinned;
414
nsCOMPtr<nsILoadContextInfo> mInfo;
415
};
416
417
NS_IMETHOD Run() override {
418
// The main loop
419
nsresult rv;
420
421
if (CacheStorageService::IsOnManagementThread()) {
422
switch (mPass) {
423
case COLLECT_STATS:
424
// Get quickly the cache stats.
425
uint32_t size;
426
rv = CacheIndex::GetCacheStats(mLoadInfo, &size, &mCount);
427
if (NS_FAILED(rv)) {
428
if (mVisitEntries) {
429
// both onStorageInfo and onCompleted are expected
430
NS_DispatchToMainThread(this);
431
}
432
return NS_DispatchToMainThread(this);
433
}
434
435
mSize = static_cast<uint64_t>(size) << 10;
436
437
// Invoke onCacheStorageInfo with valid information.
438
NS_DispatchToMainThread(this);
439
440
if (!mVisitEntries) {
441
return NS_OK; // done
442
}
443
444
mPass = ITERATE_METADATA;
445
MOZ_FALLTHROUGH;
446
447
case ITERATE_METADATA:
448
// Now grab the context iterator.
449
if (!mIter) {
450
rv =
451
CacheIndex::GetIterator(mLoadInfo, true, getter_AddRefs(mIter));
452
if (NS_FAILED(rv)) {
453
// Invoke onCacheEntryVisitCompleted now
454
return NS_DispatchToMainThread(this);
455
}
456
}
457
458
while (!mCancel && !CacheObserver::ShuttingDown()) {
459
if (CacheIOThread::YieldAndRerun()) return NS_OK;
460
461
SHA1Sum::Hash hash;
462
rv = mIter->GetNextHash(&hash);
463
if (NS_FAILED(rv)) break; // done (or error?)
464
465
// This synchronously invokes OnEntryInfo on this class where we
466
// redispatch to the main thread for the consumer callback.
467
CacheFileIOManager::GetEntryInfo(&hash, this);
468
}
469
470
// Invoke onCacheEntryVisitCompleted on the main thread
471
NS_DispatchToMainThread(this);
472
}
473
} else if (NS_IsMainThread()) {
474
if (mNotifyStorage) {
475
nsCOMPtr<nsIFile> dir;
476
CacheFileIOManager::GetCacheDirectory(getter_AddRefs(dir));
477
uint64_t capacity = CacheObserver::DiskCacheCapacity();
478
capacity <<= 10; // kilobytes to bytes
479
mCallback->OnCacheStorageInfo(mCount, mSize, capacity, dir);
480
mNotifyStorage = false;
481
} else {
482
mCallback->OnCacheEntryVisitCompleted();
483
}
484
} else {
485
MOZ_CRASH("Bad thread");
486
return NS_ERROR_FAILURE;
487
}
488
489
return NS_OK;
490
}
491
492
virtual void OnEntryInfo(const nsACString& aURISpec,
493
const nsACString& aIdEnhance, int64_t aDataSize,
494
int32_t aFetchCount, uint32_t aLastModifiedTime,
495
uint32_t aExpirationTime, bool aPinned,
496
nsILoadContextInfo* aInfo) override {
497
// Called directly from CacheFileIOManager::GetEntryInfo.
498
499
// Invoke onCacheEntryInfo on the main thread for this entry.
500
RefPtr<OnCacheEntryInfoRunnable> info = new OnCacheEntryInfoRunnable(this);
501
info->mURISpec = aURISpec;
502
info->mIdEnhance = aIdEnhance;
503
info->mDataSize = aDataSize;
504
info->mFetchCount = aFetchCount;
505
info->mLastModifiedTime = aLastModifiedTime;
506
info->mExpirationTime = aExpirationTime;
507
info->mPinned = aPinned;
508
info->mInfo = aInfo;
509
510
NS_DispatchToMainThread(info);
511
}
512
513
RefPtr<nsILoadContextInfo> mLoadInfo;
514
enum {
515
// First, we collect stats for the load context.
516
COLLECT_STATS,
517
518
// Second, if demanded, we iterate over the entries gethered
519
// from the iterator and call CacheFileIOManager::GetEntryInfo
520
// for each found entry.
521
ITERATE_METADATA,
522
} mPass;
523
524
RefPtr<CacheIndexIterator> mIter;
525
uint32_t mCount;
526
};
527
528
} // namespace
529
530
void CacheStorageService::DropPrivateBrowsingEntries() {
531
mozilla::MutexAutoLock lock(mLock);
532
533
if (mShutdown) return;
534
535
nsTArray<nsCString> keys;
536
for (auto iter = sGlobalEntryTables->Iter(); !iter.Done(); iter.Next()) {
537
const nsACString& key = iter.Key();
538
nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(key);
539
if (info && info->IsPrivate()) {
540
keys.AppendElement(key);
541
}
542
}
543
544
for (uint32_t i = 0; i < keys.Length(); ++i) {
545
DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
546
}
547
}
548
549
namespace {
550
551
class CleaupCacheDirectoriesRunnable : public Runnable {
552
public:
553
NS_DECL_NSIRUNNABLE
554
static bool Post();
555
556
private:
557
CleaupCacheDirectoriesRunnable()
558
: Runnable("net::CleaupCacheDirectoriesRunnable") {
559
nsCacheService::GetDiskCacheDirectory(getter_AddRefs(mCache1Dir));
560
CacheFileIOManager::GetCacheDirectory(getter_AddRefs(mCache2Dir));
561
#if defined(MOZ_WIDGET_ANDROID)
562
CacheFileIOManager::GetProfilelessCacheDirectory(
563
getter_AddRefs(mCache2Profileless));
564
#endif
565
}
566
567
virtual ~CleaupCacheDirectoriesRunnable() = default;
568
nsCOMPtr<nsIFile> mCache1Dir, mCache2Dir;
569
#if defined(MOZ_WIDGET_ANDROID)
570
nsCOMPtr<nsIFile> mCache2Profileless;
571
#endif
572
};
573
574
// static
575
bool CleaupCacheDirectoriesRunnable::Post() {
576
// To obtain the cache1 directory we must unfortunately instantiate the old
577
// cache service despite it may not be used at all... This also initializes
578
// nsDeleteDir.
579
nsCOMPtr<nsICacheService> service = do_GetService(NS_CACHESERVICE_CONTRACTID);
580
if (!service) return false;
581
582
nsCOMPtr<nsIEventTarget> thread;
583
service->GetCacheIOTarget(getter_AddRefs(thread));
584
if (!thread) return false;
585
586
RefPtr<CleaupCacheDirectoriesRunnable> r =
587
new CleaupCacheDirectoriesRunnable();
588
thread->Dispatch(r, NS_DISPATCH_NORMAL);
589
return true;
590
}
591
592
NS_IMETHODIMP CleaupCacheDirectoriesRunnable::Run() {
593
MOZ_ASSERT(!NS_IsMainThread());
594
595
if (mCache1Dir) {
596
nsDeleteDir::RemoveOldTrashes(mCache1Dir);
597
}
598
if (mCache2Dir) {
599
nsDeleteDir::RemoveOldTrashes(mCache2Dir);
600
}
601
#if defined(MOZ_WIDGET_ANDROID)
602
if (mCache2Profileless) {
603
nsDeleteDir::RemoveOldTrashes(mCache2Profileless);
604
// Always delete the profileless cache on Android
605
nsDeleteDir::DeleteDir(mCache2Profileless, true, 30000);
606
}
607
#endif
608
609
if (mCache1Dir) {
610
nsDeleteDir::DeleteDir(mCache1Dir, true, 30000);
611
}
612
613
return NS_OK;
614
}
615
616
} // namespace
617
618
// static
619
void CacheStorageService::CleaupCacheDirectories() {
620
// Make sure we schedule just once in case CleaupCacheDirectories gets called
621
// multiple times from some reason.
622
static bool runOnce = CleaupCacheDirectoriesRunnable::Post();
623
if (!runOnce) {
624
NS_WARNING("Could not start cache trashes cleanup");
625
}
626
}
627
628
// Helper methods
629
630
// static
631
bool CacheStorageService::IsOnManagementThread() {
632
RefPtr<CacheStorageService> service = Self();
633
if (!service) return false;
634
635
nsCOMPtr<nsIEventTarget> target = service->Thread();
636
if (!target) return false;
637
638
bool currentThread;
639
nsresult rv = target->IsOnCurrentThread(&currentThread);
640
return NS_SUCCEEDED(rv) && currentThread;
641
}
642
643
already_AddRefed<nsIEventTarget> CacheStorageService::Thread() const {
644
return CacheFileIOManager::IOTarget();
645
}
646
647
nsresult CacheStorageService::Dispatch(nsIRunnable* aEvent) {
648
RefPtr<CacheIOThread> cacheIOThread = CacheFileIOManager::IOThread();
649
if (!cacheIOThread) return NS_ERROR_NOT_AVAILABLE;
650
651
return cacheIOThread->Dispatch(aEvent, CacheIOThread::MANAGEMENT);
652
}
653
654
// nsICacheStorageService
655
656
NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(
657
nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
658
NS_ENSURE_ARG(aLoadContextInfo);
659
NS_ENSURE_ARG(_retval);
660
661
nsCOMPtr<nsICacheStorage> storage =
662
new CacheStorage(aLoadContextInfo, false, false, false, false);
663
storage.forget(_retval);
664
return NS_OK;
665
}
666
667
NS_IMETHODIMP CacheStorageService::DiskCacheStorage(
668
nsILoadContextInfo* aLoadContextInfo, bool aLookupAppCache,
669
nsICacheStorage** _retval) {
670
NS_ENSURE_ARG(aLoadContextInfo);
671
NS_ENSURE_ARG(_retval);
672
673
// TODO save some heap granularity - cache commonly used storages.
674
675
// When disk cache is disabled, still provide a storage, but just keep stuff
676
// in memory.
677
bool useDisk = CacheObserver::UseDiskCache();
678
679
nsCOMPtr<nsICacheStorage> storage =
680
new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache,
681
false /* size limit */, false /* don't pin */);
682
storage.forget(_retval);
683
return NS_OK;
684
}
685
686
NS_IMETHODIMP CacheStorageService::PinningCacheStorage(
687
nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
688
NS_ENSURE_ARG(aLoadContextInfo);
689
NS_ENSURE_ARG(_retval);
690
691
// When disk cache is disabled don't pretend we cache.
692
if (!CacheObserver::UseDiskCache()) {
693
return NS_ERROR_NOT_AVAILABLE;
694
}
695
696
nsCOMPtr<nsICacheStorage> storage = new CacheStorage(
697
aLoadContextInfo, true /* use disk */, false /* no appcache */,
698
true /* ignore size checks */, true /* pin */);
699
storage.forget(_retval);
700
return NS_OK;
701
}
702
703
NS_IMETHODIMP CacheStorageService::AppCacheStorage(
704
nsILoadContextInfo* aLoadContextInfo,
705
nsIApplicationCache* aApplicationCache, nsICacheStorage** _retval) {
706
NS_ENSURE_ARG(aLoadContextInfo);
707
NS_ENSURE_ARG(_retval);
708
709
nsCOMPtr<nsICacheStorage> storage;
710
// Using classification since cl believes we want to instantiate this method
711
// having the same name as the desired class...
712
storage =
713
new mozilla::net::AppCacheStorage(aLoadContextInfo, aApplicationCache);
714
715
storage.forget(_retval);
716
return NS_OK;
717
}
718
719
NS_IMETHODIMP CacheStorageService::SynthesizedCacheStorage(
720
nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
721
NS_ENSURE_ARG(aLoadContextInfo);
722
NS_ENSURE_ARG(_retval);
723
724
nsCOMPtr<nsICacheStorage> storage =
725
new CacheStorage(aLoadContextInfo, false, false,
726
true /* skip size checks for synthesized cache */,
727
false /* no pinning */);
728
storage.forget(_retval);
729
return NS_OK;
730
}
731
732
NS_IMETHODIMP CacheStorageService::Clear() {
733
nsresult rv;
734
735
// Tell the index to block notification to AsyncGetDiskConsumption.
736
// Will be allowed again from CacheFileContextEvictor::EvictEntries()
737
// when all the context have been removed from disk.
738
CacheIndex::OnAsyncEviction(true);
739
740
mozilla::MutexAutoLock lock(mLock);
741
742
{
743
mozilla::MutexAutoLock forcedValidEntriesLock(mForcedValidEntriesLock);
744
mForcedValidEntries.Clear();
745
}
746
747
NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
748
749
nsTArray<nsCString> keys;
750
for (auto iter = sGlobalEntryTables->Iter(); !iter.Done(); iter.Next()) {
751
keys.AppendElement(iter.Key());
752
}
753
754
for (uint32_t i = 0; i < keys.Length(); ++i) {
755
DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
756
}
757
758
// Passing null as a load info means to evict all contexts.
759
// EvictByContext() respects the entry pinning. EvictAll() does not.
760
rv = CacheFileIOManager::EvictByContext(nullptr, false, EmptyString());
761
NS_ENSURE_SUCCESS(rv, rv);
762
763
return NS_OK;
764
}
765
766
NS_IMETHODIMP CacheStorageService::ClearOrigin(nsIPrincipal* aPrincipal) {
767
nsresult rv;
768
769
if (NS_WARN_IF(!aPrincipal)) {
770
return NS_ERROR_FAILURE;
771
}
772
773
nsAutoString origin;
774
rv = nsContentUtils::GetUTFOrigin(aPrincipal, origin);
775
NS_ENSURE_SUCCESS(rv, rv);
776
777
rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), true);
778
NS_ENSURE_SUCCESS(rv, rv);
779
780
rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), false);
781
NS_ENSURE_SUCCESS(rv, rv);
782
783
return NS_OK;
784
}
785
786
static bool RemoveExactEntry(CacheEntryTable* aEntries, nsACString const& aKey,
787
CacheEntry* aEntry, bool aOverwrite) {
788
RefPtr<CacheEntry> existingEntry;
789
if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
790
LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
791
return false; // Already removed...
792
}
793
794
if (!aOverwrite && existingEntry != aEntry) {
795
LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry));
796
return false; // Already replaced...
797
}
798
799
LOG(("RemoveExactEntry [entry=%p removed]", aEntry));
800
aEntries->Remove(aKey);
801
return true;
802
}
803
804
nsresult CacheStorageService::ClearOriginInternal(
805
const nsAString& aOrigin, const OriginAttributes& aOriginAttributes,
806
bool aAnonymous) {
807
nsresult rv;
808
809
RefPtr<LoadContextInfo> info =
810
GetLoadContextInfo(aAnonymous, aOriginAttributes);
811
if (NS_WARN_IF(!info)) {
812
return NS_ERROR_FAILURE;
813
}
814
815
mozilla::MutexAutoLock lock(mLock);
816
817
if (sGlobalEntryTables) {
818
for (auto iter = sGlobalEntryTables->Iter(); !iter.Done(); iter.Next()) {
819
bool matches = false;
820
rv =
821
CacheFileUtils::KeyMatchesLoadContextInfo(iter.Key(), info, &matches);
822
NS_ENSURE_SUCCESS(rv, rv);
823
if (!matches) {
824
continue;
825
}
826
827
CacheEntryTable* table = iter.UserData();
828
MOZ_ASSERT(table);
829
830
nsTArray<RefPtr<CacheEntry>> entriesToDelete;
831
832
for (auto entryIter = table->Iter(); !entryIter.Done();
833
entryIter.Next()) {
834
CacheEntry* entry = entryIter.UserData();
835
836
nsCOMPtr<nsIURI> uri;
837
rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI());
838
NS_ENSURE_SUCCESS(rv, rv);
839
840
nsAutoString origin;
841
rv = nsContentUtils::GetUTFOrigin(uri, origin);
842
NS_ENSURE_SUCCESS(rv, rv);
843
844
if (origin != aOrigin) {
845
continue;
846
}
847
848
entriesToDelete.AppendElement(entry);
849
}
850
851
for (RefPtr<CacheEntry>& entry : entriesToDelete) {
852
nsAutoCString entryKey;
853
rv = entry->HashingKey(entryKey);
854
if (NS_FAILED(rv)) {
855
NS_ERROR("aEntry->HashingKey() failed?");
856
return rv;
857
}
858
859
RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */);
860
}
861
}
862
}
863
864
rv = CacheFileIOManager::EvictByContext(info, false /* pinned */, aOrigin);
865
NS_ENSURE_SUCCESS(rv, rv);
866
867
return NS_OK;
868
}
869
870
NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat) {
871
uint32_t what;
872
873
switch (aWhat) {
874
case PURGE_DISK_DATA_ONLY:
875
what = CacheEntry::PURGE_DATA_ONLY_DISK_BACKED;
876
break;
877
878
case PURGE_DISK_ALL:
879
what = CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED;
880
break;
881
882
case PURGE_EVERYTHING:
883
what = CacheEntry::PURGE_WHOLE;
884
break;
885
886
default:
887
return NS_ERROR_INVALID_ARG;
888
}
889
890
nsCOMPtr<nsIRunnable> event = new PurgeFromMemoryRunnable(this, what);
891
892
return Dispatch(event);
893
}
894
895
NS_IMETHODIMP CacheStorageService::PurgeFromMemoryRunnable::Run() {
896
if (NS_IsMainThread()) {
897
nsCOMPtr<nsIObserverService> observerService =
898
mozilla::services::GetObserverService();
899
if (observerService) {
900
observerService->NotifyObservers(
901
nullptr, "cacheservice:purge-memory-pools", nullptr);
902
}
903
904
return NS_OK;
905
}
906
907
if (mService) {
908
// TODO not all flags apply to both pools
909
mService->Pool(true).PurgeAll(mWhat);
910
mService->Pool(false).PurgeAll(mWhat);
911
mService = nullptr;
912
}
913
914
NS_DispatchToMainThread(this);
915
return NS_OK;
916
}
917
918
NS_IMETHODIMP CacheStorageService::AsyncGetDiskConsumption(
919
nsICacheStorageConsumptionObserver* aObserver) {
920
NS_ENSURE_ARG(aObserver);
921
922
nsresult rv;
923
924
rv = CacheIndex::AsyncGetDiskConsumption(aObserver);
925
NS_ENSURE_SUCCESS(rv, rv);
926
927
return NS_OK;
928
}
929
930
NS_IMETHODIMP CacheStorageService::GetIoTarget(nsIEventTarget** aEventTarget) {
931
NS_ENSURE_ARG(aEventTarget);
932
933
nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
934
ioTarget.forget(aEventTarget);
935
936
return NS_OK;
937
}
938
939
NS_IMETHODIMP CacheStorageService::AsyncVisitAllStorages(
940
nsICacheStorageVisitor* aVisitor, bool aVisitEntries) {
941
LOG(("CacheStorageService::AsyncVisitAllStorages [cb=%p]", aVisitor));
942
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
943
944
// Walking the disk cache also walks the memory cache.
945
RefPtr<WalkDiskCacheRunnable> event =
946
new WalkDiskCacheRunnable(nullptr, aVisitEntries, aVisitor);
947
return event->Walk();
948
949
return NS_OK;
950
}
951
952
// Methods used by CacheEntry for management of in-memory structures.
953
954
namespace {
955
956
class FrecencyComparator {
957
public:
958
bool Equals(CacheEntry* a, CacheEntry* b) const {
959
return a->GetFrecency() == b->GetFrecency();
960
}
961
bool LessThan(CacheEntry* a, CacheEntry* b) const {
962
return a->GetFrecency() < b->GetFrecency();
963
}
964
};
965
966
class ExpirationComparator {
967
public:
968
bool Equals(CacheEntry* a, CacheEntry* b) const {
969
return a->GetExpirationTime() == b->GetExpirationTime();
970
}
971
bool LessThan(CacheEntry* a, CacheEntry* b) const {
972
return a->GetExpirationTime() < b->GetExpirationTime();
973
}
974
};
975
976
} // namespace
977
978
void CacheStorageService::RegisterEntry(CacheEntry* aEntry) {
979
MOZ_ASSERT(IsOnManagementThread());
980
981
if (mShutdown || !aEntry->CanRegister()) return;
982
983
TelemetryRecordEntryCreation(aEntry);
984
985
LOG(("CacheStorageService::RegisterEntry [entry=%p]", aEntry));
986
987
MemoryPool& pool = Pool(aEntry->IsUsingDisk());
988
pool.mFrecencyArray.AppendElement(aEntry);
989
pool.mExpirationArray.AppendElement(aEntry);
990
991
aEntry->SetRegistered(true);
992
}
993
994
void CacheStorageService::UnregisterEntry(CacheEntry* aEntry) {
995
MOZ_ASSERT(IsOnManagementThread());
996
997
if (!aEntry->IsRegistered()) return;
998
999
TelemetryRecordEntryRemoval(aEntry);
1000
1001
LOG(("CacheStorageService::UnregisterEntry [entry=%p]", aEntry));
1002
1003
MemoryPool& pool = Pool(aEntry->IsUsingDisk());
1004
mozilla::DebugOnly<bool> removedFrecency =
1005
pool.mFrecencyArray.RemoveElement(aEntry);
1006
mozilla::DebugOnly<bool> removedExpiration =
1007
pool.mExpirationArray.RemoveElement(aEntry);
1008
1009
MOZ_ASSERT(mShutdown || (removedFrecency && removedExpiration));
1010
1011
// Note: aEntry->CanRegister() since now returns false
1012
aEntry->SetRegistered(false);
1013
}
1014
1015
static bool AddExactEntry(CacheEntryTable* aEntries, nsACString const& aKey,
1016
CacheEntry* aEntry, bool aOverwrite) {
1017
RefPtr<CacheEntry> existingEntry;
1018
if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
1019
bool equals = existingEntry == aEntry;
1020
LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals));
1021
return equals; // Already there...
1022
}
1023
1024
LOG(("AddExactEntry [entry=%p put]", aEntry));
1025
aEntries->Put(aKey, aEntry);
1026
return true;
1027
}
1028
1029
bool CacheStorageService::RemoveEntry(CacheEntry* aEntry,
1030
bool aOnlyUnreferenced) {
1031
LOG(("CacheStorageService::RemoveEntry [entry=%p]", aEntry));
1032
1033
nsAutoCString entryKey;
1034
nsresult rv = aEntry->HashingKey(entryKey);
1035
if (NS_FAILED(rv)) {
1036
NS_ERROR("aEntry->HashingKey() failed?");
1037
return false;
1038
}
1039
1040
mozilla::MutexAutoLock lock(mLock);
1041
1042
if (mShutdown) {
1043
LOG((" after shutdown"));
1044
return false;
1045
}
1046
1047
if (aOnlyUnreferenced) {
1048
if (aEntry->IsReferenced()) {
1049
LOG((" still referenced, not removing"));
1050
return false;
1051
}
1052
1053
if (!aEntry->IsUsingDisk() &&
1054
IsForcedValidEntry(aEntry->GetStorageID(), entryKey)) {
1055
LOG((" forced valid, not removing"));
1056
return false;
1057
}
1058
}
1059
1060
CacheEntryTable* entries;
1061
if (sGlobalEntryTables->Get(aEntry->GetStorageID(), &entries))
1062
RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
1063
1064
nsAutoCString memoryStorageID(aEntry->GetStorageID());
1065
AppendMemoryStorageTag(memoryStorageID);
1066
1067
if (sGlobalEntryTables->Get(memoryStorageID, &entries))
1068
RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
1069
1070
return true;
1071
}
1072
1073
void CacheStorageService::RecordMemoryOnlyEntry(CacheEntry* aEntry,
1074
bool aOnlyInMemory,
1075
bool aOverwrite) {
1076
LOG(
1077
("CacheStorageService::RecordMemoryOnlyEntry [entry=%p, memory=%d, "
1078
"overwrite=%d]",
1079
aEntry, aOnlyInMemory, aOverwrite));
1080
// This method is responsible to put this entry to a special record hashtable
1081
// that contains only entries that are stored in memory.
1082
// Keep in mind that every entry, regardless of whether is in-memory-only or
1083
// not is always recorded in the storage master hash table, the one identified
1084
// by CacheEntry.StorageID().
1085
1086
mLock.AssertCurrentThreadOwns();
1087
1088
if (mShutdown) {
1089
LOG((" after shutdown"));
1090
return;
1091
}
1092
1093
nsresult rv;
1094
1095
nsAutoCString entryKey;
1096
rv = aEntry->HashingKey(entryKey);
1097
if (NS_FAILED(rv)) {
1098
NS_ERROR("aEntry->HashingKey() failed?");
1099
return;
1100
}
1101
1102
CacheEntryTable* entries = nullptr;
1103
nsAutoCString memoryStorageID(aEntry->GetStorageID());
1104
AppendMemoryStorageTag(memoryStorageID);
1105
1106
if (!sGlobalEntryTables->Get(memoryStorageID, &entries)) {
1107
if (!aOnlyInMemory) {
1108
LOG((" not recorded as memory only"));
1109
return;
1110
}
1111
1112
entries = new CacheEntryTable(CacheEntryTable::MEMORY_ONLY);
1113
sGlobalEntryTables->Put(memoryStorageID, entries);
1114
LOG((" new memory-only storage table for %s", memoryStorageID.get()));
1115
}
1116
1117
if (aOnlyInMemory) {
1118
AddExactEntry(entries, entryKey, aEntry, aOverwrite);
1119
} else {
1120
RemoveExactEntry(entries, entryKey, aEntry, aOverwrite);
1121
}
1122
}
1123
1124
// Checks if a cache entry is forced valid (will be loaded directly from cache
1125
// without further validation) - see nsICacheEntry.idl for further details
1126
bool CacheStorageService::IsForcedValidEntry(nsACString const& aContextKey,
1127
nsACString const& aEntryKey) {
1128
return IsForcedValidEntry(aContextKey + aEntryKey);
1129
}
1130
1131
bool CacheStorageService::IsForcedValidEntry(
1132
nsACString const& aContextEntryKey) {
1133
mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1134
1135
TimeStamp validUntil;
1136
1137
if (!mForcedValidEntries.Get(aContextEntryKey, &validUntil)) {
1138
return false;
1139
}
1140
1141
if (validUntil.IsNull()) {
1142
return false;
1143
}
1144
1145
// Entry timeout not reached yet
1146
if (TimeStamp::NowLoRes() <= validUntil) {
1147
return true;
1148
}
1149
1150
// Entry timeout has been reached
1151
mForcedValidEntries.Remove(aContextEntryKey);
1152
return false;
1153
}
1154
1155
// Allows a cache entry to be loaded directly from cache without further
1156
// validation - see nsICacheEntry.idl for further details
1157
void CacheStorageService::ForceEntryValidFor(nsACString const& aContextKey,
1158
nsACString const& aEntryKey,
1159
uint32_t aSecondsToTheFuture) {
1160
mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1161
1162
TimeStamp now = TimeStamp::NowLoRes();
1163
ForcedValidEntriesPrune(now);
1164
1165
// This will be the timeout
1166
TimeStamp validUntil = now + TimeDuration::FromSeconds(aSecondsToTheFuture);
1167
1168
mForcedValidEntries.Put(aContextKey + aEntryKey, validUntil);
1169
}
1170
1171
void CacheStorageService::RemoveEntryForceValid(nsACString const& aContextKey,
1172
nsACString const& aEntryKey) {
1173
mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
1174
1175
LOG(("CacheStorageService::RemoveEntryForceValid context='%s' entryKey=%s",
1176
aContextKey.BeginReading(), aEntryKey.BeginReading()));
1177
mForcedValidEntries.Remove(aContextKey + aEntryKey);
1178
}
1179
1180
// Cleans out the old entries in mForcedValidEntries
1181
void CacheStorageService::ForcedValidEntriesPrune(TimeStamp& now) {
1182
static TimeDuration const oneMinute = TimeDuration::FromSeconds(60);
1183
static TimeStamp dontPruneUntil = now + oneMinute;
1184
if (now < dontPruneUntil) return;
1185
1186
for (auto iter = mForcedValidEntries.Iter(); !iter.Done(); iter.Next()) {
1187
if (iter.Data() < now) {
1188
iter.Remove();
1189
}
1190
}
1191
dontPruneUntil = now + oneMinute;
1192
}
1193
1194
void CacheStorageService::OnMemoryConsumptionChange(
1195
CacheMemoryConsumer* aConsumer, uint32_t aCurrentMemoryConsumption) {
1196
LOG(("CacheStorageService::OnMemoryConsumptionChange [consumer=%p, size=%u]",
1197
aConsumer, aCurrentMemoryConsumption));
1198
1199
uint32_t savedMemorySize = aConsumer->mReportedMemoryConsumption;
1200
if (savedMemorySize == aCurrentMemoryConsumption) return;
1201
1202
// Exchange saved size with current one.
1203
aConsumer->mReportedMemoryConsumption = aCurrentMemoryConsumption;
1204
1205
bool usingDisk = !(aConsumer->mFlags & CacheMemoryConsumer::MEMORY_ONLY);
1206
bool overLimit = Pool(usingDisk).OnMemoryConsumptionChange(
1207
savedMemorySize, aCurrentMemoryConsumption);
1208
1209
if (!overLimit) return;
1210
1211
// It's likely the timer has already been set when we get here,
1212
// check outside the lock to save resources.
1213
if (mPurgeTimer) return;
1214
1215
// We don't know if this is called under the service lock or not,
1216
// hence rather dispatch.
1217
RefPtr<nsIEventTarget> cacheIOTarget = Thread();
1218
if (!cacheIOTarget) return;
1219
1220
// Dispatch as a priority task, we want to set the purge timer
1221
// ASAP to prevent vain redispatch of this event.
1222
nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
1223
"net::CacheStorageService::SchedulePurgeOverMemoryLimit", this,
1224
&CacheStorageService::SchedulePurgeOverMemoryLimit);
1225
cacheIOTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1226
}
1227
1228
bool CacheStorageService::MemoryPool::OnMemoryConsumptionChange(
1229
uint32_t aSavedMemorySize, uint32_t aCurrentMemoryConsumption) {
1230
mMemorySize -= aSavedMemorySize;
1231
mMemorySize += aCurrentMemoryConsumption;
1232
1233
LOG((" mMemorySize=%u (+%u,-%u)", uint32_t(mMemorySize),
1234
aCurrentMemoryConsumption, aSavedMemorySize));
1235
1236
// Bypass purging when memory has not grew up significantly
1237
if (aCurrentMemoryConsumption <= aSavedMemorySize) return false;
1238
1239
return mMemorySize > Limit();
1240
}
1241
1242
void CacheStorageService::SchedulePurgeOverMemoryLimit() {
1243
LOG(("CacheStorageService::SchedulePurgeOverMemoryLimit"));
1244
1245
mozilla::MutexAutoLock lock(mLock);
1246
1247
if (mShutdown) {
1248
LOG((" past shutdown"));
1249
return;
1250
}
1251
1252
if (mPurgeTimer) {
1253
LOG((" timer already up"));
1254
return;
1255
}
1256
1257
mPurgeTimer = NS_NewTimer();
1258
if (mPurgeTimer) {
1259
nsresult rv;
1260
rv = mPurgeTimer->InitWithCallback(this, 1000, nsITimer::TYPE_ONE_SHOT);
1261
LOG((" timer init rv=0x%08" PRIx32, static_cast<uint32_t>(rv)));
1262
}
1263
}
1264
1265
NS_IMETHODIMP
1266
CacheStorageService::Notify(nsITimer* aTimer) {
1267
LOG(("CacheStorageService::Notify"));
1268
1269
mozilla::MutexAutoLock lock(mLock);
1270
1271
if (aTimer == mPurgeTimer) {
1272
mPurgeTimer = nullptr;
1273
1274
nsCOMPtr<nsIRunnable> event =
1275
NewRunnableMethod("net::CacheStorageService::PurgeOverMemoryLimit",
1276
this, &CacheStorageService::PurgeOverMemoryLimit);
1277
Dispatch(event);
1278
}
1279
1280
return NS_OK;
1281
}
1282
1283
NS_IMETHODIMP
1284
CacheStorageService::GetName(nsACString& aName) {
1285
aName.AssignLiteral("CacheStorageService");
1286
return NS_OK;
1287
}
1288
1289
void CacheStorageService::PurgeOverMemoryLimit() {
1290
MOZ_ASSERT(IsOnManagementThread());
1291
1292
LOG(("CacheStorageService::PurgeOverMemoryLimit"));
1293
1294
static TimeDuration const kFourSeconds = TimeDuration::FromSeconds(4);
1295
TimeStamp now = TimeStamp::NowLoRes();
1296
1297
if (!mLastPurgeTime.IsNull() && now - mLastPurgeTime < kFourSeconds) {
1298
LOG((" bypassed, too soon"));
1299
return;
1300
}
1301
1302
mLastPurgeTime = now;
1303
1304
Pool(true).PurgeOverMemoryLimit();
1305
Pool(false).PurgeOverMemoryLimit();
1306
}
1307
1308
void CacheStorageService::MemoryPool::PurgeOverMemoryLimit() {
1309
TimeStamp start(TimeStamp::Now());
1310
1311
uint32_t const memoryLimit = Limit();
1312
if (mMemorySize > memoryLimit) {
1313
LOG((" memory data consumption over the limit, abandon expired entries"));
1314
PurgeExpired();
1315
}
1316
1317
bool frecencyNeedsSort = true;
1318
1319
// No longer makes sense since:
1320
// Memory entries are never purged partially, only as a whole when the memory
1321
// cache limit is overreached.
1322
// Disk entries throw the data away ASAP so that only metadata are kept.
1323
// TODO when this concept of two separate pools is found working, the code
1324
// should clean up.
1325
#if 0
1326
if (mMemorySize > memoryLimit) {
1327
LOG((" memory data consumption over the limit, abandon disk backed data"));
1328
PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_DATA_ONLY_DISK_BACKED);
1329
}
1330
1331
if (mMemorySize > memoryLimit) {
1332
LOG((" metadata consumtion over the limit, abandon disk backed entries"));
1333
PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED);
1334
}
1335
#endif
1336
1337
if (mMemorySize > memoryLimit) {
1338
LOG((" memory data consumption over the limit, abandon any entry"));
1339
PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE);
1340
}
1341
1342
LOG((" purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds()));
1343
}
1344
1345
void CacheStorageService::MemoryPool::PurgeExpired() {
1346
MOZ_ASSERT(IsOnManagementThread());
1347
1348
mExpirationArray.Sort(ExpirationComparator());
1349
uint32_t now = NowInSeconds();
1350
1351
uint32_t const memoryLimit = Limit();
1352
1353
for (uint32_t i = 0;
1354
mMemorySize > memoryLimit && i < mExpirationArray.Length();) {
1355
if (CacheIOThread::YieldAndRerun()) return;
1356
1357
RefPtr<CacheEntry> entry = mExpirationArray[i];
1358
1359
uint32_t expirationTime = entry->GetExpirationTime();
1360
if (expirationTime > 0 && expirationTime <= now &&
1361
entry->Purge(CacheEntry::PURGE_WHOLE)) {
1362
LOG((" purged expired, entry=%p, exptime=%u (now=%u)", entry.get(),
1363
entry->GetExpirationTime(), now));
1364
continue;
1365
}
1366
1367
// not purged, move to the next one
1368
++i;
1369
}
1370
}
1371
1372
void CacheStorageService::MemoryPool::PurgeByFrecency(bool& aFrecencyNeedsSort,
1373
uint32_t aWhat) {
1374
MOZ_ASSERT(IsOnManagementThread());
1375
1376
if (aFrecencyNeedsSort) {
1377
mFrecencyArray.Sort(FrecencyComparator());
1378
aFrecencyNeedsSort = false;
1379
}
1380
1381
uint32_t const memoryLimit = Limit();
1382
1383
for (uint32_t i = 0;
1384
mMemorySize > memoryLimit && i < mFrecencyArray.Length();) {
1385
if (CacheIOThread::YieldAndRerun()) return;
1386
1387
RefPtr<CacheEntry> entry = mFrecencyArray[i];
1388
1389
if (entry->Purge(aWhat)) {
1390
LOG((" abandoned (%d), entry=%p, frecency=%1.10f", aWhat, entry.get(),
1391
entry->GetFrecency()));
1392
continue;
1393
}
1394
1395
// not purged, move to the next one
1396
++i;
1397
}
1398
}
1399
1400
void CacheStorageService::MemoryPool::PurgeAll(uint32_t aWhat) {
1401
LOG(("CacheStorageService::MemoryPool::PurgeAll aWhat=%d", aWhat));
1402
MOZ_ASSERT(IsOnManagementThread());
1403
1404
for (uint32_t i = 0; i < mFrecencyArray.Length();) {
1405
if (CacheIOThread::YieldAndRerun()) return;
1406
1407
RefPtr<CacheEntry> entry = mFrecencyArray[i];
1408
1409
if (entry->Purge(aWhat)) {
1410
LOG((" abandoned entry=%p", entry.get()));
1411
continue;
1412
}
1413
1414
// not purged, move to the next one
1415
++i;
1416
}
1417
}
1418
1419
// Methods exposed to and used by CacheStorage.
1420
1421
nsresult CacheStorageService::AddStorageEntry(CacheStorage const* aStorage,
1422
const nsACString& aURI,
1423
const nsACString& aIdExtension,
1424
bool aReplace,
1425
CacheEntryHandle** aResult) {
1426
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1427
1428
NS_ENSURE_ARG(aStorage);
1429
1430
nsAutoCString contextKey;
1431
CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1432
1433
return AddStorageEntry(contextKey, aURI, aIdExtension,
1434
aStorage->WriteToDisk(), aStorage->SkipSizeCheck(),
1435
aStorage->Pinning(), aReplace, aResult);
1436
}
1437
1438
nsresult CacheStorageService::AddStorageEntry(
1439
const nsACString& aContextKey, const nsACString& aURI,
1440
const nsACString& aIdExtension, bool aWriteToDisk, bool aSkipSizeCheck,
1441
bool aPin, bool aReplace, CacheEntryHandle** aResult) {
1442
nsresult rv;
1443
1444
nsAutoCString entryKey;
1445
rv = CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey);
1446
NS_ENSURE_SUCCESS(rv, rv);
1447
1448
LOG(("CacheStorageService::AddStorageEntry [entryKey=%s, contextKey=%s]",
1449
entryKey.get(), aContextKey.BeginReading()));
1450
1451
RefPtr<CacheEntry> entry;
1452
RefPtr<CacheEntryHandle> handle;
1453
1454
{
1455
mozilla::MutexAutoLock lock(mLock);
1456
1457
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1458
1459
// Ensure storage table
1460
CacheEntryTable* entries;
1461
if (!sGlobalEntryTables->Get(aContextKey, &entries)) {
1462
entries = new CacheEntryTable(CacheEntryTable::ALL_ENTRIES);
1463
sGlobalEntryTables->Put(aContextKey, entries);
1464
LOG((" new storage entries table for context '%s'",
1465
aContextKey.BeginReading()));
1466
}
1467
1468
bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
1469
1470
if (entryExists && !aReplace) {
1471
// check whether we want to turn this entry to a memory-only.
1472
if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) {
1473
LOG((" entry is persistent but we want mem-only, replacing it"));
1474
aReplace = true;
1475
}
1476
}
1477
1478
// If truncate is demanded, delete and doom the current entry
1479
if (entryExists && aReplace) {
1480
entries->Remove(entryKey);
1481
1482
LOG((" dooming entry %p for %s because of OPEN_TRUNCATE", entry.get(),
1483
entryKey.get()));
1484
// On purpose called under the lock to prevent races of doom and open on
1485
// I/O thread No need to remove from both memory-only and all-entries
1486
// tables. The new entry will overwrite the shadow entry in its ctor.
1487
entry->DoomAlreadyRemoved();
1488
1489
entry = nullptr;
1490
entryExists = false;
1491
1492
// Would only lead to deleting force-valid timestamp again. We don't need
1493
// the replace information anymore after this point anyway.
1494
aReplace = false;
1495
}
1496
1497
// Ensure entry for the particular URL
1498
if (!entryExists) {
1499
// When replacing with a new entry, always remove the current force-valid
1500
// timestamp, this is the only place to do it.
1501
if (aReplace) {
1502
RemoveEntryForceValid(aContextKey, entryKey);
1503
}
1504
1505
// Entry is not in the hashtable or has just been truncated...
1506
entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk,
1507
aSkipSizeCheck, aPin);
1508
entries->Put(entryKey, entry);
1509
LOG((" new entry %p for %s", entry.get(), entryKey.get()));
1510
}
1511
1512
if (entry) {
1513
// Here, if this entry was not for a long time referenced by any consumer,
1514
// gets again first 'handles count' reference.
1515
handle = entry->NewHandle();
1516
}
1517
}
1518
1519
handle.forget(aResult);
1520
return NS_OK;
1521
}
1522
1523
nsresult CacheStorageService::CheckStorageEntry(CacheStorage const* aStorage,
1524
const nsACString& aURI,
1525
const nsACString& aIdExtension,
1526
bool* aResult) {
1527
nsresult rv;
1528
1529
nsAutoCString contextKey;
1530
CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1531
1532
if (!aStorage->WriteToDisk()) {
1533
AppendMemoryStorageTag(contextKey);
1534
}
1535
1536
LOG(("CacheStorageService::CheckStorageEntry [uri=%s, eid=%s, contextKey=%s]",
1537
aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get()));
1538
1539
{
1540
mozilla::MutexAutoLock lock(mLock);
1541
1542
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1543
1544
nsAutoCString entryKey;
1545
rv = CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey);
1546
NS_ENSURE_SUCCESS(rv, rv);
1547
1548
CacheEntryTable* entries;
1549
if ((*aResult = sGlobalEntryTables->Get(contextKey, &entries)) &&
1550
entries->GetWeak(entryKey, aResult)) {
1551
LOG((" found in hash tables"));
1552
return NS_OK;
1553
}
1554
}
1555
1556
if (!aStorage->WriteToDisk()) {
1557
// Memory entry, nothing more to do.
1558
LOG((" not found in hash tables"));
1559
return NS_OK;
1560
}
1561
1562
// Disk entry, not found in the hashtable, check the index.
1563
nsAutoCString fileKey;
1564
rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey);
1565
1566
CacheIndex::EntryStatus status;
1567
rv = CacheIndex::HasEntry(fileKey, &status);
1568
if (NS_FAILED(rv) || status == CacheIndex::DO_NOT_KNOW) {
1569
LOG((" index doesn't know, rv=0x%08" PRIx32, static_cast<uint32_t>(rv)));
1570
return NS_ERROR_NOT_AVAILABLE;
1571
}
1572
1573
*aResult = status == CacheIndex::EXISTS;
1574
LOG((" %sfound in index", *aResult ? "" : "not "));
1575
return NS_OK;
1576
}
1577
1578
nsresult CacheStorageService::GetCacheIndexEntryAttrs(
1579
CacheStorage const* aStorage, const nsACString& aURI,
1580
const nsACString& aIdExtension, bool* aHasAltData, uint32_t* aFileSizeKb) {
1581
nsresult rv;
1582
1583
nsAutoCString contextKey;
1584
CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1585
1586
LOG(
1587
("CacheStorageService::GetCacheIndexEntryAttrs [uri=%s, eid=%s, "
1588
"contextKey=%s]",
1589
aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get()));
1590
1591
nsAutoCString fileKey;
1592
rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey);
1593
if (NS_FAILED(rv)) {
1594
return rv;
1595
}
1596
1597
*aHasAltData = false;
1598
*aFileSizeKb = 0;
1599
auto closure = [&aHasAltData, &aFileSizeKb](const CacheIndexEntry* entry) {
1600
*aHasAltData = entry->GetHasAltData();
1601
*aFileSizeKb = entry->GetFileSize();
1602
};
1603
1604
CacheIndex::EntryStatus status;
1605
rv = CacheIndex::HasEntry(fileKey, &status, closure);
1606
if (NS_FAILED(rv)) {
1607
return rv;
1608
}
1609
1610
if (status != CacheIndex::EXISTS) {
1611
return NS_ERROR_CACHE_KEY_NOT_FOUND;
1612
}
1613
1614
return NS_OK;
1615
}
1616
1617
namespace {
1618
1619
class CacheEntryDoomByKeyCallback : public CacheFileIOListener,
1620
public nsIRunnable {
1621
public:
1622
NS_DECL_THREADSAFE_ISUPPORTS
1623
NS_DECL_NSIRUNNABLE
1624
1625
explicit CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback* aCallback)
1626
: mCallback(aCallback), mResult(NS_ERROR_NOT_INITIALIZED) {}
1627
1628
private:
1629
virtual ~CacheEntryDoomByKeyCallback();
1630
1631
NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override {
1632
return NS_OK;
1633
}
1634
NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
1635
nsresult aResult) override {
1636
return NS_OK;
1637
}
1638
NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
1639
nsresult aResult) override {
1640
return NS_OK;
1641
}
1642
NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override;
1643
NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override {
1644
return NS_OK;
1645
}
1646
NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle,
1647
nsresult aResult) override {
1648
return NS_OK;
1649
}
1650
1651
nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
1652
nsresult mResult;
1653
};
1654
1655
CacheEntryDoomByKeyCallback::~CacheEntryDoomByKeyCallback() {
1656
if (mCallback)
1657
ProxyReleaseMainThread("CacheEntryDoomByKeyCallback::mCallback", mCallback);
1658
}
1659
1660
NS_IMETHODIMP CacheEntryDoomByKeyCallback::OnFileDoomed(
1661
CacheFileHandle* aHandle, nsresult aResult) {
1662
if (!mCallback) return NS_OK;
1663
1664
mResult = aResult;
1665
if (NS_IsMainThread()) {
1666
Run();
1667
} else {
1668
NS_DispatchToMainThread(this);
1669
}
1670
1671
return NS_OK;
1672
}
1673
1674
NS_IMETHODIMP CacheEntryDoomByKeyCallback::Run() {
1675
mCallback->OnCacheEntryDoomed(mResult);
1676
return NS_OK;
1677
}
1678
1679
NS_IMPL_ISUPPORTS(CacheEntryDoomByKeyCallback, CacheFileIOListener,
1680
nsIRunnable);
1681
1682
} // namespace
1683
1684
nsresult CacheStorageService::DoomStorageEntry(
1685
CacheStorage const* aStorage, const nsACString& aURI,
1686
const nsACString& aIdExtension, nsICacheEntryDoomCallback* aCallback) {
1687
LOG(("CacheStorageService::DoomStorageEntry"));
1688
1689
NS_ENSURE_ARG(aStorage);
1690
1691
nsAutoCString contextKey;
1692
CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1693
1694
nsAutoCString entryKey;
1695
nsresult rv =
1696
CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey);
1697
NS_ENSURE_SUCCESS(rv, rv);
1698
1699
RefPtr<CacheEntry> entry;
1700
{
1701
mozilla::MutexAutoLock lock(mLock);
1702
1703
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1704
1705
CacheEntryTable* entries;
1706
if (sGlobalEntryTables->Get(contextKey, &entries)) {
1707
if (entries->Get(entryKey, getter_AddRefs(entry))) {
1708
if (aStorage->WriteToDisk() || !entry->IsUsingDisk()) {
1709
// When evicting from disk storage, purge
1710
// When evicting from memory storage and the entry is memory-only,
1711
// purge
1712
LOG(
1713
(" purging entry %p for %s [storage use disk=%d, entry use "
1714
"disk=%d]",
1715
entry.get(), entryKey.get(), aStorage->WriteToDisk(),
1716
entry->IsUsingDisk()));
1717
entries->Remove(entryKey);
1718
} else {
1719
// Otherwise, leave it
1720
LOG(
1721
(" leaving entry %p for %s [storage use disk=%d, entry use "
1722
"disk=%d]",
1723
entry.get(), entryKey.get(), aStorage->WriteToDisk(),
1724
entry->IsUsingDisk()));
1725
entry = nullptr;
1726
}
1727
}
1728
}
1729
1730
if (!entry) {
1731
RemoveEntryForceValid(contextKey, entryKey);
1732
}
1733
}
1734
1735
if (entry) {
1736
LOG((" dooming entry %p for %s", entry.get(), entryKey.get()));
1737
return entry->AsyncDoom(aCallback);
1738
}
1739
1740
LOG((" no entry loaded for %s", entryKey.get()));
1741
1742
if (aStorage->WriteToDisk()) {
1743
nsAutoCString contextKey;
1744
CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1745
1746
rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, entryKey);
1747
NS_ENSURE_SUCCESS(rv, rv);
1748
1749
LOG((" dooming file only for %s", entryKey.get()));
1750
1751
RefPtr<CacheEntryDoomByKeyCallback> callback(
1752
new CacheEntryDoomByKeyCallback(aCallback));
1753
rv = CacheFileIOManager::DoomFileByKey(entryKey, callback);
1754
NS_ENSURE_SUCCESS(rv, rv);
1755
1756
return NS_OK;
1757
}
1758
1759
class Callback : public Runnable {
1760
public:
1761
explicit Callback(nsICacheEntryDoomCallback* aCallback)
1762
: mozilla::Runnable("Callback"), mCallback(aCallback) {}
1763
NS_IMETHOD Run() override {
1764
mCallback->OnCacheEntryDoomed(NS_ERROR_NOT_AVAILABLE);
1765
return NS_OK;
1766
}
1767
nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
1768
};
1769
1770
if (aCallback) {
1771
RefPtr<Runnable> callback = new Callback(aCallback);
1772
return NS_DispatchToMainThread(callback);
1773
}
1774
1775
return NS_OK;
1776
}
1777
1778
nsresult CacheStorageService::DoomStorageEntries(
1779
CacheStorage const* aStorage, nsICacheEntryDoomCallback* aCallback) {
1780
LOG(("CacheStorageService::DoomStorageEntries"));
1781
1782
NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
1783
NS_ENSURE_ARG(aStorage);
1784
1785
nsAutoCString contextKey;
1786
CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
1787
1788
mozilla::MutexAutoLock lock(mLock);
1789
1790
return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
1791
aStorage->WriteToDisk(), aStorage->Pinning(),
1792
aCallback);
1793
}
1794
1795
nsresult CacheStorageService::DoomStorageEntries(
1796
const nsACString& aContextKey, nsILoadContextInfo* aContext,
1797
bool aDiskStorage, bool aPinned, nsICacheEntryDoomCallback* aCallback) {
1798
LOG(("CacheStorageService::DoomStorageEntries [context=%s]",
1799
aContextKey.BeginReading()));
1800
1801
mLock.AssertCurrentThreadOwns();
1802
1803
NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
1804
1805
nsAutoCString memoryStorageID(aContextKey);
1806
AppendMemoryStorageTag(memoryStorageID);
1807
1808
if (aDiskStorage) {
1809
LOG((" dooming disk+memory storage of %s", aContextKey.BeginReading()));
1810
1811
// Walk one by one and remove entries according their pin status
1812
CacheEntryTable *diskEntries, *memoryEntries;
1813
if (sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
1814
sGlobalEntryTables->Get(memoryStorageID, &memoryEntries);
1815
1816
for (auto iter = diskEntries->Iter(); !iter.Done(); iter.Next()) {
1817
auto entry = iter.Data();
1818
if (entry->DeferOrBypassRemovalOnPinStatus(aPinned)) {
1819
continue;
1820
}
1821
1822
if (memoryEntries) {
1823
RemoveExactEntry(memoryEntries, iter.Key(), entry, false);
1824
}
1825
iter.Remove();
1826
}
1827
}
1828
1829
if (aContext && !aContext->IsPrivate()) {
1830
LOG((" dooming disk entries"));
1831
CacheFileIOManager::EvictByContext(aContext, aPinned, EmptyString());
1832
}
1833
} else {
1834
LOG((" dooming memory-only storage of %s", aContextKey.BeginReading()));
1835
1836
// Remove the memory entries table from the global tables.
1837
// Since we store memory entries also in the disk entries table
1838
// we need to remove the memory entries from the disk table one