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 "nsCacheService.h"
8
9
#include "mozilla/ArrayUtils.h"
10
#include "mozilla/Attributes.h"
11
#include "mozilla/Assertions.h"
12
#include "mozilla/DebugOnly.h"
13
#include "mozilla/FileUtils.h"
14
15
#include "necko-config.h"
16
17
#include "nsCache.h"
18
#include "nsCacheRequest.h"
19
#include "nsCacheEntry.h"
20
#include "nsCacheEntryDescriptor.h"
21
#include "nsCacheDevice.h"
22
#include "nsICacheVisitor.h"
23
#include "nsDiskCacheDeviceSQL.h"
24
#include "nsCacheUtils.h"
25
#include "../cache2/CacheObserver.h"
26
#include "nsINamed.h"
27
#include "nsIObserverService.h"
28
#include "nsIPrefService.h"
29
#include "nsIPrefBranch.h"
30
#include "nsIFile.h"
31
#include "nsIOService.h"
32
#include "nsDirectoryServiceDefs.h"
33
#include "nsAppDirectoryServiceDefs.h"
34
#include "nsThreadUtils.h"
35
#include "nsProxyRelease.h"
36
#include "nsDeleteDir.h"
37
#include "nsNetCID.h"
38
#include <math.h> // for log()
39
#include "mozilla/Services.h"
40
#include "nsITimer.h"
41
#include "mozIStorageService.h"
42
43
#include "mozilla/net/NeckoCommon.h"
44
#include <algorithm>
45
46
using namespace mozilla;
47
using namespace mozilla::net;
48
49
/******************************************************************************
50
* nsCacheProfilePrefObserver
51
*****************************************************************************/
52
#define OFFLINE_CACHE_ENABLE_PREF "browser.cache.offline.enable"
53
#define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory"
54
#define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity"
55
#define OFFLINE_CACHE_CAPACITY 512000
56
57
#define CACHE_COMPRESSION_LEVEL 1
58
59
static const char* observerList[] = {
60
"profile-before-change", "profile-do-change",
61
NS_XPCOM_SHUTDOWN_OBSERVER_ID, "last-pb-context-exited",
62
"suspend_process_notification", "resume_process_notification"};
63
64
static const char* prefList[] = {
65
OFFLINE_CACHE_ENABLE_PREF,
66
OFFLINE_CACHE_CAPACITY_PREF,
67
OFFLINE_CACHE_DIR_PREF,
68
nullptr,
69
};
70
71
class nsCacheProfilePrefObserver : public nsIObserver {
72
virtual ~nsCacheProfilePrefObserver() = default;
73
74
public:
75
NS_DECL_THREADSAFE_ISUPPORTS
76
NS_DECL_NSIOBSERVER
77
78
nsCacheProfilePrefObserver()
79
: mHaveProfile(false),
80
mOfflineCacheEnabled(false),
81
mOfflineCacheCapacity(0),
82
mCacheCompressionLevel(CACHE_COMPRESSION_LEVEL),
83
mSanitizeOnShutdown(false),
84
mClearCacheOnShutdown(false) {}
85
86
nsresult Install();
87
void Remove();
88
nsresult ReadPrefs(nsIPrefBranch* branch);
89
90
nsIFile* DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
91
92
bool OfflineCacheEnabled();
93
int32_t OfflineCacheCapacity() { return mOfflineCacheCapacity; }
94
nsIFile* OfflineCacheParentDirectory() {
95
return mOfflineCacheParentDirectory;
96
}
97
98
int32_t CacheCompressionLevel();
99
100
bool SanitizeAtShutdown() {
101
return mSanitizeOnShutdown && mClearCacheOnShutdown;
102
}
103
104
void PrefChanged(const char* aPref);
105
106
private:
107
bool mHaveProfile;
108
109
nsCOMPtr<nsIFile> mDiskCacheParentDirectory;
110
111
bool mOfflineCacheEnabled;
112
int32_t mOfflineCacheCapacity; // in kilobytes
113
nsCOMPtr<nsIFile> mOfflineCacheParentDirectory;
114
115
int32_t mCacheCompressionLevel;
116
117
bool mSanitizeOnShutdown;
118
bool mClearCacheOnShutdown;
119
};
120
121
NS_IMPL_ISUPPORTS(nsCacheProfilePrefObserver, nsIObserver)
122
123
class nsBlockOnCacheThreadEvent : public Runnable {
124
public:
125
nsBlockOnCacheThreadEvent()
126
: mozilla::Runnable("nsBlockOnCacheThreadEvent") {}
127
NS_IMETHOD Run() override {
128
nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSBLOCKONCACHETHREADEVENT_RUN));
129
CACHE_LOG_DEBUG(("nsBlockOnCacheThreadEvent [%p]\n", this));
130
nsCacheService::gService->mNotified = true;
131
nsCacheService::gService->mCondVar.Notify();
132
return NS_OK;
133
}
134
};
135
136
nsresult nsCacheProfilePrefObserver::Install() {
137
// install profile-change observer
138
nsCOMPtr<nsIObserverService> observerService =
139
mozilla::services::GetObserverService();
140
if (!observerService) return NS_ERROR_FAILURE;
141
142
nsresult rv, rv2 = NS_OK;
143
for (auto& observer : observerList) {
144
rv = observerService->AddObserver(this, observer, false);
145
if (NS_FAILED(rv)) rv2 = rv;
146
}
147
148
// install preferences observer
149
nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
150
if (!branch) return NS_ERROR_FAILURE;
151
152
Preferences::RegisterCallbacks(
153
PREF_CHANGE_METHOD(nsCacheProfilePrefObserver::PrefChanged), prefList,
154
this);
155
156
// Determine if we have a profile already
157
// Install() is called *after* the profile-after-change notification
158
// when there is only a single profile, or it is specified on the
159
// commandline at startup.
160
// In that case, we detect the presence of a profile by the existence
161
// of the NS_APP_USER_PROFILE_50_DIR directory.
162
163
nsCOMPtr<nsIFile> directory;
164
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
165
getter_AddRefs(directory));
166
if (NS_SUCCEEDED(rv)) mHaveProfile = true;
167
168
rv = ReadPrefs(branch);
169
NS_ENSURE_SUCCESS(rv, rv);
170
171
return rv2;
172
}
173
174
void nsCacheProfilePrefObserver::Remove() {
175
// remove Observer Service observers
176
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
177
if (obs) {
178
for (auto& observer : observerList) {
179
obs->RemoveObserver(this, observer);
180
}
181
}
182
183
// remove Pref Service observers
184
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
185
if (!prefs) return;
186
Preferences::UnregisterCallbacks(
187
PREF_CHANGE_METHOD(nsCacheProfilePrefObserver::PrefChanged), prefList,
188
this);
189
}
190
191
NS_IMETHODIMP
192
nsCacheProfilePrefObserver::Observe(nsISupports* subject, const char* topic,
193
const char16_t* data_unicode) {
194
NS_ConvertUTF16toUTF8 data(data_unicode);
195
CACHE_LOG_INFO(("Observe [topic=%s data=%s]\n", topic, data.get()));
196
197
if (!nsCacheService::IsInitialized()) {
198
if (!strcmp("resume_process_notification", topic)) {
199
// A suspended process has a closed cache, so re-open it here.
200
nsCacheService::GlobalInstance()->Init();
201
}
202
return NS_OK;
203
}
204
205
if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
206
// xpcom going away, shutdown cache service
207
nsCacheService::GlobalInstance()->Shutdown();
208
} else if (!strcmp("profile-before-change", topic)) {
209
// profile before change
210
mHaveProfile = false;
211
212
// XXX shutdown devices
213
nsCacheService::OnProfileShutdown();
214
} else if (!strcmp("suspend_process_notification", topic)) {
215
// A suspended process may never return, so shutdown the cache to reduce
216
// cache corruption.
217
nsCacheService::GlobalInstance()->Shutdown();
218
} else if (!strcmp("profile-do-change", topic)) {
219
// profile after change
220
mHaveProfile = true;
221
nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
222
if (!branch) {
223
return NS_ERROR_FAILURE;
224
}
225
(void)ReadPrefs(branch);
226
nsCacheService::OnProfileChanged();
227
228
} else if (!strcmp("last-pb-context-exited", topic)) {
229
nsCacheService::LeavePrivateBrowsing();
230
}
231
232
return NS_OK;
233
}
234
235
void nsCacheProfilePrefObserver::PrefChanged(const char* aPref) {
236
// ignore pref changes until we're done switch profiles
237
if (!mHaveProfile) return;
238
// which preference changed?
239
nsresult rv;
240
if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, aPref)) {
241
rv = Preferences::GetBool(OFFLINE_CACHE_ENABLE_PREF, &mOfflineCacheEnabled);
242
if (NS_FAILED(rv)) return;
243
nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
244
245
} else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, aPref)) {
246
int32_t capacity = 0;
247
rv = Preferences::GetInt(OFFLINE_CACHE_CAPACITY_PREF, &capacity);
248
if (NS_FAILED(rv)) return;
249
mOfflineCacheCapacity = std::max(0, capacity);
250
nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity);
251
#if 0
252
} else if (!strcmp(OFFLINE_CACHE_DIR_PREF, aPref)) {
253
// XXX We probaby don't want to respond to this pref except after
254
// XXX profile changes. Ideally, there should be some kind of user
255
// XXX notification that the pref change won't take effect until
256
// XXX the next time the profile changes (browser launch)
257
#endif
258
}
259
}
260
261
nsresult nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch) {
262
nsresult rv = NS_OK;
263
264
if (!mDiskCacheParentDirectory) {
265
nsCOMPtr<nsIFile> directory;
266
267
// try to get the disk cache parent directory
268
rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
269
getter_AddRefs(directory));
270
if (NS_FAILED(rv)) {
271
// try to get the profile directory (there may not be a profile yet)
272
nsCOMPtr<nsIFile> profDir;
273
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
274
getter_AddRefs(profDir));
275
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
276
getter_AddRefs(directory));
277
if (!directory)
278
directory = profDir;
279
else if (profDir) {
280
nsCacheService::MoveOrRemoveDiskCache(profDir, directory, "Cache");
281
}
282
}
283
// use file cache in build tree only if asked, to avoid cache dir litter
284
if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
285
rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
286
getter_AddRefs(directory));
287
}
288
if (directory) {
289
mDiskCacheParentDirectory = directory;
290
}
291
}
292
293
// read offline cache device prefs
294
mOfflineCacheEnabled = true; // presume offline cache is enabled
295
(void)branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF, &mOfflineCacheEnabled);
296
297
mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY;
298
(void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &mOfflineCacheCapacity);
299
mOfflineCacheCapacity = std::max(0, mOfflineCacheCapacity);
300
301
(void)branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error
302
NS_GET_IID(nsIFile),
303
getter_AddRefs(mOfflineCacheParentDirectory));
304
305
if (!mOfflineCacheParentDirectory) {
306
nsCOMPtr<nsIFile> directory;
307
308
// try to get the offline cache parent directory
309
rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
310
getter_AddRefs(directory));
311
if (NS_FAILED(rv)) {
312
// try to get the profile directory (there may not be a profile yet)
313
nsCOMPtr<nsIFile> profDir;
314
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
315
getter_AddRefs(profDir));
316
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
317
getter_AddRefs(directory));
318
if (!directory)
319
directory = profDir;
320
else if (profDir) {
321
nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
322
"OfflineCache");
323
}
324
}
325
#if DEBUG
326
if (!directory) {
327
// use current process directory during development
328
rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
329
getter_AddRefs(directory));
330
}
331
#endif
332
if (directory) {
333
mOfflineCacheParentDirectory = directory;
334
}
335
}
336
337
return rv;
338
}
339
340
nsresult nsCacheService::DispatchToCacheIOThread(nsIRunnable* event) {
341
if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
342
return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
343
}
344
345
nsresult nsCacheService::SyncWithCacheIOThread() {
346
if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
347
gService->mLock.AssertCurrentThreadOwns();
348
349
nsCOMPtr<nsIRunnable> event = new nsBlockOnCacheThreadEvent();
350
351
// dispatch event - it will notify the monitor when it's done
352
nsresult rv = gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
353
if (NS_FAILED(rv)) {
354
NS_WARNING("Failed dispatching block-event");
355
return NS_ERROR_UNEXPECTED;
356
}
357
358
// wait until notified, then return
359
gService->mNotified = false;
360
while (!gService->mNotified) {
361
gService->mCondVar.Wait();
362
}
363
364
return NS_OK;
365
}
366
367
bool nsCacheProfilePrefObserver::OfflineCacheEnabled() {
368
if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
369
return false;
370
371
return mOfflineCacheEnabled;
372
}
373
374
int32_t nsCacheProfilePrefObserver::CacheCompressionLevel() {
375
return mCacheCompressionLevel;
376
}
377
378
/******************************************************************************
379
* nsProcessRequestEvent
380
*****************************************************************************/
381
382
class nsProcessRequestEvent : public Runnable {
383
public:
384
explicit nsProcessRequestEvent(nsCacheRequest* aRequest)
385
: mozilla::Runnable("nsProcessRequestEvent") {
386
mRequest = aRequest;
387
}
388
389
NS_IMETHOD Run() override {
390
nsresult rv;
391
392
NS_ASSERTION(mRequest->mListener,
393
"Sync OpenCacheEntry() posted to background thread!");
394
395
nsCacheServiceAutoLock lock(LOCK_TELEM(NSPROCESSREQUESTEVENT_RUN));
396
rv = nsCacheService::gService->ProcessRequest(mRequest, false, nullptr);
397
398
// Don't delete the request if it was queued
399
if (!(mRequest->IsBlocking() && rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))
400
delete mRequest;
401
402
return NS_OK;
403
}
404
405
protected:
406
virtual ~nsProcessRequestEvent() = default;
407
408
private:
409
nsCacheRequest* mRequest;
410
};
411
412
/******************************************************************************
413
* nsDoomEvent
414
*****************************************************************************/
415
416
class nsDoomEvent : public Runnable {
417
public:
418
nsDoomEvent(nsCacheSession* session, const nsACString& key,
419
nsICacheListener* listener)
420
: mozilla::Runnable("nsDoomEvent") {
421
mKey = *session->ClientID();
422
mKey.Append(':');
423
mKey.Append(key);
424
mStoragePolicy = session->StoragePolicy();
425
mListener = listener;
426
mEventTarget = GetCurrentThreadEventTarget();
427
// We addref the listener here and release it in nsNotifyDoomListener
428
// on the callers thread. If posting of nsNotifyDoomListener event fails
429
// we leak the listener which is better than releasing it on a wrong
430
// thread.
431
NS_IF_ADDREF(mListener);
432
}
433
434
NS_IMETHOD Run() override {
435
nsCacheServiceAutoLock lock;
436
437
bool foundActive = true;
438
nsresult status = NS_ERROR_NOT_AVAILABLE;
439
nsCacheEntry* entry;
440
entry = nsCacheService::gService->mActiveEntries.GetEntry(&mKey);
441
if (!entry) {
442
bool collision = false;
443
foundActive = false;
444
entry = nsCacheService::gService->SearchCacheDevices(
445
&mKey, mStoragePolicy, &collision);
446
}
447
448
if (entry) {
449
status = NS_OK;
450
nsCacheService::gService->DoomEntry_Internal(entry, foundActive);
451
}
452
453
if (mListener) {
454
mEventTarget->Dispatch(new nsNotifyDoomListener(mListener, status),
455
NS_DISPATCH_NORMAL);
456
// posted event will release the reference on the correct thread
457
mListener = nullptr;
458
}
459
460
return NS_OK;
461
}
462
463
private:
464
nsCString mKey;
465
nsCacheStoragePolicy mStoragePolicy;
466
nsICacheListener* mListener;
467
nsCOMPtr<nsIEventTarget> mEventTarget;
468
};
469
470
/******************************************************************************
471
* nsCacheService
472
*****************************************************************************/
473
nsCacheService* nsCacheService::gService = nullptr;
474
475
NS_IMPL_ISUPPORTS(nsCacheService, nsICacheService, nsICacheServiceInternal)
476
477
nsCacheService::nsCacheService()
478
: mObserver(nullptr),
479
mLock("nsCacheService.mLock"),
480
mCondVar(mLock, "nsCacheService.mCondVar"),
481
mNotified(false),
482
mTimeStampLock("nsCacheService.mTimeStampLock"),
483
mInitialized(false),
484
mClearingEntries(false),
485
mEnableOfflineDevice(false),
486
mOfflineDevice(nullptr),
487
mDoomedEntries{},
488
mTotalEntries(0),
489
mCacheHits(0),
490
mCacheMisses(0),
491
mMaxKeyLength(0),
492
mMaxDataSize(0),
493
mMaxMetaSize(0),
494
mDeactivateFailures(0),
495
mDeactivatedUnboundEntries(0) {
496
NS_ASSERTION(gService == nullptr, "multiple nsCacheService instances!");
497
gService = this;
498
499
// create list of cache devices
500
PR_INIT_CLIST(&mDoomedEntries);
501
}
502
503
nsCacheService::~nsCacheService() {
504
if (mInitialized) // Shutdown hasn't been called yet.
505
(void)Shutdown();
506
507
if (mObserver) {
508
mObserver->Remove();
509
NS_RELEASE(mObserver);
510
}
511
512
gService = nullptr;
513
}
514
515
nsresult nsCacheService::Init() {
516
// Thie method must be called on the main thread because mCacheIOThread must
517
// only be modified on the main thread.
518
if (!NS_IsMainThread()) {
519
NS_ERROR("nsCacheService::Init called off the main thread");
520
return NS_ERROR_NOT_SAME_THREAD;
521
}
522
523
NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
524
if (mInitialized) return NS_ERROR_ALREADY_INITIALIZED;
525
526
if (mozilla::net::IsNeckoChild()) {
527
return NS_ERROR_UNEXPECTED;
528
}
529
530
nsresult rv;
531
532
mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
533
NS_ENSURE_SUCCESS(rv, rv);
534
535
rv = NS_NewNamedThread("Cache I/O", getter_AddRefs(mCacheIOThread));
536
if (NS_FAILED(rv)) {
537
NS_WARNING("Can't create cache IO thread");
538
}
539
540
rv = nsDeleteDir::Init();
541
if (NS_FAILED(rv)) {
542
NS_WARNING("Can't initialize nsDeleteDir");
543
}
544
545
// initialize hashtable for active cache entries
546
mActiveEntries.Init();
547
548
// create profile/preference observer
549
if (!mObserver) {
550
mObserver = new nsCacheProfilePrefObserver();
551
NS_ADDREF(mObserver);
552
mObserver->Install();
553
}
554
555
mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
556
557
mInitialized = true;
558
return NS_OK;
559
}
560
561
void nsCacheService::Shutdown() {
562
// This method must be called on the main thread because mCacheIOThread must
563
// only be modified on the main thread.
564
if (!NS_IsMainThread()) {
565
MOZ_CRASH("nsCacheService::Shutdown called off the main thread");
566
}
567
568
nsCOMPtr<nsIThread> cacheIOThread;
569
Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
570
571
bool shouldSanitize = false;
572
nsCOMPtr<nsIFile> parentDir;
573
574
{
575
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
576
NS_ASSERTION(
577
mInitialized,
578
"can't shutdown nsCacheService unless it has been initialized.");
579
if (!mInitialized) return;
580
581
mClearingEntries = true;
582
DoomActiveEntries(nullptr);
583
}
584
585
CloseAllStreams();
586
587
{
588
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
589
NS_ASSERTION(mInitialized, "Bad state");
590
591
mInitialized = false;
592
593
// Clear entries
594
ClearDoomList();
595
596
// Make sure to wait for any pending cache-operations before
597
// proceeding with destructive actions (bug #620660)
598
(void)SyncWithCacheIOThread();
599
mActiveEntries.Shutdown();
600
601
// obtain the disk cache directory in case we need to sanitize it
602
parentDir = mObserver->DiskCacheParentDirectory();
603
shouldSanitize = mObserver->SanitizeAtShutdown();
604
605
if (mOfflineDevice) mOfflineDevice->Shutdown();
606
607
NS_IF_RELEASE(mOfflineDevice);
608
609
for (auto iter = mCustomOfflineDevices.Iter(); !iter.Done(); iter.Next()) {
610
iter.Data()->Shutdown();
611
iter.Remove();
612
}
613
614
LogCacheStatistics();
615
616
mClearingEntries = false;
617
mCacheIOThread.swap(cacheIOThread);
618
}
619
620
if (cacheIOThread) nsShutdownThread::BlockingShutdown(cacheIOThread);
621
622
if (shouldSanitize) {
623
nsresult rv = parentDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
624
if (NS_SUCCEEDED(rv)) {
625
bool exists;
626
if (NS_SUCCEEDED(parentDir->Exists(&exists)) && exists)
627
nsDeleteDir::DeleteDir(parentDir, false);
628
}
629
Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE>
630
timer;
631
nsDeleteDir::Shutdown(shouldSanitize);
632
} else {
633
Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN>
634
timer;
635
nsDeleteDir::Shutdown(shouldSanitize);
636
}
637
}
638
639
nsresult nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID,
640
void** aResult) {
641
nsresult rv;
642
643
if (aOuter != nullptr) return NS_ERROR_NO_AGGREGATION;
644
645
RefPtr<nsCacheService> cacheService = new nsCacheService();
646
rv = cacheService->Init();
647
if (NS_SUCCEEDED(rv)) {
648
rv = cacheService->QueryInterface(aIID, aResult);
649
}
650
return rv;
651
}
652
653
NS_IMETHODIMP
654
nsCacheService::CreateSession(const char* clientID,
655
nsCacheStoragePolicy storagePolicy,
656
bool streamBased, nsICacheSession** result) {
657
*result = nullptr;
658
659
return NS_ERROR_NOT_IMPLEMENTED;
660
}
661
662
nsresult nsCacheService::CreateSessionInternal(
663
const char* clientID, nsCacheStoragePolicy storagePolicy, bool streamBased,
664
nsICacheSession** result) {
665
RefPtr<nsCacheSession> session =
666
new nsCacheSession(clientID, storagePolicy, streamBased);
667
session.forget(result);
668
669
return NS_OK;
670
}
671
672
nsresult nsCacheService::EvictEntriesForSession(nsCacheSession* session) {
673
NS_ASSERTION(gService, "nsCacheService::gService is null.");
674
return gService->EvictEntriesForClient(session->ClientID()->get(),
675
session->StoragePolicy());
676
}
677
678
namespace {
679
680
class EvictionNotifierRunnable : public Runnable {
681
public:
682
explicit EvictionNotifierRunnable(nsISupports* aSubject)
683
: mozilla::Runnable("EvictionNotifierRunnable"), mSubject(aSubject) {}
684
685
NS_DECL_NSIRUNNABLE
686
687
private:
688
nsCOMPtr<nsISupports> mSubject;
689
};
690
691
NS_IMETHODIMP
692
EvictionNotifierRunnable::Run() {
693
nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
694
if (obsSvc) {
695
obsSvc->NotifyObservers(mSubject, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
696
nullptr);
697
}
698
return NS_OK;
699
}
700
701
} // namespace
702
703
nsresult nsCacheService::EvictEntriesForClient(
704
const char* clientID, nsCacheStoragePolicy storagePolicy) {
705
RefPtr<EvictionNotifierRunnable> r =
706
new EvictionNotifierRunnable(NS_ISUPPORTS_CAST(nsICacheService*, this));
707
NS_DispatchToMainThread(r);
708
709
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_EVICTENTRIESFORCLIENT));
710
nsresult res = NS_OK;
711
712
// Only clear the offline cache if it has been specifically asked for.
713
if (storagePolicy == nsICache::STORE_OFFLINE) {
714
if (mEnableOfflineDevice) {
715
nsresult rv = NS_OK;
716
if (!mOfflineDevice) rv = CreateOfflineDevice();
717
if (mOfflineDevice) rv = mOfflineDevice->EvictEntries(clientID);
718
if (NS_FAILED(rv)) res = rv;
719
}
720
}
721
722
return res;
723
}
724
725
nsresult nsCacheService::IsStorageEnabledForPolicy(
726
nsCacheStoragePolicy storagePolicy, bool* result) {
727
if (gService == nullptr) return NS_ERROR_NOT_AVAILABLE;
728
nsCacheServiceAutoLock lock(
729
LOCK_TELEM(NSCACHESERVICE_ISSTORAGEENABLEDFORPOLICY));
730
731
*result = nsCacheService::IsStorageEnabledForPolicy_Locked(storagePolicy);
732
return NS_OK;
733
}
734
735
nsresult nsCacheService::DoomEntry(nsCacheSession* session,
736
const nsACString& key,
737
nsICacheListener* listener) {
738
CACHE_LOG_DEBUG(("Dooming entry for session %p, key %s\n", session,
739
PromiseFlatCString(key).get()));
740
if (!gService || !gService->mInitialized) return NS_ERROR_NOT_INITIALIZED;
741
742
return DispatchToCacheIOThread(new nsDoomEvent(session, key, listener));
743
}
744
745
bool nsCacheService::IsStorageEnabledForPolicy_Locked(
746
nsCacheStoragePolicy storagePolicy) {
747
if (gService->mEnableOfflineDevice &&
748
storagePolicy == nsICache::STORE_OFFLINE) {
749
return true;
750
}
751
752
return false;
753
}
754
755
NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor* visitor) {
756
return NS_ERROR_NOT_IMPLEMENTED;
757
}
758
759
nsresult nsCacheService::VisitEntriesInternal(nsICacheVisitor* visitor) {
760
NS_ENSURE_ARG_POINTER(visitor);
761
762
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_VISITENTRIES));
763
764
if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
765
766
// XXX record the fact that a visitation is in progress,
767
// XXX i.e. keep list of visitors in progress.
768
769
nsresult rv = NS_OK;
770
771
if (mEnableOfflineDevice) {
772
if (!mOfflineDevice) {
773
rv = CreateOfflineDevice();
774
if (NS_FAILED(rv)) return rv;
775
}
776
rv = mOfflineDevice->Visit(visitor);
777
if (NS_FAILED(rv)) return rv;
778
}
779
780
// XXX notify any shutdown process that visitation is complete for THIS
781
// visitor.
782
// XXX keep queue of visitors
783
784
return NS_OK;
785
}
786
787
void nsCacheService::FireClearNetworkCacheStoredAnywhereNotification() {
788
MOZ_ASSERT(NS_IsMainThread());
789
nsCOMPtr<nsIObserverService> obsvc = mozilla::services::GetObserverService();
790
if (obsvc) {
791
obsvc->NotifyObservers(nullptr, "network-clear-cache-stored-anywhere",
792
nullptr);
793
}
794
}
795
796
NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy) {
797
return NS_ERROR_NOT_IMPLEMENTED;
798
}
799
800
nsresult nsCacheService::EvictEntriesInternal(
801
nsCacheStoragePolicy storagePolicy) {
802
if (storagePolicy == nsICache::STORE_ANYWHERE) {
803
// if not called on main thread, dispatch the notification to the main
804
// thread to notify observers
805
if (!NS_IsMainThread()) {
806
nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
807
"nsCacheService::FireClearNetworkCacheStoredAnywhereNotification",
808
this,
809
&nsCacheService::FireClearNetworkCacheStoredAnywhereNotification);
810
NS_DispatchToMainThread(event);
811
} else {
812
// else you're already on main thread - notify observers
813
FireClearNetworkCacheStoredAnywhereNotification();
814
}
815
}
816
return EvictEntriesForClient(nullptr, storagePolicy);
817
}
818
819
NS_IMETHODIMP nsCacheService::GetCacheIOTarget(
820
nsIEventTarget** aCacheIOTarget) {
821
NS_ENSURE_ARG_POINTER(aCacheIOTarget);
822
823
// Because mCacheIOThread can only be changed on the main thread, it can be
824
// read from the main thread without the lock. This is useful to prevent
825
// blocking the main thread on other cache operations.
826
if (!NS_IsMainThread()) {
827
Lock(LOCK_TELEM(NSCACHESERVICE_GETCACHEIOTARGET));
828
}
829
830
nsresult rv;
831
if (mCacheIOThread) {
832
NS_ADDREF(*aCacheIOTarget = mCacheIOThread);
833
rv = NS_OK;
834
} else {
835
*aCacheIOTarget = nullptr;
836
rv = NS_ERROR_NOT_AVAILABLE;
837
}
838
839
if (!NS_IsMainThread()) {
840
Unlock();
841
}
842
843
return rv;
844
}
845
846
NS_IMETHODIMP nsCacheService::GetLockHeldTime(double* aLockHeldTime) {
847
MutexAutoLock lock(mTimeStampLock);
848
849
if (mLockAcquiredTimeStamp.IsNull()) {
850
*aLockHeldTime = 0.0;
851
} else {
852
*aLockHeldTime =
853
(TimeStamp::Now() - mLockAcquiredTimeStamp).ToMilliseconds();
854
}
855
856
return NS_OK;
857
}
858
859
/**
860
* Internal Methods
861
*/
862
nsresult nsCacheService::GetOfflineDevice(nsOfflineCacheDevice** aDevice) {
863
if (!mOfflineDevice) {
864
nsresult rv = CreateOfflineDevice();
865
NS_ENSURE_SUCCESS(rv, rv);
866
}
867
868
NS_ADDREF(*aDevice = mOfflineDevice);
869
return NS_OK;
870
}
871
872
nsresult nsCacheService::GetCustomOfflineDevice(
873
nsIFile* aProfileDir, int32_t aQuota, nsOfflineCacheDevice** aDevice) {
874
nsresult rv;
875
876
nsAutoString profilePath;
877
rv = aProfileDir->GetPath(profilePath);
878
NS_ENSURE_SUCCESS(rv, rv);
879
880
if (!mCustomOfflineDevices.Get(profilePath, aDevice)) {
881
rv = CreateCustomOfflineDevice(aProfileDir, aQuota, aDevice);
882
NS_ENSURE_SUCCESS(rv, rv);
883
884
(*aDevice)->SetAutoShutdown();
885
mCustomOfflineDevices.Put(profilePath, *aDevice);
886
}
887
888
return NS_OK;
889
}
890
891
nsresult nsCacheService::CreateOfflineDevice() {
892
CACHE_LOG_INFO(("Creating default offline device"));
893
894
if (mOfflineDevice) return NS_OK;
895
if (!nsCacheService::IsInitialized()) {
896
return NS_ERROR_NOT_AVAILABLE;
897
}
898
899
nsresult rv = CreateCustomOfflineDevice(
900
mObserver->OfflineCacheParentDirectory(),
901
mObserver->OfflineCacheCapacity(), &mOfflineDevice);
902
NS_ENSURE_SUCCESS(rv, rv);
903
904
return NS_OK;
905
}
906
907
nsresult nsCacheService::CreateCustomOfflineDevice(
908
nsIFile* aProfileDir, int32_t aQuota, nsOfflineCacheDevice** aDevice) {
909
NS_ENSURE_ARG(aProfileDir);
910
911
if (MOZ_LOG_TEST(gCacheLog, LogLevel::Info)) {
912
CACHE_LOG_INFO(("Creating custom offline device, %s, %d",
913
aProfileDir->HumanReadablePath().get(), aQuota));
914
}
915
916
if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
917
if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
918
919
RefPtr<nsOfflineCacheDevice> device = new nsOfflineCacheDevice();
920
921
// set the preferences
922
device->SetCacheParentDirectory(aProfileDir);
923
device->SetCapacity(aQuota);
924
925
nsresult rv = device->InitWithSqlite(mStorageService);
926
if (NS_FAILED(rv)) {
927
CACHE_LOG_DEBUG(("OfflineDevice->InitWithSqlite() failed (0x%.8" PRIx32
928
")\n",
929
static_cast<uint32_t>(rv)));
930
CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n"));
931
device = nullptr;
932
}
933
934
device.forget(aDevice);
935
return rv;
936
}
937
938
nsresult nsCacheService::RemoveCustomOfflineDevice(
939
nsOfflineCacheDevice* aDevice) {
940
nsCOMPtr<nsIFile> profileDir = aDevice->BaseDirectory();
941
if (!profileDir) return NS_ERROR_UNEXPECTED;
942
943
nsAutoString profilePath;
944
nsresult rv = profileDir->GetPath(profilePath);
945
NS_ENSURE_SUCCESS(rv, rv);
946
947
mCustomOfflineDevices.Remove(profilePath);
948
return NS_OK;
949
}
950
951
nsresult nsCacheService::CreateRequest(nsCacheSession* session,
952
const nsACString& clientKey,
953
nsCacheAccessMode accessRequested,
954
bool blockingMode,
955
nsICacheListener* listener,
956
nsCacheRequest** request) {
957
NS_ASSERTION(request, "CreateRequest: request is null");
958
959
nsAutoCString key(*session->ClientID());
960
key.Append(':');
961
key.Append(clientKey);
962
963
if (mMaxKeyLength < key.Length()) mMaxKeyLength = key.Length();
964
965
// create request
966
*request =
967
new nsCacheRequest(key, listener, accessRequested, blockingMode, session);
968
969
if (!listener) return NS_OK; // we're sync, we're done.
970
971
// get the request's thread
972
(*request)->mEventTarget = GetCurrentThreadEventTarget();
973
974
return NS_OK;
975
}
976
977
class nsCacheListenerEvent : public Runnable {
978
public:
979
nsCacheListenerEvent(nsICacheListener* listener,
980
nsICacheEntryDescriptor* descriptor,
981
nsCacheAccessMode accessGranted, nsresult status)
982
: mozilla::Runnable("nsCacheListenerEvent"),
983
mListener(listener) // transfers reference
984
,
985
mDescriptor(descriptor) // transfers reference (may be null)
986
,
987
mAccessGranted(accessGranted),
988
mStatus(status) {}
989
990
NS_IMETHOD Run() override {
991
mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus);
992
993
NS_RELEASE(mListener);
994
NS_IF_RELEASE(mDescriptor);
995
return NS_OK;
996
}
997
998
private:
999
// We explicitly leak mListener or mDescriptor if Run is not called
1000
// because otherwise we cannot guarantee that they are destroyed on
1001
// the right thread.
1002
1003
nsICacheListener* mListener;
1004
nsICacheEntryDescriptor* mDescriptor;
1005
nsCacheAccessMode mAccessGranted;
1006
nsresult mStatus;
1007
};
1008
1009
nsresult nsCacheService::NotifyListener(nsCacheRequest* request,
1010
nsICacheEntryDescriptor* descriptor,
1011
nsCacheAccessMode accessGranted,
1012
nsresult status) {
1013
NS_ASSERTION(request->mEventTarget, "no thread set in async request!");
1014
1015
// Swap ownership, and release listener on target thread...
1016
nsICacheListener* listener = request->mListener;
1017
request->mListener = nullptr;
1018
1019
nsCOMPtr<nsIRunnable> ev =
1020
new nsCacheListenerEvent(listener, descriptor, accessGranted, status);
1021
if (!ev) {
1022
// Better to leak listener and descriptor if we fail because we don't
1023
// want to destroy them inside the cache service lock or on potentially
1024
// the wrong thread.
1025
return NS_ERROR_OUT_OF_MEMORY;
1026
}
1027
1028
return request->mEventTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
1029
}
1030
1031
nsresult nsCacheService::ProcessRequest(nsCacheRequest* request,
1032
bool calledFromOpenCacheEntry,
1033
nsICacheEntryDescriptor** result) {
1034
// !!! must be called with mLock held !!!
1035
nsresult rv;
1036
nsCacheEntry* entry = nullptr;
1037
nsCacheEntry* doomedEntry = nullptr;
1038
nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
1039
if (result) *result = nullptr;
1040
1041
while (true) { // Activate entry loop
1042
rv = ActivateEntry(request, &entry,
1043
&doomedEntry); // get the entry for this request
1044
if (NS_FAILED(rv)) break;
1045
1046
while (true) { // Request Access loop
1047
NS_ASSERTION(entry, "no entry in Request Access loop!");
1048
// entry->RequestAccess queues request on entry
1049
rv = entry->RequestAccess(request, &accessGranted);
1050
if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
1051
1052
if (request->IsBlocking()) {
1053
if (request->mListener) {
1054
// async exits - validate, doom, or close will resume
1055
return rv;
1056
}
1057
1058
// XXX this is probably wrong...
1059
Unlock();
1060
rv = request->WaitForValidation();
1061
Lock(LOCK_TELEM(NSCACHESERVICE_PROCESSREQUEST));
1062
}
1063
1064
PR_REMOVE_AND_INIT_LINK(request);
1065
if (NS_FAILED(rv))
1066
break; // non-blocking mode returns WAIT_FOR_VALIDATION error
1067
// okay, we're ready to process this request, request access again
1068
}
1069
if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
1070
1071
if (entry->IsNotInUse()) {
1072
// this request was the last one keeping it around, so get rid of it
1073
DeactivateEntry(entry);
1074
}
1075
// loop back around to look for another entry
1076
}
1077
1078
if (NS_SUCCEEDED(rv) && request->mProfileDir) {
1079
// Custom cache directory has been demanded. Preset the cache device.
1080
if (entry->StoragePolicy() != nsICache::STORE_OFFLINE) {
1081
// Failsafe check: this is implemented only for offline cache atm.
1082
rv = NS_ERROR_FAILURE;
1083
} else {
1084
RefPtr<nsOfflineCacheDevice> customCacheDevice;
1085
rv = GetCustomOfflineDevice(request->mProfileDir, -1,
1086
getter_AddRefs(customCacheDevice));
1087
if (NS_SUCCEEDED(rv)) entry->SetCustomCacheDevice(customCacheDevice);
1088
}
1089
}
1090
1091
nsICacheEntryDescriptor* descriptor = nullptr;
1092
1093
if (NS_SUCCEEDED(rv))
1094
rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
1095
1096
// If doomedEntry is set, ActivatEntry() doomed an existing entry and
1097
// created a new one for that cache-key. However, any pending requests
1098
// on the doomed entry were not processed and we need to do that here.
1099
// This must be done after adding the created entry to list of active
1100
// entries (which is done in ActivateEntry()) otherwise the hashkeys crash
1101
// (see bug ##561313). It is also important to do this after creating a
1102
// descriptor for this request, or some other request may end up being
1103
// executed first for the newly created entry.
1104
// Finally, it is worth to emphasize that if doomedEntry is set,
1105
// ActivateEntry() created a new entry for the request, which will be
1106
// initialized by RequestAccess() and they both should have returned NS_OK.
1107
if (doomedEntry) {
1108
(void)ProcessPendingRequests(doomedEntry);
1109
if (doomedEntry->IsNotInUse()) DeactivateEntry(doomedEntry);
1110
doomedEntry = nullptr;
1111
}
1112
1113
if (request->mListener) { // Asynchronous
1114
1115
if (NS_FAILED(rv) && calledFromOpenCacheEntry && request->IsBlocking())
1116
return rv; // skip notifying listener, just return rv to caller
1117
1118
// call listener to report error or descriptor
1119
nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
1120
if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
1121
rv = rv2; // trigger delete request
1122
}
1123
} else { // Synchronous
1124
*result = descriptor;
1125
}
1126
return rv;
1127
}
1128
1129
nsresult nsCacheService::OpenCacheEntry(nsCacheSession* session,
1130
const nsACString& key,
1131
nsCacheAccessMode accessRequested,
1132
bool blockingMode,
1133
nsICacheListener* listener,
1134
nsICacheEntryDescriptor** result) {
1135
CACHE_LOG_DEBUG(
1136
("Opening entry for session %p, key %s, mode %d, blocking %d\n", session,
1137
PromiseFlatCString(key).get(), accessRequested, blockingMode));
1138
if (result) *result = nullptr;
1139
1140
if (!gService || !gService->mInitialized) return NS_ERROR_NOT_INITIALIZED;
1141
1142
nsCacheRequest* request = nullptr;
1143
1144
nsresult rv = gService->CreateRequest(session, key, accessRequested,
1145
blockingMode, listener, &request);
1146
if (NS_FAILED(rv)) return rv;
1147
1148
CACHE_LOG_DEBUG(("Created request %p\n", request));
1149
1150
// Process the request on the background thread if we are on the main thread
1151
// and the the request is asynchronous
1152
if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
1153
nsCOMPtr<nsIRunnable> ev = new nsProcessRequestEvent(request);
1154
rv = DispatchToCacheIOThread(ev);
1155
1156
// delete request if we didn't post the event
1157
if (NS_FAILED(rv)) delete request;
1158
} else {
1159
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_OPENCACHEENTRY));
1160
rv = gService->ProcessRequest(request, true, result);
1161
1162
// delete requests that have completed
1163
if (!(listener && blockingMode &&
1164
(rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
1165
delete request;
1166
}
1167
1168
return rv;
1169
}
1170
1171
nsresult nsCacheService::ActivateEntry(nsCacheRequest* request,
1172
nsCacheEntry** result,
1173
nsCacheEntry** doomedEntry) {
1174
CACHE_LOG_DEBUG(("Activate entry for request %p\n", request));
1175
if (!mInitialized || mClearingEntries) return NS_ERROR_NOT_AVAILABLE;
1176
1177
nsresult rv = NS_OK;
1178
1179
NS_ASSERTION(request != nullptr, "ActivateEntry called with no request");
1180
if (result) *result = nullptr;
1181
if (doomedEntry) *doomedEntry = nullptr;
1182
if ((!request) || (!result) || (!doomedEntry)) return NS_ERROR_NULL_POINTER;
1183
1184
// check if the request can be satisfied
1185
if (!request->IsStreamBased()) return NS_ERROR_FAILURE;
1186
if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
1187
return NS_ERROR_FAILURE;
1188
1189
// search active entries (including those not bound to device)
1190
nsCacheEntry* entry = mActiveEntries.GetEntry(&(request->mKey));
1191
CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry));
1192
1193
if (!entry) {
1194
// search cache devices for entry
1195
bool collision = false;
1196
entry = SearchCacheDevices(&(request->mKey), request->StoragePolicy(),
1197
&collision);
1198
CACHE_LOG_DEBUG(
1199
("Device search for request %p returned %p\n", request, entry));
1200
// When there is a hashkey collision just refuse to cache it...
1201
if (collision) return NS_ERROR_CACHE_IN_USE;
1202
1203
if (entry) entry->MarkInitialized();
1204
} else {
1205
NS_ASSERTION(entry->IsActive(), "Inactive entry found in mActiveEntries!");
1206
}
1207
1208
if (entry) {
1209
++mCacheHits;
1210
entry->Fetched();
1211
} else {
1212
++mCacheMisses;
1213
}
1214
1215
if (entry && ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
1216
((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
1217
(entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
1218
request->WillDoomEntriesIfExpired()))))
1219
1220
{
1221
// this is FORCE-WRITE request or the entry has expired
1222
// we doom entry without processing pending requests, but store it in
1223
// doomedEntry which causes pending requests to be processed below
1224
rv = DoomEntry_Internal(entry, false);
1225
*doomedEntry = entry;
1226
if (NS_FAILED(rv)) {
1227
// XXX what to do? Increment FailedDooms counter?
1228
}
1229
entry = nullptr;
1230
}
1231
1232
if (!entry) {
1233
if (!(request->AccessRequested() & nsICache::ACCESS_WRITE)) {
1234
// this is a READ-ONLY request
1235
rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
1236
goto error;
1237
}
1238
1239
entry = new nsCacheEntry(request->mKey, request->IsStreamBased(),
1240
request->StoragePolicy());
1241
if (!entry) return NS_ERROR_OUT_OF_MEMORY;
1242
1243
if (request->IsPrivate()) entry->MarkPrivate();
1244
1245
entry->Fetched();
1246
++mTotalEntries;
1247
1248
// XXX we could perform an early bind in some cases based on storage policy
1249
}
1250
1251
if (!entry->IsActive()) {
1252
rv = mActiveEntries.AddEntry(entry);
1253
if (NS_FAILED(rv)) goto error;
1254
CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry));
1255
entry->MarkActive(); // mark entry active, because it's now in
1256
// mActiveEntries
1257
}
1258
*result = entry;
1259
return NS_OK;
1260
1261
error:
1262
*result = nullptr;
1263
delete entry;
1264
return rv;
1265
}
1266
1267
nsCacheEntry* nsCacheService::SearchCacheDevices(nsCString* key,
1268
nsCacheStoragePolicy policy,
1269
bool* collision) {
1270
Telemetry::AutoTimer<Telemetry::CACHE_DEVICE_SEARCH_2> timer;
1271
nsCacheEntry* entry = nullptr;
1272
1273
*collision = false;
1274
if (policy == nsICache::STORE_OFFLINE ||
1275
(policy == nsICache::STORE_ANYWHERE && gIOService->IsOffline())) {
1276
if (mEnableOfflineDevice) {
1277
if (!mOfflineDevice) {
1278
nsresult rv = CreateOfflineDevice();
1279
if (NS_FAILED(rv)) return nullptr;
1280
}
1281
1282
entry = mOfflineDevice->FindEntry(key, collision);
1283
}
1284
}
1285
1286
return entry;
1287
}
1288
1289
nsCacheDevice* nsCacheService::EnsureEntryHasDevice(nsCacheEntry* entry) {
1290
nsCacheDevice* device = entry->CacheDevice();
1291
// return device if found, possibly null if the entry is doomed i.e prevent
1292
// doomed entries to bind to a device (see e.g. bugs #548406 and #596443)
1293
if (device || entry->IsDoomed()) return device;
1294
1295
if (!device && entry->IsStreamData() && entry->IsAllowedOffline() &&
1296
mEnableOfflineDevice) {
1297
if (!mOfflineDevice) {
1298
(void)CreateOfflineDevice(); // ignore the error (check for
1299
// mOfflineDevice instead)
1300
}
1301
1302
device = entry->CustomCacheDevice() ? entry->CustomCacheDevice()
1303
: mOfflineDevice;
1304
1305
if (device) {
1306
entry->MarkBinding();
1307
nsresult rv = device->BindEntry(entry);
1308
entry->ClearBinding();
1309
if (NS_FAILED(rv)) device = nullptr;
1310
}
1311
}
1312
1313
if (device) entry->SetCacheDevice(device);
1314
return device;
1315
}
1316
1317
nsresult nsCacheService::DoomEntry(nsCacheEntry* entry) {
1318
return gService->DoomEntry_Internal(entry, true);
1319
}
1320
1321
nsresult nsCacheService::DoomEntry_Internal(nsCacheEntry* entry,
1322
bool doProcessPendingRequests) {
1323
if (entry->IsDoomed()) return NS_OK;
1324
1325
CACHE_LOG_DEBUG(("Dooming entry %p\n", entry));
1326
nsresult rv = NS_OK;
1327
entry->MarkDoomed();
1328
1329
NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
1330
nsCacheDevice* device = entry->CacheDevice();
1331
if (device) device->DoomEntry(entry);
1332
1333
if (entry->IsActive()) {
1334
// remove from active entries
1335
mActiveEntries.RemoveEntry(entry);
1336
CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry));
1337
entry->MarkInactive();
1338
}
1339
1340
// put on doom list to wait for descriptors to close
1341
NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
1342
PR_APPEND_LINK(entry, &mDoomedEntries);
1343
1344
// handle pending requests only if we're supposed to
1345
if (doProcessPendingRequests) {
1346
// tell pending requests to get on with their lives...
1347
rv = ProcessPendingRequests(entry);
1348
1349
// All requests have been removed, but there may still be open descriptors
1350
if (entry->IsNotInUse()) {
1351
DeactivateEntry(entry); // tell device to get rid of it
1352
}
1353
}
1354
return rv;
1355
}
1356
1357
void nsCacheService::OnProfileShutdown() {
1358
if (!gService || !gService->mInitialized) {
1359
// The cache service has been shut down, but someone is still holding
1360
// a reference to it. Ignore this call.
1361
return;
1362
}
1363
1364
{
1365
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
1366
gService->mClearingEntries = true;
1367
gService->DoomActiveEntries(nullptr);
1368
}
1369
1370
gService->CloseAllStreams();
1371
1372
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
1373
gService->ClearDoomList();
1374
1375
// Make sure to wait for any pending cache-operations before
1376
// proceeding with destructive actions (bug #620660)
1377
(void)SyncWithCacheIOThread();
1378
1379
if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
1380
gService->mOfflineDevice->Shutdown();
1381
}
1382
for (auto iter = gService->mCustomOfflineDevices.Iter(); !iter.Done();
1383
iter.Next()) {
1384
iter.Data()->Shutdown();
1385
iter.Remove();
1386
}
1387
1388
gService->mEnableOfflineDevice = false;
1389
1390
gService->mClearingEntries = false;
1391
}
1392
1393
void nsCacheService::OnProfileChanged() {
1394
if (!gService) return;
1395
1396
CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
1397
1398
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILECHANGED));
1399
1400
gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
1401
1402
if (gService->mOfflineDevice) {
1403
gService->mOfflineDevice->SetCacheParentDirectory(
1404
gService->mObserver->OfflineCacheParentDirectory());
1405
gService->mOfflineDevice->SetCapacity(
1406
gService->mObserver->OfflineCacheCapacity());
1407
1408
// XXX initialization of mOfflineDevice could be made lazily, if
1409
// mEnableOfflineDevice is false
1410
nsresult rv =
1411
gService->mOfflineDevice->InitWithSqlite(gService->mStorageService);
1412
if (NS_FAILED(rv)) {
1413
NS_ERROR(
1414
"nsCacheService::OnProfileChanged: Re-initializing offline device "
1415
"failed");
1416
gService->mEnableOfflineDevice = false;
1417
// XXX delete mOfflineDevice?
1418
}
1419
}
1420
}
1421
1422
void nsCacheService::SetOfflineCacheEnabled(bool enabled) {
1423
if (!gService) return;
1424
nsCacheServiceAutoLock lock(
1425
LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHEENABLED));
1426
gService->mEnableOfflineDevice = enabled;
1427
}
1428
1429
void nsCacheService::SetOfflineCacheCapacity(int32_t capacity) {
1430
if (!gService) return;
1431
nsCacheServiceAutoLock lock(
1432
LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHECAPACITY));
1433
1434
if (gService->mOfflineDevice) {
1435
gService->mOfflineDevice->SetCapacity(capacity);
1436
}
1437
1438
gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
1439
}
1440
1441
/******************************************************************************
1442
* static methods for nsCacheEntryDescriptor
1443
*****************************************************************************/
1444
void nsCacheService::CloseDescriptor(nsCacheEntryDescriptor* descriptor) {
1445
// ask entry to remove descriptor
1446
nsCacheEntry* entry = descriptor->CacheEntry();
1447
bool doomEntry;
1448
bool stillActive = entry->RemoveDescriptor(descriptor, &doomEntry);
1449
1450
if (!entry->IsValid()) {
1451
gService->ProcessPendingRequests(entry);
1452
}
1453
1454
if (doomEntry) {
1455
gService->DoomEntry_Internal(entry, true);
1456
return;
1457
}
1458
1459
if (!stillActive) {
1460
gService->DeactivateEntry(entry);
1461
}
1462
}
1463
1464
nsresult nsCacheService::GetFileForEntry(nsCacheEntry* entry,
1465
nsIFile** result) {
1466
nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
1467
if (!device) return NS_ERROR_UNEXPECTED;
1468
1469
return device->GetFileForEntry(entry, result);
1470
}
1471
1472
nsresult nsCacheService::OpenInputStreamForEntry(nsCacheEntry* entry,
1473
nsCacheAccessMode mode,
1474
uint32_t offset,
1475
nsIInputStream** result) {
1476
nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
1477
if (!device) return NS_ERROR_UNEXPECTED;
1478
1479
return device->OpenInputStreamForEntry(entry, mode, offset, result);
1480
}
1481
1482
nsresult nsCacheService::OpenOutputStreamForEntry(nsCacheEntry* entry,
1483
nsCacheAccessMode mode,
1484
uint32_t offset,
1485
nsIOutputStream** result) {
1486
nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
1487
if (!device) return NS_ERROR_UNEXPECTED;
1488
1489
return device->OpenOutputStreamForEntry(entry, mode, offset, result);
1490
}
1491
1492
nsresult nsCacheService::OnDataSizeChange(nsCacheEntry* entry,
1493
int32_t deltaSize) {
1494
nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
1495
if (!device) return NS_ERROR_UNEXPECTED;
1496
1497
return device->OnDataSizeChange(entry, deltaSize);
1498
}
1499
1500
void nsCacheService::LockAcquired() {
1501
MutexAutoLock lock(mTimeStampLock);
1502
mLockAcquiredTimeStamp = TimeStamp::Now();
1503
}
1504
1505
void nsCacheService::LockReleased() {
1506
MutexAutoLock lock(mTimeStampLock);
1507
mLockAcquiredTimeStamp = TimeStamp();
1508
}
1509
1510
void nsCacheService::Lock() {
1511
gService->mLock.Lock();
1512
gService->LockAcquired();
1513
}
1514
1515
void nsCacheService::Lock(mozilla::Telemetry::HistogramID mainThreadLockerID) {
1516
mozilla::Telemetry::HistogramID lockerID;
1517
mozilla::Telemetry::HistogramID generalID;
1518
1519
if (NS_IsMainThread()) {
1520
lockerID = mainThreadLockerID;
1521
generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_2;
1522
} else {
1523
lockerID = mozilla::Telemetry::HistogramCount;
1524
generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_2;
1525
}
1526
1527
TimeStamp start(TimeStamp::Now());
1528
1529
nsCacheService::Lock();
1530
1531
TimeStamp stop(TimeStamp::Now());
1532
1533
// Telemetry isn't thread safe on its own, but this is OK because we're
1534
// protecting it with the cache lock.
1535
if (lockerID != mozilla::Telemetry::HistogramCount) {
1536
mozilla::Telemetry::AccumulateTimeDelta(lockerID, start, stop);
1537
}
1538
mozilla::Telemetry::AccumulateTimeDelta(generalID, start, stop);
1539
}
1540
1541
void nsCacheService::Unlock() {
1542
gService->mLock.AssertCurrentThreadOwns();
1543
1544
nsTArray<nsISupports*> doomed;
1545
doomed.SwapElements(gService->mDoomedObjects);
1546
1547
gService->LockReleased();
1548
gService->mLock.Unlock();
1549
1550
for (uint32_t i = 0; i < doomed.Length(); ++i) doomed[i]->Release();
1551
}
1552
1553
void nsCacheService::ReleaseObject_Locked(nsISupports* obj,
1554
nsIEventTarget* target) {
1555
gService->mLock.AssertCurrentThreadOwns();
1556
1557
bool isCur;
1558
if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
1559
gService->mDoomedObjects.AppendElement(obj);
1560
} else {
1561
NS_ProxyRelease("nsCacheService::ReleaseObject_Locked::obj", target,
1562
dont_AddRef(obj));
1563
}
1564
}
1565
1566
nsresult nsCacheService::SetCacheElement(nsCacheEntry* entry,
1567
nsISupports* element) {
1568
entry->SetData(element);
1569
entry->TouchData();
1570
return NS_OK;
1571
}
1572
1573
nsresult nsCacheService::ValidateEntry(nsCacheEntry* entry) {
1574
nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
1575
if (!device) return NS_ERROR_UNEXPECTED;
1576
1577
entry->MarkValid();
1578
nsresult rv = gService->ProcessPendingRequests(entry);
1579
NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
1580
// XXX what else should be done?
1581
1582
return rv;
1583
}
1584
1585
int32_t nsCacheService::CacheCompressionLevel() {
1586
int32_t level = gService->mObserver->CacheCompressionLevel();
1587
return level;
1588
}
1589
1590
void nsCacheService::DeactivateEntry(nsCacheEntry* entry) {
1591
CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry));
1592
nsresult rv = NS_OK;
1593
NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
1594
nsCacheDevice* device = nullptr;
1595
1596
if (mMaxDataSize < entry->DataSize()) mMaxDataSize = entry->DataSize();
1597
if (mMaxMetaSize < entry->MetaDataSize())
1598
mMaxMetaSize = entry->MetaDataSize();
1599
1600
if (entry->IsDoomed()) {
1601
// remove from Doomed list
1602
PR_REMOVE_AND_INIT_LINK(entry);
1603
} else if (entry->IsActive()) {
1604
// remove from active entries
1605
mActiveEntries.RemoveEntry(entry);
1606
CACHE_LOG_DEBUG(
1607
("Removed deactivated entry %p from mActiveEntries\n", entry));
1608
entry->MarkInactive();
1609
1610
// bind entry if necessary to store meta-data
1611
device = EnsureEntryHasDevice(entry);
1612
if (!device) {
1613
CACHE_LOG_DEBUG(
1614
("DeactivateEntry: unable to bind active "
1615
"entry %p\n",
1616
entry));
1617
NS_WARNING("DeactivateEntry: unable to bind active entry\n");
1618
return;
1619
}
1620
} else {
1621
// if mInitialized == false,
1622
// then we're shutting down and this state is okay.
1623
NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
1624
}
1625
1626
device = entry->CacheDevice();
1627
if (device) {
1628
rv = device->DeactivateEntry(entry);
1629
if (NS_FAILED(rv)) {
1630
// increment deactivate failure count
1631
++mDeactivateFailures;
1632
}
1633
} else {
1634
// increment deactivating unbound entry statistic
1635
++mDeactivatedUnboundEntries;
1636
delete entry; // because no one else will
1637
}
1638
}
1639
1640
nsresult nsCacheService::ProcessPendingRequests(nsCacheEntry* entry) {
1641
nsresult rv = NS_OK;
1642
nsCacheRequest* request = (nsCacheRequest*)PR_LIST_HEAD(&entry->mRequestQ);
1643
nsCacheRequest* nextRequest;
1644
bool newWriter = false;
1645
1646
CACHE_LOG_DEBUG((
1647
"ProcessPendingRequests for %sinitialized %s %salid entry %p\n",
1648
(entry->IsInitialized() ? "" : "Un"), (entry->IsDoomed() ? "DOOMED" : ""),
1649
(entry->IsValid() ? "V" : "Inv"), entry));
1650
1651
if (request == &entry->mRequestQ) return NS_OK; // no queued requests
1652
1653
if (!entry->IsDoomed() && entry->IsInvalid()) {
1654
// 1st descriptor closed w/o MarkValid()
1655
NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ),
1656
"shouldn't be here with open descriptors");
1657
1658
#if DEBUG
1659
// verify no ACCESS_WRITE requests(shouldn't have any of these)
1660
while (request != &entry->mRequestQ) {
1661
NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
1662
"ACCESS_WRITE request should have been given a new entry");
1663
request = (nsCacheRequest*)PR_NEXT_LINK(request);
1664
}
1665
request = (nsCacheRequest*)PR_LIST_HEAD(&entry->mRequestQ);
1666
#endif
1667
// find first request with ACCESS_READ_WRITE (if any) and promote it to 1st
1668
// writer
1669
while (request != &entry->mRequestQ) {
1670
if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
1671
newWriter = true;
1672
CACHE_LOG_DEBUG((" promoting request %p to 1st writer\n", request));
1673
break;
1674
}
1675
1676
request = (nsCacheRequest*)PR_NEXT_LINK(request);
1677
}
1678
1679
if (request == &entry->mRequestQ) // no requests asked for
1680
// ACCESS_READ_WRITE, back to top
1681
request = (nsCacheRequest*)PR_LIST_HEAD(&entry->mRequestQ);
1682
1683
// XXX what should we do if there are only READ requests in queue?
1684
// XXX serialize their accesses, give them only read access, but force them
1685
// to check validate flag?
1686
// XXX or do readers simply presume the entry is valid
1687
// See fix for bug #467392 below
1688
}
1689
1690
nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
1691
1692
while (request != &entry->mRequestQ) {
1693
nextRequest = (nsCacheRequest*)PR_NEXT_LINK(request);
1694
CACHE_LOG_DEBUG((" %sync request %p for %p\n",
1695
(request->mListener ? "As" : "S"), request, entry));
1696
1697
if (request->mListener) {
1698
// Async request
1699
PR_REMOVE_AND_INIT_LINK(request);
1700
1701
if (entry->IsDoomed()) {
1702
rv = ProcessRequest(request, false, nullptr);
1703
if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
1704
rv = NS_OK;
1705
else
1706
delete request;
1707
1708
if (NS_FAILED(rv)) {
1709
// XXX what to do?
1710
}
1711
} else if (entry->IsValid() || newWriter) {
1712
rv = entry->RequestAccess(request, &accessGranted);
1713
NS_ASSERTION(NS_SUCCEEDED(rv),
1714
"if entry is valid, RequestAccess must succeed.");
1715
// XXX if (newWriter) {
1716
// NS_ASSERTION( accessGranted ==
1717
// request->AccessRequested(), "why not?");
1718
// }
1719
1720
// entry->CreateDescriptor dequeues request, and queues descriptor
1721
nsICacheEntryDescriptor* descriptor = nullptr;
1722
rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
1723
1724
// post call to listener to report error or descriptor
1725
rv = NotifyListener(request, descriptor, accessGranted, rv);
1726
delete request;
1727
if (NS_FAILED(rv)) {
1728
// XXX what to do?
1729
}
1730
1731
} else {
1732
// read-only request to an invalid entry - need to wait for
1733
// the entry to become valid so we post an event to process
1734
// the request again later (bug #467392)
1735
nsCOMPtr<nsIRunnable> ev = new nsProcessRequestEvent(request);
1736
rv = DispatchToCacheIOThread(ev);
1737
if (NS_FAILED(rv)) {
1738
delete request; // avoid leak
1739
}
1740
}
1741
} else {
1742
// Synchronous request
1743
request->WakeUp();
1744
}
1745
if (newWriter) break; // process remaining requests after validation
1746
request = nextRequest;
1747
}
1748
1749
return NS_OK;
1750
}
1751
1752
bool nsCacheService::IsDoomListEmpty() {
1753
nsCacheEntry* entry = (nsCacheEntry*)PR_LIST_HEAD(&mDoomedEntries);
1754
return &mDoomedEntries == entry;
1755
}
1756
1757
void nsCacheService::ClearDoomList() {
1758
nsCacheEntry* entry = (nsCacheEntry*)PR_LIST_HEAD(&mDoomedEntries);
1759
1760
while (entry != &mDoomedEntries) {